From 16aa58482fe524de02a38602321758ff456ffd10 Mon Sep 17 00:00:00 2001 From: beckerinj Date: Mon, 5 Dec 2022 02:58:04 -0500 Subject: [PATCH] update build --- Cargo.lock | 1 + core/Cargo.toml | 3 +- core/src/api/build.rs | 77 ++++++++++++++++++++++++++++++++------ core/src/api/deployment.rs | 14 ++++--- core/src/api/mod.rs | 26 ++----------- core/src/api/server.rs | 14 ++++--- core/src/helpers.rs | 67 +++++++++++++++++++++++++++++++++ lib/types/src/lib.rs | 32 +++++++++++++--- 8 files changed, 184 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b85805d3..bc3628876 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,7 @@ dependencies = [ "axum-extra", "bcrypt", "db_client", + "diff-struct", "dotenv", "envy", "futures-util", diff --git a/core/Cargo.toml b/core/Cargo.toml index 7de80738b..6399089b7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -30,4 +30,5 @@ jwt = "0.16" hmac = "0.12" sha2 = "0.10" async_timing_util = "0.1.11" -futures-util = "0.3" \ No newline at end of file +futures-util = "0.3" +diff-struct = "0.5" \ No newline at end of file diff --git a/core/src/api/build.rs b/core/src/api/build.rs index 99f2080ab..994d059a6 100644 --- a/core/src/api/build.rs +++ b/core/src/api/build.rs @@ -2,17 +2,22 @@ use anyhow::{anyhow, Context}; use async_timing_util::unix_timestamp_ms; use axum::{ extract::Path, - routing::{delete, post}, + routing::{delete, patch, post}, Extension, Json, Router, }; use db::DbExtension; +use diff::Diff; use helpers::handle_anyhow_error; use mungos::Deserialize; use types::{traits::Permissioned, Build, Log, Operation, PermissionLevel, Update, UpdateTarget}; -use crate::{auth::RequestUserExtension, ws::update}; +use crate::{ + auth::RequestUserExtension, + helpers::{add_update, all_logs_success, any_option_diff_is_some, option_diff_is_some}, + ws::update, +}; -use super::{add_update, PeripheryExtension}; +use super::PeripheryExtension; #[derive(Deserialize)] struct BuildId { @@ -43,6 +48,14 @@ pub fn router() -> Router { .map_err(handle_anyhow_error) }), ) + .route( + "/update", + patch(|db, user, update_ws, periphery, build| async { + update(db, user, update_ws, periphery, build) + .await + .map_err(handle_anyhow_error) + }), + ) } impl Into for CreateBuildBody { @@ -80,9 +93,11 @@ async fn create( start_ts, end_ts: Some(unix_timestamp_ms() as i64), operator: user.id.clone(), + success: true, ..Default::default() }; - add_update(update, &db, &update_ws).await + add_update(update, &db, &update_ws).await?; + Ok(()) } async fn delete_one( @@ -94,7 +109,7 @@ async fn delete_one( ) -> anyhow::Result<()> { let build = db.get_build(&id).await?; let permissions = build.get_user_permissions(&user.id); - if permissions != PermissionLevel::Write { + if !user.is_admin && permissions != PermissionLevel::Write { return Err(anyhow!( "user does not have permissions to delete build {} ({id})", build.name @@ -113,16 +128,18 @@ async fn delete_one( start_ts, end_ts: Some(unix_timestamp_ms() as i64), operator: user.id.clone(), - log: vec![ + logs: vec![ delete_repo_log, Log::simple(format!( "deleted build {} on server {}", build.name, server.name )), ], + success: true, ..Default::default() }; - add_update(update, &db, &update_ws).await + add_update(update, &db, &update_ws).await?; + Ok(()) } async fn update( @@ -130,7 +147,45 @@ async fn update( Extension(user): RequestUserExtension, Extension(update_ws): update::UpdateWsSenderExtension, Extension(periphery): PeripheryExtension, - Path(build): Path, -) { - -} \ No newline at end of file + Json(mut new_build): Json, +) -> anyhow::Result<()> { + let current_build = db.get_build(&new_build.id).await?; + let permissions = current_build.get_user_permissions(&user.id); + if !user.is_admin && permissions != PermissionLevel::Write { + return Err(anyhow!( + "user does not have permissions to update build {} ({})", + current_build.name, + current_build.id + )); + } + let start_ts = unix_timestamp_ms() as i64; + let server = db.get_server(¤t_build.server_id).await?; + + new_build.permissions = current_build.permissions.clone(); + let diff = current_build.diff(&new_build); + let mut logs = vec![Log::simple(format!("{diff:#?}"))]; + + if any_option_diff_is_some(&[&diff.repo, &diff.branch, &diff.github_account]) + || option_diff_is_some(&diff.on_clone) + { + match periphery.clone_repo(&server, &new_build).await { + Ok(clone_logs) => { + logs.extend(clone_logs); + } + Err(e) => logs.push(Log::error("cloning repo", format!("{e:#?}"))), + } + } + + let update = Update { + operation: Operation::UpdateBuild, + target: UpdateTarget::Build(new_build.id), + start_ts, + end_ts: Some(unix_timestamp_ms() as i64), + success: all_logs_success(&logs), + logs, + operator: user.id.clone(), + ..Default::default() + }; + add_update(update, &db, &update_ws).await?; + Ok(()) +} diff --git a/core/src/api/deployment.rs b/core/src/api/deployment.rs index 981854fbd..47b379e99 100644 --- a/core/src/api/deployment.rs +++ b/core/src/api/deployment.rs @@ -12,9 +12,9 @@ use types::{ traits::Permissioned, Deployment, Log, Operation, PermissionLevel, Update, UpdateTarget, }; -use crate::{auth::RequestUserExtension, ws::update}; +use crate::{auth::RequestUserExtension, helpers::add_update, ws::update}; -use super::{add_update, PeripheryExtension}; +use super::PeripheryExtension; #[derive(Deserialize)] pub struct DeploymentId { @@ -86,9 +86,11 @@ async fn create( start_ts, end_ts: Some(unix_timestamp_ms() as i64), operator: user.id.clone(), + success: true, ..Default::default() }; - add_update(update, &db, &update_ws).await + add_update(update, &db, &update_ws).await?; + Ok(()) } async fn delete_one( @@ -118,14 +120,16 @@ async fn delete_one( start_ts, end_ts: Some(unix_timestamp_ms() as i64), operator: user.id.clone(), - log: vec![ + logs: vec![ log, Log::simple(format!( "deleted deployment {} on server {}", deployment.name, server.name )), ], + success: true, ..Default::default() }; - add_update(update, &db, &update_ws).await + add_update(update, &db, &update_ws).await?; + Ok(()) } diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index d1d4ba038..f6fa5e3c9 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -1,16 +1,13 @@ use std::sync::Arc; -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use axum::{middleware, routing::get, Extension, Json, Router}; -use db::{DbClient, DbExtension}; +use db::DbExtension; use helpers::handle_anyhow_error; use periphery::PeripheryClient; -use types::{Update, User}; +use types::User; -use crate::{ - auth::{auth_request, RequestUserExtension}, - ws::update, -}; +use crate::auth::{auth_request, RequestUserExtension}; mod build; mod deployment; @@ -50,18 +47,3 @@ async fn get_user( } Ok(Json(user)) } - -async fn add_update( - mut update: Update, - db: &DbClient, - update_ws: &update::UpdateWsSender, -) -> anyhow::Result<()> { - update.id = db - .updates - .create_one(update.clone()) - .await - .context("failed to insert update into db. the create build process was completed.")? - .to_string(); - let _ = update_ws.lock().await.send(update); - Ok(()) -} diff --git a/core/src/api/server.rs b/core/src/api/server.rs index 3b112efec..c3e845bd0 100644 --- a/core/src/api/server.rs +++ b/core/src/api/server.rs @@ -13,9 +13,9 @@ use types::{ UpdateTarget, }; -use crate::{auth::RequestUserExtension, ws::update}; +use crate::{auth::RequestUserExtension, helpers::add_update, ws::update}; -use super::{add_update, PeripheryExtension}; +use super::PeripheryExtension; #[derive(Deserialize)] struct ServerId { @@ -120,9 +120,11 @@ async fn create( start_ts, end_ts: Some(unix_timestamp_ms() as i64), operator: user.id.clone(), + success: true, ..Default::default() }; - add_update(update, &db, &update_ws).await + add_update(update, &db, &update_ws).await?; + Ok(()) } async fn delete_one( @@ -147,10 +149,12 @@ async fn delete_one( start_ts, end_ts: Some(unix_timestamp_ms() as i64), operator: user.id.clone(), - log: vec![Log::simple(format!("deleted server {}", server.name))], + logs: vec![Log::simple(format!("deleted server {}", server.name))], + success: true, ..Default::default() }; - add_update(update, &db, &update_ws).await + add_update(update, &db, &update_ws).await?; + Ok(()) } async fn stats( diff --git a/core/src/helpers.rs b/core/src/helpers.rs index 9fadd9b54..8a4216c0d 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -1,6 +1,73 @@ +use anyhow::Context; +use db::DbClient; +use diff::{Diff, OptionDiff}; +use types::{Log, Update}; + +use crate::ws::update; + #[macro_export] macro_rules! response { ($x:expr) => { Ok::<_, (axum::http::StatusCode, String)>($x) }; } + +pub fn option_diff_is_some(diff: &OptionDiff) -> bool +where + ::Repr: PartialEq, +{ + diff != &OptionDiff::NoChange && diff != &OptionDiff::None +} + +pub fn any_option_diff_is_some(diffs: &[&OptionDiff]) -> bool +where + ::Repr: PartialEq, +{ + for diff in diffs { + if diff != &&OptionDiff::NoChange && diff != &&OptionDiff::None { + return true; + } + } + return false; +} + +pub fn all_logs_success(logs: &Vec) -> bool { + for log in logs { + if !log.success { + return false; + } + } + true +} + +pub async fn add_update( + mut update: Update, + db: &DbClient, + update_ws: &update::UpdateWsSender, +) -> anyhow::Result { + update.id = db + .updates + .create_one(update.clone()) + .await + .context("failed to insert update into db")? + .to_string(); + let id = update.id.clone(); + let _ = update_ws.lock().await.send(update); + Ok(id) +} + +pub async fn update_update( + mut update: Update, + db: &DbClient, + update_ws: &update::UpdateWsSender, +) -> anyhow::Result<()> { + let mut update_id = String::new(); + std::mem::swap(&mut update.id, &mut update_id); + db.updates + .update_one(&update_id, mungos::Update::Regular(update.clone())) + .await + .context("failed to update the update on db. the update build process was deleted")?; + std::mem::swap(&mut update.id, &mut update_id); + let _ = update_ws.lock().await.send(update); + Ok(()) +} diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index c609476d9..9c8764be7 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -28,6 +28,7 @@ pub type SecretsMap = HashMap; // these are used for injection i pub type PermissionsMap = HashMap; #[derive(Serialize, Deserialize, Debug, Clone, Default, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct User { #[serde( default, @@ -54,6 +55,7 @@ pub struct User { } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct ApiSecret { pub name: String, pub hash: String, @@ -62,6 +64,7 @@ pub struct ApiSecret { } #[derive(Serialize, Deserialize, Debug, Clone, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct Server { #[serde( default, @@ -121,6 +124,7 @@ fn default_disk_alert() -> f64 { } #[derive(Serialize, Deserialize, Debug, Clone, Default, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct Deployment { #[serde( default, @@ -146,6 +150,7 @@ pub struct Deployment { } #[derive(Serialize, Deserialize, Debug, Clone, Default, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct Build { #[serde( default, @@ -192,15 +197,16 @@ pub struct Update { pub id: String, pub target: UpdateTarget, pub operation: Operation, - pub log: Vec, + pub logs: Vec, pub start_ts: i64, pub end_ts: Option, pub status: UpdateStatus, - pub is_error: bool, + pub success: bool, pub operator: String, } #[derive(Serialize, Deserialize, Debug, Clone, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct Procedure { #[serde( default, @@ -215,12 +221,14 @@ pub struct Procedure { } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Diff)] +#[diff(attr(#[derive(Debug, Serialize)]))] pub struct DockerBuildArgs { pub build_path: String, pub dockerfile_path: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default, Diff)] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub struct DockerRunArgs { pub image: String, pub ports: Vec, @@ -282,9 +290,10 @@ impl Log { } } - pub fn error(msg: String) -> Log { + pub fn error(stage: &str, msg: String) -> Log { let ts = unix_timestamp_ms() as i64; Log { + stage: stage.to_string(), stderr: msg, start_ts: ts, end_ts: ts, @@ -294,13 +303,15 @@ impl Log { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Diff)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Diff)] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub struct Command { pub path: String, pub command: String, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Diff)] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub struct Version { pub major: u64, pub minor: u64, @@ -313,12 +324,14 @@ impl ToString for Version { } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Diff)] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub struct Conversion { pub local: String, pub container: String, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Diff)] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub struct EnvironmentVar { pub variable: String, pub value: String, @@ -477,9 +490,12 @@ impl Default for UpdateStatus { } } -#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy, Diff)] +#[derive( + Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy, Diff, +)] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub enum Operation { // do nothing None, @@ -521,6 +537,7 @@ impl Default for Operation { )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub enum PermissionLevel { None, Read, @@ -561,7 +578,10 @@ pub enum DockerContainerState { Dead, } -#[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy, Diff)] +#[derive( + Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy, Diff, +)] +#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub enum RestartMode { #[serde(rename = "no")] NoRestart,