diff --git a/core/src/helpers.rs b/core/src/helpers.rs index 5cc707ad2..722e91aea 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -5,11 +5,12 @@ use monitor_types::{ busy::Busy, entities::{ build::Build, + builder::Builder, deployment::{Deployment, DockerContainerState}, server::{Server, ServerStatus}, update::Update, user::User, - PermissionLevel, builder::Builder, + PermissionLevel, }, permissioned::Permissioned, }; @@ -114,7 +115,10 @@ impl State { )) } - pub async fn get_deployment_state(&self, deployment: &Deployment) -> anyhow::Result { + pub async fn get_deployment_state( + &self, + deployment: &Deployment, + ) -> anyhow::Result { if deployment.config.server_id.is_empty() { return Ok(DockerContainerState::NotDeployed); } @@ -133,7 +137,7 @@ impl State { let state = match container { Some(container) => container.state, - None => DockerContainerState::NotDeployed + None => DockerContainerState::NotDeployed, }; Ok(state) @@ -358,4 +362,4 @@ pub fn empty_or_only_spaces(word: &str) -> bool { } } true -} \ No newline at end of file +} diff --git a/core/src/monitoring.rs b/core/src/monitoring.rs index 0dc493b93..3ec21b6e2 100644 --- a/core/src/monitoring.rs +++ b/core/src/monitoring.rs @@ -5,7 +5,7 @@ use monitor_types::entities::{ deployment::{BasicContainerInfo, Deployment, DockerContainerState}, server::{ stats::{ - AllSystemStats, BasicSystemStats, CpuUsage, ServerHealth, SingleDiskUsage, StatsState, + AllSystemStats, BasicSystemStats, ServerHealth, SingleDiskUsage, StatsState, SystemComponent, }, Server, ServerConfig, ServerStatus, diff --git a/core/src/requests/api/build.rs b/core/src/requests/api/build.rs index 8bed18785..e9cb97860 100644 --- a/core/src/requests/api/build.rs +++ b/core/src/requests/api/build.rs @@ -14,9 +14,7 @@ use monitor_types::{ }, monitor_timestamp, permissioned::Permissioned, - requests::api::{ - CreateBuild, DeleteBuild, Deploy, GetBuild, ListBuilds, RunBuild, UpdateBuild, - }, + requests::api::*, }; use mungos::mongodb::bson::{doc, to_bson}; use periphery_client::{ @@ -112,7 +110,84 @@ impl Resolve for State { let build_id = self .db .builds - .create_one(&build) + .create_one(build) + .await + .context("failed to add build to db")?; + let build = self.get_build(&build_id).await?; + let update = Update { + target: UpdateTarget::Build(build_id), + operation: Operation::CreateBuild, + start_ts, + end_ts: Some(monitor_timestamp()), + operator: user.id.clone(), + success: true, + logs: vec![ + Log::simple( + "create build", + format!("created build\nid: {}\nname: {}", build.id, build.name), + ), + Log::simple("config", format!("{:#?}", build.config)), + ], + ..Default::default() + }; + + self.add_update(update).await?; + + Ok(build) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + CopyBuild { name, id }: CopyBuild, + user: RequestUser, + ) -> anyhow::Result { + let Build { + config, + description, + .. + } = self + .get_build_check_permissions(&id, &user, PermissionLevel::Update) + .await?; + match &config.builder { + BuildBuilderConfig::Server { server_id } => { + self.get_server_check_permissions( + server_id, + &user, + PermissionLevel::Update, + ) + .await + .context("cannot create build on this server. user must have update permissions on the server.")?; + } + BuildBuilderConfig::Builder { builder_id } => { + self.get_builder_check_permissions( + builder_id, + &user, + PermissionLevel::Read, + ) + .await + .context("cannot create build using this builder. user must have at least read permissions on the builder.")?; + } + } + let start_ts = monitor_timestamp(); + let build = Build { + id: Default::default(), + name, + created_at: start_ts, + updated_at: start_ts, + last_built_at: 0, + permissions: [(user.id.clone(), PermissionLevel::Update)] + .into_iter() + .collect(), + description, + config, + }; + let build_id = self + .db + .builds + .create_one(build) .await .context("failed to add build to db")?; let build = self.get_build(&build_id).await?; diff --git a/core/src/requests/api/deployment.rs b/core/src/requests/api/deployment.rs index 6bbeec0c2..26231172c 100644 --- a/core/src/requests/api/deployment.rs +++ b/core/src/requests/api/deployment.rs @@ -10,10 +10,7 @@ use monitor_types::{ }, get_image_name, monitor_timestamp, permissioned::Permissioned, - requests::api::{ - CreateDeployment, DeleteDeployment, Deploy, GetDeployment, ListDeployments, - RemoveContainer, RenameDeployment, StartContainer, StopContainer, UpdateDeployment, - }, + requests::api::*, to_monitor_name, }; use mungos::mongodb::bson::{doc, to_bson}; @@ -71,14 +68,18 @@ impl Resolve for State { user: RequestUser, ) -> anyhow::Result { if let Some(server_id) = &config.server_id { - self.get_server_check_permissions(server_id, &user, PermissionLevel::Update) - .await - .context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?; + if !server_id.is_empty() { + self.get_server_check_permissions(server_id, &user, PermissionLevel::Update) + .await + .context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?; + } } if let Some(DeploymentImage::Build { build_id, .. }) = &config.image { - self.get_build_check_permissions(build_id, &user, PermissionLevel::Read) - .await - .context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?; + if !build_id.is_empty() { + self.get_build_check_permissions(build_id, &user, PermissionLevel::Read) + .await + .context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?; + } } let start_ts = monitor_timestamp(); let deployment = Deployment { @@ -125,6 +126,77 @@ impl Resolve for State { } } +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + CopyDeployment { name, id }: CopyDeployment, + user: RequestUser, + ) -> anyhow::Result { + let Deployment { + config, + description, + .. + } = self + .get_deployment_check_permissions(&id, &user, PermissionLevel::Update) + .await?; + if !config.server_id.is_empty() { + self.get_server_check_permissions(&config.server_id, &user, PermissionLevel::Update) + .await + .context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?; + } + if let DeploymentImage::Build { build_id, .. } = &config.image { + if !build_id.is_empty() { + self.get_build_check_permissions(build_id, &user, PermissionLevel::Read) + .await + .context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?; + } + } + let start_ts = monitor_timestamp(); + let deployment = Deployment { + id: Default::default(), + name, + created_at: start_ts, + updated_at: start_ts, + permissions: [(user.id.clone(), PermissionLevel::Update)] + .into_iter() + .collect(), + description, + config, + }; + let deployment_id = self + .db + .deployments + .create_one(&deployment) + .await + .context("failed to add deployment to db")?; + let deployment = self.get_deployment(&deployment_id).await?; + let update = Update { + target: UpdateTarget::Deployment(deployment_id), + operation: Operation::CreateDeployment, + start_ts, + end_ts: Some(monitor_timestamp()), + operator: user.id.clone(), + success: true, + logs: vec![ + Log::simple( + "create deployment", + format!( + "created deployment\nid: {}\nname: {}", + deployment.id, deployment.name + ), + ), + Log::simple("config", format!("{:#?}", deployment.config)), + ], + ..Default::default() + }; + + self.add_update(update).await?; + + Ok(deployment) + } +} + #[async_trait] impl Resolve for State { async fn resolve( diff --git a/core/src/requests/api/mod.rs b/core/src/requests/api/mod.rs index 090ff6a87..35af96ec2 100644 --- a/core/src/requests/api/mod.rs +++ b/core/src/requests/api/mod.rs @@ -15,10 +15,10 @@ use crate::{ state::{State, StateExtension}, }; +mod build; mod deployment; mod secret; mod server; -mod build; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Resolver)] @@ -70,7 +70,7 @@ pub enum ApiRequest { // ==== DEPLOYMENT ==== // GetDeployment(GetDeployment), - ListDeployments(ListDeployments), + ListDeployments(ListDeployments), // CRUD CreateDeployment(CreateDeployment), DeleteDeployment(DeleteDeployment), @@ -86,7 +86,7 @@ pub enum ApiRequest { // ==== BUILD ==== // GetBuild(GetBuild), - ListBuilds(ListBuilds), + ListBuilds(ListBuilds), // CRUD CreateBuild(CreateBuild), DeleteBuild(DeleteBuild), @@ -105,18 +105,26 @@ pub fn router() -> Router { Json(request): Json| async move { let timer = Instant::now(); let req_id = Uuid::new_v4(); - info!("/auth request {req_id} | {request:?}"); - let res = state - .resolve_request(request, user) - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}"))); + info!("/api request {req_id} | {request:?}"); + let res = tokio::spawn(async move { + state + .resolve_request(request, user) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}"))) + }) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}"))); if let Err(e) = &res { - info!("/auth request {req_id} ERROR: {e:?}"); + info!("/api request {req_id} SPAWN ERROR: {e:?}"); + } + let res = res?; + if let Err(e) = &res { + info!("/api request {req_id} ERROR: {e:?}"); } let res = res?; let elapsed = timer.elapsed(); - info!("/auth request {req_id} | resolve time: {elapsed:?}"); - debug!("/auth request {req_id} RESPONSE: {res}"); + info!("/api request {req_id} | resolve time: {elapsed:?}"); + debug!("/api request {req_id} RESPONSE: {res}"); Result::<_, (StatusCode, String)>::Ok((TypedHeader(ContentType::json()), res)) }, ), diff --git a/core/src/requests/api/secret.rs b/core/src/requests/api/secret.rs index f504cc49c..518ab9c4c 100644 --- a/core/src/requests/api/secret.rs +++ b/core/src/requests/api/secret.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Context}; use async_trait::async_trait; use monitor_types::{ - monitor_timestamp, entities::user::ApiSecret, + monitor_timestamp, requests::api::{CreateLoginSecret, CreateLoginSecretResponse, DeleteLoginSecret}, }; use mungos::{ diff --git a/core/src/requests/api/server.rs b/core/src/requests/api/server.rs index f2e368583..751205d78 100644 --- a/core/src/requests/api/server.rs +++ b/core/src/requests/api/server.rs @@ -12,13 +12,7 @@ use monitor_types::{ }, monitor_timestamp, permissioned::Permissioned, - requests::api::{ - CreateServer, DeleteServer, GetAllSystemStats, GetBasicSystemStats, GetCpuUsage, - GetDiskUsage, GetDockerContainers, GetDockerImages, GetDockerNetworks, GetNetworkUsage, - GetPeripheryVersion, GetPeripheryVersionResponse, GetServer, GetSystemComponents, - GetSystemInformation, GetSystemProcesses, ListServers, PruneDockerContainers, - PruneDockerImages, PruneDockerNetworks, RenameServer, UpdateServer, - }, + requests::api::*, }; use mungos::mongodb::bson::{doc, to_bson}; use periphery_client::requests; diff --git a/lib/types/src/busy.rs b/lib/types/src/busy.rs index aa0196b2a..4d4abc7c3 100644 --- a/lib/types/src/busy.rs +++ b/lib/types/src/busy.rs @@ -1,4 +1,6 @@ -use crate::entities::{server::ServerActionState, deployment::DeploymentActionState, build::BuildActionState}; +use crate::entities::{ + build::BuildActionState, deployment::DeploymentActionState, server::ServerActionState, +}; pub trait Busy { fn busy(&self) -> bool; @@ -27,4 +29,4 @@ impl Busy for BuildActionState { fn busy(&self) -> bool { self.building || self.updating } -} \ No newline at end of file +} diff --git a/lib/types/src/entities/server/mod.rs b/lib/types/src/entities/server/mod.rs index 86c082092..0faaff65b 100644 --- a/lib/types/src/entities/server/mod.rs +++ b/lib/types/src/entities/server/mod.rs @@ -5,7 +5,7 @@ use partial_derive2::Partial; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::{I64, i64_is_zero}; +use crate::{i64_is_zero, I64}; use super::PermissionsMap; diff --git a/lib/types/src/entities/user.rs b/lib/types/src/entities/user.rs index 8a5d46333..5dc0c259c 100644 --- a/lib/types/src/entities/user.rs +++ b/lib/types/src/entities/user.rs @@ -3,7 +3,7 @@ use mungos::MungosIndexed; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::{I64, i64_is_zero}; +use crate::{i64_is_zero, I64}; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Default, MungosIndexed)] diff --git a/lib/types/src/permissioned.rs b/lib/types/src/permissioned.rs index ac31b0451..68d1dc2f5 100644 --- a/lib/types/src/permissioned.rs +++ b/lib/types/src/permissioned.rs @@ -1,4 +1,7 @@ -use crate::entities::{PermissionsMap, PermissionLevel, deployment::Deployment, build::Build, server::Server, builder::Builder}; +use crate::entities::{ + build::Build, builder::Builder, deployment::Deployment, server::Server, PermissionLevel, + PermissionsMap, +}; pub trait Permissioned { fn permissions_map(&self) -> &PermissionsMap; @@ -30,4 +33,4 @@ impl Permissioned for Builder { fn permissions_map(&self) -> &PermissionsMap { &self.permissions } -} \ No newline at end of file +} diff --git a/lib/types/src/requests/api/build.rs b/lib/types/src/requests/api/build.rs index 13e123874..b17db02f1 100644 --- a/lib/types/src/requests/api/build.rs +++ b/lib/types/src/requests/api/build.rs @@ -38,6 +38,16 @@ pub struct CreateBuild { // +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Build)] +pub struct CopyBuild { + pub name: String, + pub id: String, +} + +// + #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Request)] #[response(Build)] diff --git a/lib/types/src/requests/api/builder.rs b/lib/types/src/requests/api/builder.rs index ae5ba1cf3..f164c091d 100644 --- a/lib/types/src/requests/api/builder.rs +++ b/lib/types/src/requests/api/builder.rs @@ -8,6 +8,4 @@ // MongoDocument, // }; - - -// derive_crud_requests!(Builder); \ No newline at end of file +// derive_crud_requests!(Builder); diff --git a/lib/types/src/requests/api/deployment.rs b/lib/types/src/requests/api/deployment.rs index 928fb55b7..3a09def80 100644 --- a/lib/types/src/requests/api/deployment.rs +++ b/lib/types/src/requests/api/deployment.rs @@ -40,6 +40,16 @@ pub struct CreateDeployment { // +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Deployment)] +pub struct CopyDeployment { + pub name: String, + pub id: String, +} + +// + #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Request)] #[response(Deployment)] diff --git a/periphery/src/main.rs b/periphery/src/main.rs index 84b02d63f..da0642f7b 100644 --- a/periphery/src/main.rs +++ b/periphery/src/main.rs @@ -36,10 +36,18 @@ async fn app() -> anyhow::Result<()> { let timer = Instant::now(); let req_id = Uuid::new_v4(); info!("request {req_id} | {request:?}"); - let res = state - .resolve_request(request, ()) - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}"))); + let res = tokio::spawn(async move { + state + .resolve_request(request, ()) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}"))) + }) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}"))); + if let Err(e) = &res { + debug!("request {req_id} SPAWN ERROR: {e:?}"); + } + let res = res?; if let Err(e) = &res { debug!("request {req_id} ERROR: {e:?}"); } diff --git a/periphery/src/requests/build.rs b/periphery/src/requests/build.rs index 4f30df93b..c007f4f83 100644 --- a/periphery/src/requests/build.rs +++ b/periphery/src/requests/build.rs @@ -1,5 +1,8 @@ use async_trait::async_trait; -use monitor_types::{entities::{server::docker_image::ImageSummary, update::Log}, optional_string}; +use monitor_types::{ + entities::{server::docker_image::ImageSummary, update::Log}, + optional_string, +}; use resolver_api::{derive::Request, Resolve}; use serde::{Deserialize, Serialize}; diff --git a/periphery/src/requests/git.rs b/periphery/src/requests/git.rs index 50c05a3f0..2cc257589 100644 --- a/periphery/src/requests/git.rs +++ b/periphery/src/requests/git.rs @@ -1,4 +1,7 @@ -use monitor_types::{entities::{update::Log, CloneArgs, SystemCommand}, to_monitor_name}; +use monitor_types::{ + entities::{update::Log, CloneArgs, SystemCommand}, + to_monitor_name, +}; use resolver_api::{derive::Request, Resolve}; use serde::{Deserialize, Serialize};