fix branch matching
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
parent
616ec50435
commit
f2d5d50b4c
2 changed files with 105 additions and 74 deletions
|
@ -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_, _)| 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;
|
||||||
|
|
||||||
|
@ -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,16 +292,22 @@ 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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
let status = RwLock::new(status);
|
||||||
|
|
||||||
(ref_, Arc::new(RefState { config, status }))
|
(ref_, Arc::new(RefState { config, status }))
|
||||||
}).collect();
|
})
|
||||||
|
.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(),
|
||||||
|
@ -68,14 +72,16 @@ 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();
|
||||||
}
|
}
|
Loading…
Reference in a new issue