crud for builder

This commit is contained in:
mbecker20
2023-07-04 00:02:27 +00:00
parent c5b1079be9
commit 99c2d6195d
13 changed files with 468 additions and 78 deletions

4
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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<LoginWithSecret> for State {
if expires < ts {
self.db
.users
.update_one::<Document>(
.update_one(
&user.id,
Update::Custom(doc! { "$pull": { "secrets": { "name": s.name } } }),
)

View File

@@ -317,7 +317,7 @@ impl Resolve<UpdateBuild, RequestUser> 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<RunBuild, RequestUser> for State {
let _ = self
.db
.builds
.update_one::<Build>(
.update_one(
&build.id,
mungos::Update::Set(doc! {
"version": to_bson(&build.config.version)

View File

@@ -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<GetBuilder, RequestUser> for State {
async fn resolve(
&self,
GetBuilder { id }: GetBuilder,
user: RequestUser,
) -> anyhow::Result<Builder> {
self.get_builder_check_permissions(&id, &user, PermissionLevel::Read)
.await
}
}
#[async_trait]
impl Resolve<ListBuilders, RequestUser> for State {
async fn resolve(
&self,
ListBuilders { query }: ListBuilders,
user: RequestUser,
) -> anyhow::Result<Vec<Builder>> {
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<CreateBuilder, RequestUser> for State {
async fn resolve(
&self,
CreateBuilder { name, config }: CreateBuilder,
user: RequestUser,
) -> anyhow::Result<Builder> {
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<CopyBuilder, RequestUser> for State {
async fn resolve(
&self,
CopyBuilder { name, id }: CopyBuilder,
user: RequestUser,
) -> anyhow::Result<Builder> {
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<DeleteBuilder, RequestUser> for State {
async fn resolve(
&self,
DeleteBuilder { id }: DeleteBuilder,
user: RequestUser,
) -> anyhow::Result<Builder> {
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<UpdateBuilder, RequestUser> for State {
async fn resolve(
&self,
UpdateBuilder { id, config }: UpdateBuilder,
user: RequestUser,
) -> anyhow::Result<Builder> {
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)
}
}

View File

@@ -487,7 +487,7 @@ impl Resolve<UpdateDeployment, RequestUser> 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<RenameDeployment, RequestUser> for State {
.deployments
.update_one(
&deployment.id,
mungos::Update::<()>::Set(
mungos::Update::Set(
doc! { "name": &name, "updated_at": monitor_timestamp() },
),
)

View File

@@ -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 {

View File

@@ -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<UpdateUserPermissions, RequestUser> for State {
}
self.db
.users
.update_one::<Document>(&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<UpdateUserPermissionsOnTarget, RequestUser> for State {
.ok_or(anyhow!("failed to find a build with id {id}"))?;
self.db
.builds
.update_one::<Build>(
.update_one(
id,
mungos::Update::Set(doc! {
format!("permissions.{}", user_id): permission.to_string()
@@ -149,7 +144,7 @@ impl Resolve<UpdateUserPermissionsOnTarget, RequestUser> for State {
.ok_or(anyhow!("failed to find a builder with id {id}"))?;
self.db
.builders
.update_one::<Builder>(
.update_one(
id,
mungos::Update::Set(doc! {
format!("permissions.{}", user_id): permission.to_string()
@@ -171,7 +166,7 @@ impl Resolve<UpdateUserPermissionsOnTarget, RequestUser> for State {
.ok_or(anyhow!("failed to find a deployment with id {id}"))?;
self.db
.deployments
.update_one::<Deployment>(
.update_one(
id,
mungos::Update::Set(doc! {
format!("permissions.{}", user_id): permission.to_string()
@@ -193,7 +188,7 @@ impl Resolve<UpdateUserPermissionsOnTarget, RequestUser> for State {
.ok_or(anyhow!("failed to find a server with id {id}"))?;
self.db
.servers
.update_one::<Server>(
.update_one(
id,
mungos::Update::Set(doc! {
format!("permissions.{}", user_id): permission.to_string()
@@ -215,7 +210,7 @@ impl Resolve<UpdateUserPermissionsOnTarget, RequestUser> for State {
.ok_or(anyhow!("failed to find a repo with id {id}"))?;
self.db
.repos
.update_one::<Repo>(
.update_one(
id,
mungos::Update::Set(doc! {
format!("permissions.{}", user_id): permission.to_string()

View File

@@ -301,7 +301,7 @@ impl Resolve<UpdateRepo, RequestUser> 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")?;

View File

@@ -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<CreateLoginSecret, RequestUser> for State {
};
self.db
.users
.update_one::<Document>(
.update_one(
&user.id,
Update::Custom(doc! {
"$push": {
@@ -65,7 +65,7 @@ impl Resolve<DeleteLoginSecret, RequestUser> for State {
) -> anyhow::Result<()> {
self.db
.users
.update_one::<Document>(
.update_one(
&user.id,
Update::Custom(doc! {
"$pull": {

View File

@@ -126,49 +126,70 @@ impl Resolve<CreateServer, RequestUser> for State {
#[async_trait]
impl Resolve<DeleteServer, RequestUser> for State {
async fn resolve(&self, req: DeleteServer, user: RequestUser) -> anyhow::Result<Server> {
if self.action_states.server.busy(&req.id).await {
async fn resolve(
&self,
DeleteServer { id }: DeleteServer,
user: RequestUser,
) -> anyhow::Result<Server> {
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<UpdateServer, RequestUser> 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<RenameServer, RequestUser> for State {
.updates
.update_one(
&id,
mungos::Update::<Server>::Set(
doc! { "name": &name, "updated_at": monitor_timestamp() },
),
mungos::Update::Set(doc! { "name": &name, "updated_at": monitor_timestamp() }),
)
.await?;
let mut update = Update {

View File

@@ -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<PartialBuilderConfig> 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<PartialAwsBuilder> 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(),
}
}
}

View File

@@ -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<Builder>)]
pub struct ListBuilders {
pub query: Option<MongoDocument>,
}
//
#[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,
}