Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4709c3b2b7 | ||
|
|
2466472235 | ||
|
|
a69ef7b927 | ||
|
|
b70a35135c | ||
|
|
c8f820e238 | ||
|
|
caa0833ea2 | ||
|
|
166b7079ca | ||
|
|
ae9d9fa66b | ||
|
|
bca1c911d3 | ||
|
|
5bb2a6c192 | ||
|
|
d19dab7561 | ||
|
|
bb3e55eecb | ||
|
|
febf91d417 | ||
|
|
6fe3357efe | ||
|
|
a2da030a99 | ||
|
|
03263815e6 | ||
|
|
3043f30012 | ||
|
|
2dd165b451 | ||
|
|
59d0dd7ab4 | ||
|
|
4e3b561f61 | ||
|
|
9f4fb6c274 | ||
|
|
90856717e4 |
11 changed files with 3291 additions and 1272 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/target
|
||||
.tbcfg
|
||||
*.txt
|
||||
*.log
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/target
|
||||
.tbcfg
|
||||
.tbcfg*
|
||||
*.txt
|
||||
*.log
|
||||
|
||||
|
|
|
|||
2653
Cargo.lock
generated
2653
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
|
@ -4,19 +4,28 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
async-compression = { version = "0.4.39", features = ["tokio", "brotli"] }
|
||||
axum = "0.8.7"
|
||||
axum-macros = "0.5.0"
|
||||
axum-extra = { version = "0.12.6", features = ["multipart"] }
|
||||
bb8 = "0.9.1"
|
||||
bb8-redis = "0.26.0"
|
||||
brotli = "8.0.2"
|
||||
bytes = "1.11.1"
|
||||
env_logger = "0.11.8"
|
||||
futures-util = "0.3.31"
|
||||
git2 = { version = "0.20.3", features = ["https", "ssh"] }
|
||||
image = "0.25.9"
|
||||
json-patch = "4.1.0"
|
||||
libgit2-sys = { version = "0.18.3", features = ["ssh"] }
|
||||
libtbr = { git = "https://gitlab.forthrd.io/Pakin/libtbr.git", version = "0.1.1" }
|
||||
libtbr = { git = "https://pakin-inspiron-15-3530.tail110d9.ts.net/pakin/libtbr.git", version = "0.1.1" }
|
||||
log = "0.4.29"
|
||||
prost = "0.14.1"
|
||||
redis = { version = "1.0.2", features = ["tokio-comp"] }
|
||||
reqwest = { version = "0.12.25", features = ["json"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = { version = "1.0.145", features = ["preserve_order"] }
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
tonic = { version = "0.14.2", features = ["transport"] }
|
||||
tonic-prost = "0.14.2"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-prost-build = "0.14.2"
|
||||
tokio-util = { version = "0.7.18", features = ["io"] }
|
||||
uuid = { version = "1.20.0", features = ["v4"] }
|
||||
tokio-postgres = "0.7.17"
|
||||
chrono = "0.4.45"
|
||||
|
|
|
|||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
FROM rustlang/rust:nightly-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt update && apt install -y \
|
||||
musl-tools \
|
||||
musl-dev \
|
||||
build-essential \
|
||||
libssl-dev \
|
||||
curl \
|
||||
pkg-config \
|
||||
ca-certificates \
|
||||
protobuf-compiler \
|
||||
zlib1g
|
||||
|
||||
# Copy dependency files first for better caching
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY ./src ./src
|
||||
COPY build.rs ./
|
||||
COPY tbm-proto ./tbm-proto
|
||||
|
||||
# Download config
|
||||
RUN curl -X GET https://pakin-inspiron-15-3530.tail110d9.ts.net/pakin/tbm-git-repo-service/releases/download/config/.tbcfg -o .tbcfg
|
||||
|
||||
# Build the application
|
||||
ENV RUSTFLAGS="-C target-feature=+crt-static"
|
||||
RUN cargo build --release
|
||||
|
||||
# Builder
|
||||
FROM gcr.io/distroless/cc
|
||||
|
||||
# Copy the binary
|
||||
COPY --from=builder /app/target/release/tbm-git-repo-service /
|
||||
COPY --from=builder /app/.tbcfg /
|
||||
|
||||
# Create data directory
|
||||
|
||||
EXPOSE 36583
|
||||
|
||||
# Environment defaults
|
||||
|
||||
CMD ["./tbm-git-repo-service"]
|
||||
183
README.md
Normal file
183
README.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# tbm-git-service
|
||||
|
||||
Git repo as a service with operational endpoints
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Checkout file
|
||||
|
||||
Get file content or list of file in directory from HEAD
|
||||
|
||||
```
|
||||
GET .../checkout?path=<path_to_file>
|
||||
```
|
||||
|
||||
Examples
|
||||
|
||||
Get list of files in directory
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:36593/checkout\?path\=inter
|
||||
aus,common,dev,gbr,gbr_premium,hkg,ltu,mys,rou,sgp,tha,tha_premium,uae_dubai,usa,whatthecup
|
||||
```
|
||||
|
||||
Get file
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:36593/checkout\?path\=inter/mys/xml/page_catalog_group_other.lxml
|
||||
|
||||
<Popup>
|
||||
<Cache> "Enable" </Cache>
|
||||
<Width> 1080 </Width>
|
||||
<Height> 1920 </Height>
|
||||
;<Background> "0xeae6e1" </Background>
|
||||
<Volume> SoundVolume </Volume>
|
||||
<EventOpen>
|
||||
; On open
|
||||
|
||||
```
|
||||
|
||||
Error
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:36593/checkout\?path\=inter2
|
||||
File not found
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fetch
|
||||
|
||||
Fetching & update index of current repo
|
||||
|
||||
```
|
||||
GET .../fetch
|
||||
```
|
||||
|
||||
Expected result
|
||||
|
||||
```
|
||||
{"result": "fetch success"}
|
||||
```
|
||||
|
||||
Error
|
||||
|
||||
```
|
||||
{"error": "..."}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Commit
|
||||
|
||||
Commit changes of file i.e. editing, adding, etc.
|
||||
|
||||
```
|
||||
POST .../commit
|
||||
```
|
||||
|
||||
Body must be multipart
|
||||
|
||||
- Single file
|
||||
|
||||
```
|
||||
path: string
|
||||
file: text/binary is acceptable
|
||||
signature_username: string
|
||||
signature_email: stirng
|
||||
message: string
|
||||
```
|
||||
|
||||
- Multiple file
|
||||
|
||||
```
|
||||
signature_username: string
|
||||
signature_email: stirng
|
||||
message: string
|
||||
fileX1: text/binary is acceptable
|
||||
pathX1: string
|
||||
fileX2: text/binary is acceptable
|
||||
pathX2: string
|
||||
...
|
||||
```
|
||||
|
||||
Expected result
|
||||
|
||||
```
|
||||
{"result": "commit hash id"}
|
||||
```
|
||||
|
||||
Error
|
||||
|
||||
```
|
||||
{"error": "..."}
|
||||
```
|
||||
|
||||
Example
|
||||
|
||||
```curl
|
||||
curl --request POST \
|
||||
--url http://localhost:36583/commit \
|
||||
--header 'content-type: multipart/form-data' \
|
||||
--form path=mys/version.dev \
|
||||
--form 'signature_username=git api' \
|
||||
--form signature_email=supra.m2.dev@forth.co.th \
|
||||
--form 'message=test commit' \
|
||||
--form file=@./mys.version.test
|
||||
```
|
||||
|
||||
NOTE: file & path in multiple file must have the same name after
|
||||
|
||||
---
|
||||
|
||||
### Push
|
||||
|
||||
Push local commits to remote
|
||||
|
||||
```
|
||||
GET .../push
|
||||
```
|
||||
|
||||
Expected result
|
||||
|
||||
```
|
||||
{"result": "push completed"}
|
||||
```
|
||||
|
||||
Error
|
||||
|
||||
```
|
||||
{"error": "..."}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Pull
|
||||
|
||||
Pull commits from remote. This operation always does git reset hard first before pull for reason of no conflicts.
|
||||
|
||||
```
|
||||
GET .../pull
|
||||
```
|
||||
|
||||
Expected result
|
||||
|
||||
```
|
||||
{"result": "pull completed"}
|
||||
```
|
||||
|
||||
Error
|
||||
|
||||
```
|
||||
{"error": "..."}
|
||||
```
|
||||
|
||||
NOTE: Commit did lost but could be checkout later
|
||||
|
||||
---
|
||||
|
||||
### Recovery
|
||||
|
||||
WIP...
|
||||
5
build.rs
5
build.rs
|
|
@ -1,5 +0,0 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_prost_build::configure().compile_protos(&["tbm-proto/registry.proto"], &["tbm-proto"])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1478
src/app.rs
1478
src/app.rs
File diff suppressed because it is too large
Load diff
90
src/git.rs
90
src/git.rs
|
|
@ -1,16 +1,21 @@
|
|||
use std::{cell::RefCell, collections::HashMap, io::{self, Write}, path::{Path, PathBuf}};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use git2::{build::RepoBuilder, Cred, FetchOptions, Progress, RemoteCallbacks};
|
||||
use log::{info};
|
||||
use git2::{Cred, FetchOptions, Progress, RemoteCallbacks, build::RepoBuilder};
|
||||
use log::info;
|
||||
|
||||
use crate::gcm;
|
||||
|
||||
struct GitState {
|
||||
progress: Option<Progress<'static>>,
|
||||
total: usize,
|
||||
current: usize,
|
||||
path: Option<PathBuf>,
|
||||
newline: bool
|
||||
progress: Option<Progress<'static>>,
|
||||
total: usize,
|
||||
current: usize,
|
||||
path: Option<PathBuf>,
|
||||
newline: bool,
|
||||
}
|
||||
|
||||
fn print(state: &mut GitState) {
|
||||
|
|
@ -29,10 +34,11 @@ fn print(state: &mut GitState) {
|
|||
state.newline = true;
|
||||
}
|
||||
|
||||
info!("Resolving deltas {}/{}",
|
||||
stats.indexed_deltas(),
|
||||
stats.total_deltas());
|
||||
|
||||
info!(
|
||||
"Resolving deltas {}/{}",
|
||||
stats.indexed_deltas(),
|
||||
stats.total_deltas()
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"net {:3}% ({:4} kb, {:5}/{:5}) / idx {:3}% ({:5}/{:5}) \
|
||||
|
|
@ -58,40 +64,38 @@ fn print(state: &mut GitState) {
|
|||
}
|
||||
|
||||
pub fn setup_git_repo(config: HashMap<String, String>) -> gcm::StandardResult {
|
||||
let state = RefCell::new(GitState {
|
||||
progress: None,
|
||||
total: 0,
|
||||
current: 0,
|
||||
path: None,
|
||||
newline: true
|
||||
});
|
||||
let state = RefCell::new(GitState {
|
||||
progress: None,
|
||||
total: 0,
|
||||
current: 0,
|
||||
path: None,
|
||||
newline: true,
|
||||
});
|
||||
|
||||
let mut cb = RemoteCallbacks::new();
|
||||
cb.transfer_progress(|stats| {
|
||||
let mut state = state.borrow_mut();
|
||||
state.progress = Some(stats.to_owned());
|
||||
print(&mut state);
|
||||
true
|
||||
});
|
||||
let mut cb = RemoteCallbacks::new();
|
||||
cb.transfer_progress(|stats| {
|
||||
let mut state = state.borrow_mut();
|
||||
state.progress = Some(stats.to_owned());
|
||||
print(&mut state);
|
||||
true
|
||||
});
|
||||
|
||||
cb.credentials(|_,_,_| {
|
||||
Cred::userpass_plaintext(
|
||||
config.get("GIT_REPO_USERNAME").unwrap_or(&"".to_string()),
|
||||
config.get("GIT_REPO_PASSWORD").unwrap_or(&"".to_string())
|
||||
)
|
||||
});
|
||||
cb.credentials(|_, _, _| {
|
||||
Cred::userpass_plaintext(
|
||||
config.get("GIT_REPO_USERNAME").unwrap_or(&"".to_string()),
|
||||
config.get("GIT_REPO_PASSWORD").unwrap_or(&"".to_string()),
|
||||
)
|
||||
});
|
||||
|
||||
let mut fo = FetchOptions::new();
|
||||
fo.remote_callbacks(cb);
|
||||
fo.depth(1);
|
||||
let mut fo = FetchOptions::new();
|
||||
fo.remote_callbacks(cb);
|
||||
|
||||
RepoBuilder::new()
|
||||
.bare(true)
|
||||
.fetch_options(fo)
|
||||
.clone(
|
||||
config.get("GIT_REPO_REMOTE").unwrap_or(&"".to_string()),
|
||||
Path::new(config.get("GIT_REPO_LOCAL_DEST").unwrap()).into()
|
||||
let _ = RepoBuilder::new().bare(false).fetch_options(fo).clone(
|
||||
config.get("GIT_REPO_REMOTE").unwrap_or(&"".to_string()),
|
||||
Path::new(config.get("GIT_REPO_LOCAL_DEST").unwrap()).into(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
println!("clone completed !");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
74
src/main.rs
74
src/main.rs
|
|
@ -1,56 +1,64 @@
|
|||
use std::{fs::File};
|
||||
|
||||
use env_logger::Builder;
|
||||
use libtbr::recipe_functions::common;
|
||||
use log::info;
|
||||
|
||||
use crate::{git::setup_git_repo, reg::{heartbeat_loop, registry::registry_client::RegistryClient}};
|
||||
use crate::{
|
||||
git::setup_git_repo,
|
||||
// reg::{heartbeat_loop, registry::registry_client::RegistryClient},
|
||||
};
|
||||
|
||||
mod app;
|
||||
mod gcm;
|
||||
mod git;
|
||||
mod reg;
|
||||
// mod reg;
|
||||
|
||||
fn setup_log(config: gcm::Configure) -> gcm::StandardResult {
|
||||
let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?;
|
||||
fn setup_log(_config: gcm::Configure) -> gcm::StandardResult {
|
||||
// NOTE: disable logging file, use send to log service instead
|
||||
// let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?;
|
||||
|
||||
Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
|
||||
.target(env_logger::Target::Pipe(Box::new(logfile))).init();
|
||||
Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
|
||||
// .target(env_logger::Target::Pipe(Box::new(logfile)))
|
||||
.init();
|
||||
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_registry(config: gcm::Configure) -> gcm::StandardResult {
|
||||
let cfg_clone = config.clone();
|
||||
let register_server_address = cfg_clone.get("REGISTRY_SERVER").expect("not found registry server");
|
||||
let name = cfg_clone.get("SERVICE_NAME").expect("missing service name");
|
||||
let url = cfg_clone.get("SERVICE_URL").expect("missing service url");
|
||||
|
||||
let mut client = RegistryClient::connect(register_server_address.clone()).await?;
|
||||
reg::register_service(&mut client, name, url).await;
|
||||
|
||||
tokio::spawn(heartbeat_loop(name.to_string(), url.to_string()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> gcm::StandardResult {
|
||||
let config: gcm::Configure = common::get_config();
|
||||
setup_log(config.clone())?;
|
||||
|
||||
match std::fs::read_dir(config.get("GIT_REPO_LOCAL_DEST").expect("not exist")) {
|
||||
Ok(_) => {
|
||||
info!("GIT_REPO `{dest}` already setup", dest = config.get("GIT_REPO_LOCAL_DEST").unwrap())
|
||||
},
|
||||
Err(_) => {
|
||||
setup_git_repo(config.clone())?;
|
||||
}
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"GIT_REPO `{dest}` existed, checking if has git",
|
||||
dest = config.get("GIT_REPO_LOCAL_DEST").unwrap()
|
||||
);
|
||||
|
||||
match std::fs::read_dir(format!(
|
||||
"{}/.git",
|
||||
config.get("GIT_REPO_LOCAL_DEST").expect("not exist")
|
||||
)) {
|
||||
Ok(_) => {
|
||||
info!("GIT REPO already set up!")
|
||||
}
|
||||
Err(_) => {
|
||||
setup_git_repo(config.clone())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
setup_git_repo(config.clone())?;
|
||||
}
|
||||
}
|
||||
setup_registry(config.clone()).await?;
|
||||
|
||||
info!(
|
||||
"APP VERSION: {}",
|
||||
config
|
||||
.get("CONFIG_VERSION")
|
||||
.expect("config version not defined")
|
||||
);
|
||||
info!("RUNNING: {:?}", config.get("PUBLIC_PORT"));
|
||||
app::run(config.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use log::{error, info};
|
||||
use tokio::time::sleep;
|
||||
use tonic::transport::Channel;
|
||||
|
||||
|
|
@ -17,7 +18,11 @@ pub async fn register_service(client: &mut RegistryClient<Channel>, name: &str,
|
|||
token: std::env::var("REGISTRY_TOKEN").unwrap_or_default(),
|
||||
});
|
||||
|
||||
let _ = client.register(req).await;
|
||||
let resp = client.register(req).await;
|
||||
match resp {
|
||||
Ok(r) => info!("{:?}", r),
|
||||
Err(e) => error!("error register: {:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn heartbeat_loop(name: String, url: String) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue