From 99c2d6195df6ab06468652035d306673540af697 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Tue, 4 Jul 2023 00:02:27 +0000 Subject: [PATCH] crud for builder --- Cargo.lock | 4 +- Cargo.toml | 2 +- core/src/auth/secret.rs | 7 +- core/src/requests/api/build.rs | 4 +- core/src/requests/api/builder.rs | 254 ++++++++++++++++++++++++++ core/src/requests/api/deployment.rs | 4 +- core/src/requests/api/mod.rs | 17 +- core/src/requests/api/permissions.rs | 17 +- core/src/requests/api/repo.rs | 2 +- core/src/requests/api/secret.rs | 6 +- core/src/requests/api/server.rs | 81 ++++---- lib/types/src/entities/builder.rs | 76 +++++++- lib/types/src/requests/api/builder.rs | 72 +++++++- 13 files changed, 468 insertions(+), 78 deletions(-) create mode 100644 core/src/requests/api/builder.rs diff --git a/Cargo.lock b/Cargo.lock index eba2945a3..cb81d51bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1913,9 +1913,9 @@ dependencies = [ [[package]] name = "mungos" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e0e07595834795a2884abf009363c3884f3e5ca0ed63748e586aa62efcd26b" +checksum = "606e6fe66f9507be4814a6ee50508650c4563ecd21904713cf5eda650cf4dd42" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 602d911c4..201a30a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,6 @@ partial_derive2 = "0.1.4" make_option = "0.1.3" resolver_api = "0.1.5" parse_csl = "0.1.0" -mungos = "0.4.4" +mungos = "0.4.5" svi = "0.1.4" diff --git a/core/src/auth/secret.rs b/core/src/auth/secret.rs index e8b5e2f3f..d2d64987b 100644 --- a/core/src/auth/secret.rs +++ b/core/src/auth/secret.rs @@ -2,10 +2,7 @@ use anyhow::{anyhow, Context}; use async_timing_util::unix_timestamp_ms; use axum::async_trait; use monitor_types::requests::auth::{LoginWithSecret, LoginWithSecretResponse}; -use mungos::{ - mongodb::bson::{doc, Document}, - Update, -}; +use mungos::{mongodb::bson::doc, Update}; use resolver_api::Resolve; use crate::state::State; @@ -30,7 +27,7 @@ impl Resolve for State { if expires < ts { self.db .users - .update_one::( + .update_one( &user.id, Update::Custom(doc! { "$pull": { "secrets": { "name": s.name } } }), ) diff --git a/core/src/requests/api/build.rs b/core/src/requests/api/build.rs index d22213b51..2f07b02c1 100644 --- a/core/src/requests/api/build.rs +++ b/core/src/requests/api/build.rs @@ -317,7 +317,7 @@ impl Resolve for State { .builds .update_one( &id, - mungos::Update::<()>::Set(doc! { "config": to_bson(&config)? }), + mungos::Update::Set(doc! { "config": to_bson(&config)? }), ) .await .context("failed to update build on database")?; @@ -447,7 +447,7 @@ impl Resolve for State { let _ = self .db .builds - .update_one::( + .update_one( &build.id, mungos::Update::Set(doc! { "version": to_bson(&build.config.version) diff --git a/core/src/requests/api/builder.rs b/core/src/requests/api/builder.rs new file mode 100644 index 000000000..768e5a8a0 --- /dev/null +++ b/core/src/requests/api/builder.rs @@ -0,0 +1,254 @@ +use anyhow::Context; +use async_trait::async_trait; +use monitor_types::{ + entities::{ + builder::Builder, + update::{Log, ResourceTarget, Update}, + Operation, PermissionLevel, + }, + monitor_timestamp, + permissioned::Permissioned, + requests::api::*, +}; +use mungos::mongodb::bson::{doc, to_bson}; +use resolver_api::Resolve; + +use crate::{auth::RequestUser, state::State}; + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + GetBuilder { id }: GetBuilder, + user: RequestUser, + ) -> anyhow::Result { + self.get_builder_check_permissions(&id, &user, PermissionLevel::Read) + .await + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + ListBuilders { query }: ListBuilders, + user: RequestUser, + ) -> anyhow::Result> { + let builders = self + .db + .builders + .get_some(query, None) + .await + .context("failed to pull builders from mongo")?; + + let builders = if user.is_admin { + builders + } else { + builders + .into_iter() + .filter(|builder| builder.get_user_permissions(&user.id) > PermissionLevel::None) + .collect() + }; + + Ok(builders) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + CreateBuilder { name, config }: CreateBuilder, + user: RequestUser, + ) -> anyhow::Result { + let start_ts = monitor_timestamp(); + let builder = Builder { + id: Default::default(), + name, + created_at: start_ts, + updated_at: start_ts, + permissions: [(user.id.clone(), PermissionLevel::Update)] + .into_iter() + .collect(), + description: Default::default(), + config: config.into(), + }; + let builder_id = self + .db + .builders + .create_one(builder) + .await + .context("failed to add build to db")?; + let builder = self.get_builder(&builder_id).await?; + let update = Update { + target: ResourceTarget::Builder(builder_id), + operation: Operation::CreateBuilder, + start_ts, + end_ts: Some(monitor_timestamp()), + operator: user.id.clone(), + success: true, + logs: vec![ + Log::simple( + "create builder", + format!( + "created builder\nid: {}\nname: {}", + builder.id, builder.name + ), + ), + Log::simple("config", format!("{:#?}", builder.config)), + ], + ..Default::default() + }; + + self.add_update(update).await?; + + Ok(builder) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + CopyBuilder { name, id }: CopyBuilder, + user: RequestUser, + ) -> anyhow::Result { + let Builder { + config, + description, + .. + } = self + .get_builder_check_permissions(&id, &user, PermissionLevel::Update) + .await?; + let start_ts = monitor_timestamp(); + let builder = Builder { + id: Default::default(), + name, + created_at: start_ts, + updated_at: start_ts, + permissions: [(user.id.clone(), PermissionLevel::Update)] + .into_iter() + .collect(), + description, + config, + }; + let builder_id = self + .db + .builders + .create_one(builder) + .await + .context("failed to add builder to db")?; + let builder = self.get_builder(&builder_id).await?; + let update = Update { + target: ResourceTarget::Builder(builder_id), + operation: Operation::CreateBuilder, + start_ts, + end_ts: Some(monitor_timestamp()), + operator: user.id.clone(), + success: true, + logs: vec![ + Log::simple( + "create builder", + format!( + "created builder\nid: {}\nname: {}", + builder.id, builder.name + ), + ), + Log::simple("config", format!("{:#?}", builder.config)), + ], + ..Default::default() + }; + + self.add_update(update).await?; + + Ok(builder) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + DeleteBuilder { id }: DeleteBuilder, + user: RequestUser, + ) -> anyhow::Result { + let builder = self + .get_builder_check_permissions(&id, &user, PermissionLevel::Update) + .await?; + + let start_ts = monitor_timestamp(); + + self.db + .builds + .update_many( + doc! { "config.builder.params.builder_id": &id }, + doc! { "$set": { "config.builder.params.builder_id": "" } }, + ) + .await?; + + self.db + .builds + .delete_one(&id) + .await + .context("failed to delete build from database")?; + + let mut update = Update { + target: ResourceTarget::Builder(id.clone()), + operation: Operation::DeleteBuilder, + start_ts, + operator: user.id.clone(), + logs: vec![Log::simple( + "delete builder", + format!("deleted builder {}", builder.name), + )], + ..Default::default() + }; + + update.finalize(); + self.add_update(update).await?; + + Ok(builder) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + UpdateBuilder { id, config }: UpdateBuilder, + user: RequestUser, + ) -> anyhow::Result { + let builder = self + .get_builder_check_permissions(&id, &user, PermissionLevel::Update) + .await?; + + let mut update = Update { + target: ResourceTarget::Builder(id.clone()), + operation: Operation::UpdateBuilder, + start_ts: monitor_timestamp(), + logs: vec![Log::simple( + "builder update", + serde_json::to_string_pretty(&config).unwrap(), + )], + operator: user.id.clone(), + ..Default::default() + }; + + let config = builder.config.merge_partial(config); + + self.db + .builders + .update_one( + &id, + mungos::Update::Set(doc! { "config": to_bson(&config)? }), + ) + .await?; + + let builder = self.get_builder(&id).await?; + + update.finalize(); + self.add_update(update).await?; + + Ok(builder) + } +} diff --git a/core/src/requests/api/deployment.rs b/core/src/requests/api/deployment.rs index 088d41c1d..784cb9474 100644 --- a/core/src/requests/api/deployment.rs +++ b/core/src/requests/api/deployment.rs @@ -487,7 +487,7 @@ impl Resolve for State { .deployments .update_one( &id, - mungos::Update::<()>::Set(doc! { "config": to_bson(&config)? }), + mungos::Update::Set(doc! { "config": to_bson(&config)? }), ) .await .context("failed to update server on mongo")?; @@ -583,7 +583,7 @@ impl Resolve for State { .deployments .update_one( &deployment.id, - mungos::Update::<()>::Set( + mungos::Update::Set( doc! { "name": &name, "updated_at": monitor_timestamp() }, ), ) diff --git a/core/src/requests/api/mod.rs b/core/src/requests/api/mod.rs index 5c9ff4339..43f2e3c4a 100644 --- a/core/src/requests/api/mod.rs +++ b/core/src/requests/api/mod.rs @@ -16,6 +16,7 @@ use crate::{ }; mod build; +mod builder; mod deployment; mod permissions; mod repo; @@ -33,6 +34,10 @@ pub enum ApiRequest { CreateLoginSecret(CreateLoginSecret), DeleteLoginSecret(DeleteLoginSecret), + // ==== PERMISSIONS ==== + UpdateUserPerimissions(UpdateUserPermissions), + UpdateUserPermissionsOnTarget(UpdateUserPermissionsOnTarget), + // ==== SERVER ==== GetPeripheryVersion(GetPeripheryVersion), GetSystemInformation(GetSystemInformation), @@ -89,7 +94,17 @@ pub enum ApiRequest { UpdateBuild(UpdateBuild), // ACTIONS RunBuild(RunBuild), - // ==== PERMISSIONS ==== + + // ==== REPO ==== + GetRepo(GetRepo), + ListRepos(ListRepos), + // CRUD + CreateRepo(CreateRepo), + UpdateRepo(UpdateRepo), + DeleteRepo(DeleteRepo), + // ACTIONS + CloneRepo(CloneRepo), + PullRepo(PullRepo), } pub fn router() -> Router { diff --git a/core/src/requests/api/permissions.rs b/core/src/requests/api/permissions.rs index 08ca7fdb6..b8ba94b75 100644 --- a/core/src/requests/api/permissions.rs +++ b/core/src/requests/api/permissions.rs @@ -2,11 +2,6 @@ use anyhow::{anyhow, Context}; use async_trait::async_trait; use monitor_types::{ entities::{ - build::Build, - builder::Builder, - deployment::Deployment, - repo::Repo, - server::Server, update::{Log, ResourceTarget, Update, UpdateStatus}, Operation, }, @@ -58,7 +53,7 @@ impl Resolve for State { } self.db .users - .update_one::(&user_id, mungos::Update::Set(update_doc)) + .update_one(&user_id, mungos::Update::Set(update_doc)) .await?; let end_ts = monitor_timestamp(); let mut update = Update { @@ -127,7 +122,7 @@ impl Resolve for State { .ok_or(anyhow!("failed to find a build with id {id}"))?; self.db .builds - .update_one::( + .update_one( id, mungos::Update::Set(doc! { format!("permissions.{}", user_id): permission.to_string() @@ -149,7 +144,7 @@ impl Resolve for State { .ok_or(anyhow!("failed to find a builder with id {id}"))?; self.db .builders - .update_one::( + .update_one( id, mungos::Update::Set(doc! { format!("permissions.{}", user_id): permission.to_string() @@ -171,7 +166,7 @@ impl Resolve for State { .ok_or(anyhow!("failed to find a deployment with id {id}"))?; self.db .deployments - .update_one::( + .update_one( id, mungos::Update::Set(doc! { format!("permissions.{}", user_id): permission.to_string() @@ -193,7 +188,7 @@ impl Resolve for State { .ok_or(anyhow!("failed to find a server with id {id}"))?; self.db .servers - .update_one::( + .update_one( id, mungos::Update::Set(doc! { format!("permissions.{}", user_id): permission.to_string() @@ -215,7 +210,7 @@ impl Resolve for State { .ok_or(anyhow!("failed to find a repo with id {id}"))?; self.db .repos - .update_one::( + .update_one( id, mungos::Update::Set(doc! { format!("permissions.{}", user_id): permission.to_string() diff --git a/core/src/requests/api/repo.rs b/core/src/requests/api/repo.rs index 815de39a0..3a5911bf8 100644 --- a/core/src/requests/api/repo.rs +++ b/core/src/requests/api/repo.rs @@ -301,7 +301,7 @@ impl Resolve for State { .repos .update_one( &repo.id, - mungos::Update::<()>::Set(doc! { "config": to_bson(&config)? }), + mungos::Update::Set(doc! { "config": to_bson(&config)? }), ) .await .context("failed to update repo on database")?; diff --git a/core/src/requests/api/secret.rs b/core/src/requests/api/secret.rs index 518ab9c4c..92fbd3c23 100644 --- a/core/src/requests/api/secret.rs +++ b/core/src/requests/api/secret.rs @@ -6,7 +6,7 @@ use monitor_types::{ requests::api::{CreateLoginSecret, CreateLoginSecretResponse, DeleteLoginSecret}, }; use mungos::{ - mongodb::bson::{doc, to_bson, Document}, + mongodb::bson::{doc, to_bson}, Update, }; use resolver_api::Resolve; @@ -42,7 +42,7 @@ impl Resolve for State { }; self.db .users - .update_one::( + .update_one( &user.id, Update::Custom(doc! { "$push": { @@ -65,7 +65,7 @@ impl Resolve for State { ) -> anyhow::Result<()> { self.db .users - .update_one::( + .update_one( &user.id, Update::Custom(doc! { "$pull": { diff --git a/core/src/requests/api/server.rs b/core/src/requests/api/server.rs index 5a5002dcc..2976d3cd9 100644 --- a/core/src/requests/api/server.rs +++ b/core/src/requests/api/server.rs @@ -126,49 +126,70 @@ impl Resolve for State { #[async_trait] impl Resolve for State { - async fn resolve(&self, req: DeleteServer, user: RequestUser) -> anyhow::Result { - if self.action_states.server.busy(&req.id).await { + async fn resolve( + &self, + DeleteServer { id }: DeleteServer, + user: RequestUser, + ) -> anyhow::Result { + if self.action_states.server.busy(&id).await { return Err(anyhow!("server busy")); } let server = self - .get_server_check_permissions(&req.id, &user, PermissionLevel::Update) + .get_server_check_permissions(&id, &user, PermissionLevel::Update) .await?; let start_ts = monitor_timestamp(); + self.db + .builds + .update_many( + doc! { "config.builder.params.server_id": &id }, + doc! { "$set": { "config.builder.params.server_id": "" } }, + ) + .await + .context("failed to detach server from builds")?; + + self.db + .deployments + .update_many( + doc! { "config.server_id": &id }, + doc! { "$set": { "config.server_id": "" } }, + ) + .await + .context("failed to detach server from deployments")?; + + self.db + .repos + .update_many( + doc! { "config.server_id": &id }, + doc! { "$set": { "config.server_id": "" } }, + ) + .await + .context("failed to detach server from repos")?; + + self.db + .servers + .delete_one(&id) + .await + .context("failed to delete server from mongo")?; + let mut update = Update { - target: ResourceTarget::Server(req.id.clone()), + target: ResourceTarget::Server(id.clone()), operation: Operation::DeleteServer, start_ts, operator: user.id.clone(), - success: true, - status: UpdateStatus::InProgress, + logs: vec![Log::simple( + "delete server", + format!("deleted server {}", server.name), + )], ..Default::default() }; - update.id = self.add_update(update.clone()).await?; + update.finalize(); + self.add_update(update).await?; - let res = self - .db - .servers - .delete_one(&req.id) - .await - .context("failed to delete server from mongo"); - - let log = match res { - Ok(_) => Log::simple("delete server", format!("deleted server {}", server.name)), - Err(e) => Log::error("delete server", format!("failed to delete server\n{e:#?}")), - }; - - update.end_ts = Some(monitor_timestamp()); - update.status = UpdateStatus::Complete; - update.success = log.success; - update.logs.push(log); - - self.update_update(update).await?; - - self.server_status_cache.remove(&req.id).await; + self.server_status_cache.remove(&id).await; Ok(server) } @@ -191,7 +212,7 @@ impl Resolve for State { .servers .update_one( &id, - mungos::Update::<()>::Set(doc! { "config": to_bson(&config)? }), + mungos::Update::Set(doc! { "config": to_bson(&config)? }), ) .await .context("failed to update server on mongo")?; @@ -235,9 +256,7 @@ impl Resolve for State { .updates .update_one( &id, - mungos::Update::::Set( - doc! { "name": &name, "updated_at": monitor_timestamp() }, - ), + mungos::Update::Set(doc! { "name": &name, "updated_at": monitor_timestamp() }), ) .await?; let mut update = Update { diff --git a/lib/types/src/entities/builder.rs b/lib/types/src/entities/builder.rs index a22af19e0..d77035ec7 100644 --- a/lib/types/src/entities/builder.rs +++ b/lib/types/src/entities/builder.rs @@ -1,6 +1,7 @@ use bson::serde_helpers::hex_string_as_object_id; use derive_builder::Builder; use mungos::MungosIndexed; +use partial_derive2::Partial; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -50,18 +51,58 @@ pub enum BuilderConfig { } #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Builder, MungosIndexed)] +#[derive(Serialize, Deserialize, Debug, Clone, MungosIndexed)] +#[serde(tag = "type", content = "params")] +pub enum PartialBuilderConfig { + AwsBuilder(PartialAwsBuilder), +} + +impl From for BuilderConfig { + fn from(value: PartialBuilderConfig) -> BuilderConfig { + match value { + PartialBuilderConfig::AwsBuilder(builder) => BuilderConfig::AwsBuilder(builder.into()), + } + } +} + +impl BuilderConfig { + pub fn merge_partial(self, partial: PartialBuilderConfig) -> BuilderConfig { + match partial { + PartialBuilderConfig::AwsBuilder(partial) => match self { + BuilderConfig::AwsBuilder(config) => { + let config = AwsBuilder { + region: partial.region.unwrap_or(config.region), + instance_type: partial.instance_type.unwrap_or(config.instance_type), + volume_gb: partial.volume_gb.unwrap_or(config.volume_gb), + ami_id: partial.ami_id.unwrap_or(config.ami_id), + subnet_id: partial.subnet_id.unwrap_or(config.subnet_id), + security_group_ids: partial.security_group_ids.unwrap_or(config.security_group_ids), + key_pair_name: partial.key_pair_name.unwrap_or(config.key_pair_name), + assign_public_ip: partial.assign_public_ip.unwrap_or(config.assign_public_ip), + }; + BuilderConfig::AwsBuilder(config) + } + // _ => BuilderConfig::AwsBuilder(partial.into()), + }, + } + } +} + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)] +#[partial_derive(Serialize, Deserialize, Debug, Clone)] +#[skip_serializing_none] pub struct AwsBuilder { - #[serde(default = "default_region")] - #[builder(default = "default_region()")] + #[serde(default = "aws_default_region")] + #[builder(default = "aws_default_region()")] pub region: String, - #[serde(default = "default_instance_type")] - #[builder(default = "default_instance_type()")] + #[serde(default = "aws_default_instance_type")] + #[builder(default = "aws_default_instance_type()")] pub instance_type: String, - #[serde(default = "default_volume_gb")] - #[builder(default = "default_volume_gb()")] + #[serde(default = "aws_default_volume_gb")] + #[builder(default = "aws_default_volume_gb()")] pub volume_gb: i32, pub ami_id: String, @@ -71,14 +112,29 @@ pub struct AwsBuilder { pub assign_public_ip: bool, } -fn default_region() -> String { +fn aws_default_region() -> String { String::from("us-east-1") } -fn default_instance_type() -> String { +fn aws_default_instance_type() -> String { String::from("c5.2xlarge") } -fn default_volume_gb() -> i32 { +fn aws_default_volume_gb() -> i32 { 20 } + +impl From for AwsBuilder { + fn from(value: PartialAwsBuilder) -> AwsBuilder { + AwsBuilder { + region: value.region.unwrap_or(aws_default_region()), + instance_type: value.instance_type.unwrap_or(aws_default_instance_type()), + volume_gb: value.volume_gb.unwrap_or(aws_default_volume_gb()), + ami_id: value.ami_id.unwrap_or_default(), + subnet_id: value.subnet_id.unwrap_or_default(), + security_group_ids: value.security_group_ids.unwrap_or_default(), + key_pair_name: value.key_pair_name.unwrap_or_default(), + assign_public_ip: value.assign_public_ip.unwrap_or_default(), + } + } +} diff --git a/lib/types/src/requests/api/builder.rs b/lib/types/src/requests/api/builder.rs index f164c091d..62693ea90 100644 --- a/lib/types/src/requests/api/builder.rs +++ b/lib/types/src/requests/api/builder.rs @@ -1,11 +1,65 @@ -// use monitor_macros::derive_crud_requests; +use resolver_api::derive::Request; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; -// use crate::{ -// entities::{ -// builder::{Builder}, -// update::Update, -// }, -// MongoDocument, -// }; +use crate::{ + entities::builder::{Builder, PartialBuilderConfig}, + MongoDocument, +}; -// derive_crud_requests!(Builder); +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Builder)] +pub struct GetBuilder { + pub id: String, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Vec)] +pub struct ListBuilders { + pub query: Option, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Builder)] +pub struct CreateBuilder { + pub name: String, + pub config: PartialBuilderConfig, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Builder)] +pub struct CopyBuilder { + pub name: String, + pub id: String, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Builder)] +pub struct DeleteBuilder { + pub id: String, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Builder)] +pub struct UpdateBuilder { + pub id: String, + pub config: PartialBuilderConfig, +}