fix branch matching
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m10s
Publish Docker image / Build (push) Successful in 4m58s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m23s
Publish Docker image / Build (pull_request) Successful in 4m26s

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
ゆめ 2024-11-08 21:23:22 -06:00
parent 616ec50435
commit 8e9c175c06
No known key found for this signature in database
2 changed files with 105 additions and 74 deletions

View file

@ -1,6 +1,10 @@
use std::{collections::HashMap, fs::OpenOptions, path::PathBuf, process::Stdio, sync::Arc}; use std::{collections::HashMap, fs::OpenOptions, path::PathBuf, process::Stdio, sync::Arc};
use axum::{extract::{Query, State, Json}, http::{HeaderMap, StatusCode}, response::{IntoResponse, Response}}; use axum::{
extract::{Json, Query, State},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use libc::SIGTERM; use libc::SIGTERM;
use tokio::{process::Command, sync::RwLock}; use tokio::{process::Command, sync::RwLock};
@ -80,10 +84,7 @@ pub struct GetStatusQuery {
pub branch: Option<String>, pub branch: Option<String>,
} }
fn require_bearer(bcrypted: &str, headers: &HeaderMap) -> Result<(), ApiError> {
fn require_bearer(
bcrypted: &str,
headers: &HeaderMap) -> Result<(), ApiError> {
let bearer = headers let bearer = headers
.get("authorization") .get("authorization")
.ok_or_else(|| ApiError { .ok_or_else(|| ApiError {
@ -95,11 +96,13 @@ fn require_bearer(
code: 400, code: 400,
message: "Invalid Authorization header".to_string(), message: "Invalid Authorization header".to_string(),
})?; })?;
let bearer = bearer.trim_start_matches("Bearer ").trim_start_matches("bearer "); let bearer = bearer
.trim_start_matches("Bearer ")
.trim_start_matches("bearer ");
if !bcrypt::verify(bearer, bcrypted).map_err(|_| ApiError { if !bcrypt::verify(bearer, bcrypted).map_err(|_| ApiError {
code: 401, code: 401,
message: "Unauthorized".to_string(), message: "Unauthorized".to_string(),
})? { })? {
return Err(ApiError { return Err(ApiError {
code: 401, code: 401,
@ -110,7 +113,6 @@ fn require_bearer(
Ok(()) Ok(())
} }
impl App { impl App {
pub async fn post_deploy( pub async fn post_deploy(
State(state): State<AppState>, State(state): State<AppState>,
@ -119,10 +121,16 @@ impl App {
) -> Result<StatusCode, ApiError> { ) -> Result<StatusCode, ApiError> {
require_bearer(&state.bearer_secret, &headers)?; require_bearer(&state.bearer_secret, &headers)?;
let matched_ref = state.refs.get(&payload.ref_).ok_or_else(|| ApiError { let matched_ref = state
code: 404, .refs
message: "Ref not found".to_string(), .into_iter()
})?.clone(); .find(|(_, ref_state)| ref_state.config.ref_ == payload.ref_)
.ok_or_else(|| ApiError {
code: 404,
message: "Ref not found".to_string(),
})?
.1
.clone();
let mut status = matched_ref.status.write().await; let mut status = matched_ref.status.write().await;
@ -142,7 +150,7 @@ impl App {
}); });
} }
}; };
if !Command::new("git") if !Command::new("git")
.arg("fetch") .arg("fetch")
.arg("origin") .arg("origin")
@ -159,7 +167,9 @@ impl App {
.map_err(|_| ApiError { .map_err(|_| ApiError {
code: 500, code: 500,
message: "Failed to wait for command".to_string(), message: "Failed to wait for command".to_string(),
})?.success() { })?
.success()
{
status.status = Status::Error("Failed to fetch".to_string()); status.status = Status::Error("Failed to fetch".to_string());
return Ok(StatusCode::OK); return Ok(StatusCode::OK);
} }
@ -181,38 +191,50 @@ impl App {
.map_err(|_| ApiError { .map_err(|_| ApiError {
code: 500, code: 500,
message: "Failed to wait for command".to_string(), message: "Failed to wait for command".to_string(),
})?.success() { })?
.success()
{
status.status = Status::Error("Failed to reset".to_string()); status.status = Status::Error("Failed to reset".to_string());
return Ok(StatusCode::OK); return Ok(StatusCode::OK);
} }
let mut command = Command::new("docker") let mut command = Command::new("docker")
.arg("compose") .arg("compose")
.arg("up") .arg("up")
.arg("--detach") .arg("--detach")
.arg("--build") .arg("--build")
.args(matched_ref.config.compose_flags.as_ref().unwrap_or(&Vec::new())) .args(
matched_ref
.config
.compose_flags
.as_ref()
.unwrap_or(&Vec::new()),
)
.current_dir(matched_ref.config.working_dir.clone()) .current_dir(matched_ref.config.working_dir.clone())
.kill_on_drop(true) .kill_on_drop(true)
.envs(matched_ref.config.env.as_ref().unwrap_or(&HashMap::new()).iter()) .envs(
.stdout( matched_ref
match matched_ref.config.stdout.as_ref() { .config
Some(path) => { .env
let file = OpenOptions::new() .as_ref()
.create(true) .unwrap_or(&HashMap::new())
.append(true) .iter(),
.open(path)
.map_err(|_| ApiError {
code: 500,
message: "Failed to open stdout file".to_string(),
})?;
Stdio::from(file)
}
None => Stdio::null(),
}
) )
.stdout(match matched_ref.config.stdout.as_ref() {
Some(path) => {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.map_err(|_| ApiError {
code: 500,
message: "Failed to open stdout file".to_string(),
})?;
Stdio::from(file)
}
None => Stdio::null(),
})
.spawn() .spawn()
.map_err(|_| ApiError { .map_err(|_| ApiError {
code: 500, code: 500,
@ -222,12 +244,10 @@ impl App {
status.status = Status::Deploying; status.status = Status::Deploying;
status.triggered = Some(chrono::Utc::now().naive_utc()); status.triggered = Some(chrono::Utc::now().naive_utc());
status.child_pid = command.id().ok_or_else( || status.child_pid = command.id().ok_or_else(|| ApiError {
ApiError { code: 500,
code: 500, message: "Failed to get child PID".to_string(),
message: "Failed to get child PID".to_string(), })?;
}
)?;
let matched_ref = matched_ref.clone(); let matched_ref = matched_ref.clone();
tokio::spawn(async move { tokio::spawn(async move {
@ -250,7 +270,6 @@ impl App {
} }
}); });
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@ -273,19 +292,25 @@ impl App {
let ref_ = match (&query.ref_, &query.branch) { let ref_ = match (&query.ref_, &query.branch) {
(Some(ref_), None) => ref_.clone(), (Some(ref_), None) => ref_.clone(),
(None, Some(branch)) => format!("refs/heads/{}", branch), (None, Some(branch)) => format!("refs/heads/{}", branch),
_ => return Err(ApiError { _ => {
code: 400, return Err(ApiError {
message: "Either ref or branch must be specified".to_string(), code: 400,
}), message: "Either ref or branch must be specified".to_string(),
})
}
}; };
let matched_ref = state.refs.get(&ref_).ok_or_else(|| ApiError { let matched_ref = state
code: 404, .refs
message: "Ref not found".to_string(), .get(&ref_)
})?.clone(); .ok_or_else(|| ApiError {
code: 404,
message: "Ref not found".to_string(),
})?
.clone();
let status = matched_ref.status.read().await; let status = matched_ref.status.read().await;
Ok(Json(status.clone())) Ok(Json(status.clone()))
} }
} }

View file

@ -1,7 +1,7 @@
use std::{collections::HashMap, sync::Arc};
use axum::Router; use axum::Router;
use tokio::{net::TcpListener, sync::RwLock};
use clap::Parser; use clap::Parser;
use std::{collections::HashMap, sync::Arc};
use tokio::{net::TcpListener, sync::RwLock};
use misskey_auto_deploy::{App, AppState, RefConfig, RefState, RefStatus, Status}; use misskey_auto_deploy::{App, AppState, RefConfig, RefState, RefStatus, Status};
@ -13,7 +13,6 @@ struct Args {
config: String, config: String,
} }
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct Config { pub struct Config {
pub service: ServiceConfig, pub service: ServiceConfig,
@ -33,18 +32,22 @@ pub struct ServiceConfig {
impl From<Config> for AppState { impl From<Config> for AppState {
fn from(val: Config) -> Self { fn from(val: Config) -> Self {
let refs = val.refs.into_iter().map(|(ref_, config)| { let refs = val
let status = RefStatus{ .refs
status: Status::Never, .into_iter()
last_payload: None, .map(|(ref_, config)| {
triggered: None, let status = RefStatus {
succeeded: None, status: Status::Never,
child_pid: 0, last_payload: None,
}; triggered: None,
let status = RwLock::new(status); succeeded: None,
child_pid: 0,
(ref_, Arc::new(RefState { config, status })) };
}).collect(); let status = RwLock::new(status);
(ref_, Arc::new(RefState { config, status }))
})
.collect();
AppState { AppState {
bearer_secret: val.auth.secret, bearer_secret: val.auth.secret,
refs, refs,
@ -52,12 +55,13 @@ impl From<Config> for AppState {
} }
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let args = Args::parse(); let args = Args::parse();
let config:Config = toml::from_str(&std::fs::read_to_string(args.config).expect("Failed to read config file")).expect("Failed to parse config file"); let config: Config =
toml::from_str(&std::fs::read_to_string(args.config).expect("Failed to read config file"))
.expect("Failed to parse config file");
let listen = match (&args.listen, &config.service.listen) { let listen = match (&args.listen, &config.service.listen) {
(Some(listen), _) => listen.clone(), (Some(listen), _) => listen.clone(),
@ -67,15 +71,17 @@ async fn main() {
let app_state: AppState = config.into(); let app_state: AppState = config.into();
let app = Router::new() let app = Router::new()
.route("/deploy", .route(
axum::routing::post(App::post_deploy) "/deploy",
.get(App::get_status) axum::routing::post(App::post_deploy).get(App::get_status),
) )
.route("/summary", axum::routing::get(App::summary)) .route("/summary", axum::routing::get(App::summary))
.with_state(app_state); .with_state(app_state);
let listener = TcpListener::bind(listen).await.expect("Failed to bind listener"); let listener = TcpListener::bind(listen)
.await
.expect("Failed to bind listener");
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }