CRUD for procedures

This commit is contained in:
mbecker20
2024-01-07 14:54:57 -08:00
parent cb6dc29469
commit 7ed115cddb
16 changed files with 568 additions and 11 deletions

View File

@@ -23,6 +23,7 @@ mod alerter;
mod build;
mod builder;
mod deployment;
mod procedure;
mod repo;
mod search;
mod server;
@@ -45,6 +46,13 @@ enum ReadRequest {
// ==== SEARCH ====
FindResources(FindResources),
// ==== PROCEDURE ====
GetProceduresSummary(GetProceduresSummary),
GetProcedure(GetProcedure),
GetProcedureActionState(GetProcedureActionState),
ListProcedures(ListProcedures),
ListProceduresByIds(ListProceduresByIds),
// ==== SERVER ====
GetServersSummary(GetServersSummary),
GetServer(GetServer),

View File

@@ -0,0 +1,117 @@
use anyhow::Context;
use async_trait::async_trait;
use monitor_client::{
api::read::{
GetProcedure, GetProcedureActionState,
GetProcedureActionStateResponse, GetProcedureResponse,
GetProceduresSummary, GetProceduresSummaryResponse,
ListProcedures, ListProceduresByIds, ListProceduresByIdsResponse,
ListProceduresResponse,
},
entities::{procedure::Procedure, PermissionLevel},
};
use mungos::mongodb::bson::doc;
use resolver_api::Resolve;
use crate::{
auth::RequestUser, helpers::resource::StateResource, state::State,
};
#[async_trait]
impl Resolve<GetProcedure, RequestUser> for State {
async fn resolve(
&self,
GetProcedure { id }: GetProcedure,
user: RequestUser,
) -> anyhow::Result<GetProcedureResponse> {
self
.get_resource_check_permissions(
&id,
&user,
PermissionLevel::Read,
)
.await
}
}
#[async_trait]
impl Resolve<ListProcedures, RequestUser> for State {
async fn resolve(
&self,
ListProcedures { query }: ListProcedures,
user: RequestUser,
) -> anyhow::Result<ListProceduresResponse> {
<State as StateResource<Procedure>>::list_resources_for_user(
self, query, &user,
)
.await
}
}
#[async_trait]
impl Resolve<ListProceduresByIds, RequestUser> for State {
async fn resolve(
&self,
ListProceduresByIds { ids }: ListProceduresByIds,
user: RequestUser,
) -> anyhow::Result<ListProceduresByIdsResponse> {
<State as StateResource<Procedure>>::list_resources_for_user(
self,
doc! { "_id": { "$in": ids } }.into(),
&user,
)
.await
}
}
#[async_trait]
impl Resolve<GetProceduresSummary, RequestUser> for State {
async fn resolve(
&self,
GetProceduresSummary {}: GetProceduresSummary,
user: RequestUser,
) -> anyhow::Result<GetProceduresSummaryResponse> {
let query = if user.is_admin {
None
} else {
let query = doc! {
format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] }
};
Some(query)
};
let total = self
.db
.procedures
.count_documents(query, None)
.await
.context("failed to count all procedure documents")?;
let res = GetProceduresSummaryResponse {
total: total as u32,
};
Ok(res)
}
}
#[async_trait]
impl Resolve<GetProcedureActionState, RequestUser> for State {
async fn resolve(
&self,
GetProcedureActionState { id }: GetProcedureActionState,
user: RequestUser,
) -> anyhow::Result<GetProcedureActionStateResponse> {
let _: Procedure = self
.get_resource_check_permissions(
&id,
&user,
PermissionLevel::Read,
)
.await?;
let action_state = self
.action_states
.procedure
.get(&id)
.await
.unwrap_or_default();
Ok(action_state)
}
}

View File

@@ -23,6 +23,7 @@ mod deployment;
mod description;
mod launch;
mod permissions;
mod procedure;
mod repo;
mod secret;
mod server;
@@ -90,6 +91,12 @@ enum WriteRequest {
DeleteAlerter(DeleteAlerter),
UpdateAlerter(UpdateAlerter),
// ==== PROCEDURE ====
CreateProcedure(CreateProcedure),
CopyProcedure(CopyProcedure),
DeleteProcedure(DeleteProcedure),
UpdateProcedure(UpdateProcedure),
// ==== TAG ====
CreateTag(CreateTag),
DeleteTag(DeleteTag),

View File

@@ -0,0 +1,236 @@
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::write::*,
entities::{
monitor_timestamp, procedure::Procedure, to_monitor_name,
update::Log, Operation, PermissionLevel,
},
};
use mungos::{
by_id::{delete_one_by_id, update_one_by_id},
mongodb::bson::{doc, to_document},
};
use resolver_api::Resolve;
use crate::{
auth::RequestUser,
helpers::{make_update, resource::StateResource},
state::State,
};
#[async_trait]
impl Resolve<CreateProcedure, RequestUser> for State {
async fn resolve(
&self,
CreateProcedure { name, config }: CreateProcedure,
user: RequestUser,
) -> anyhow::Result<CreateProcedureResponse> {
let name = to_monitor_name(&name);
let start_ts = monitor_timestamp();
let procedure = Procedure {
id: Default::default(),
name,
updated_at: start_ts,
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
description: Default::default(),
tags: Default::default(),
info: Default::default(),
config,
};
let procedure_id = self
.db
.procedures
.insert_one(procedure, None)
.await
.context("failed to add procedure to db")?
.inserted_id
.to_string();
let procedure: Procedure =
self.get_resource(&procedure_id).await?;
let mut update =
make_update(&procedure, Operation::CreateProcedure, &user);
update.push_simple_log(
"create procedure",
format!(
"created procedure\nid: {}\nname: {}",
procedure.id, procedure.name
),
);
update
.push_simple_log("config", format!("{:#?}", procedure.config));
update.finalize();
self.add_update(update).await?;
Ok(procedure)
}
}
#[async_trait]
impl Resolve<CopyProcedure, RequestUser> for State {
async fn resolve(
&self,
CopyProcedure { name, id }: CopyProcedure,
user: RequestUser,
) -> anyhow::Result<CopyProcedureResponse> {
let name = to_monitor_name(&name);
let Procedure {
config,
description,
tags,
..
} = self
.get_resource_check_permissions(
&id,
&user,
PermissionLevel::Update,
)
.await?;
let start_ts = monitor_timestamp();
let build = Procedure {
id: Default::default(),
name,
updated_at: start_ts,
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
description,
tags,
config,
info: Default::default(),
};
let procedure_id = self
.db
.procedures
.insert_one(build, None)
.await
.context("failed to add build to db")?
.inserted_id
.to_string();
let procedure: Procedure =
self.get_resource(&procedure_id).await?;
let mut update =
make_update(&procedure, Operation::CreateProcedure, &user);
update.push_simple_log(
"create procedure",
format!(
"created procedure\nid: {}\nname: {}",
procedure.id, procedure.name
),
);
update.push_simple_log(
"config",
serde_json::to_string_pretty(&procedure)?,
);
update.finalize();
self.add_update(update).await?;
Ok(procedure)
}
}
#[async_trait]
impl Resolve<UpdateProcedure, RequestUser> for State {
async fn resolve(
&self,
UpdateProcedure { id, config }: UpdateProcedure,
user: RequestUser,
) -> anyhow::Result<UpdateProcedureResponse> {
let procedure: Procedure = self
.get_resource_check_permissions(
&id,
&user,
PermissionLevel::Update,
)
.await?;
update_one_by_id(
&self.db.procedures,
&procedure.id,
mungos::update::Update::FlattenSet(
doc! { "config": to_document(&config)? },
),
None,
)
.await
.context("failed to update procedure on database")?;
let mut update =
make_update(&procedure, Operation::UpdateProcedure, &user);
update.push_simple_log(
"procedure update",
serde_json::to_string_pretty(&config)?,
);
update.finalize();
self.add_update(update).await?;
let procedure: Procedure =
self.get_resource(&procedure.id).await?;
Ok(procedure)
}
}
#[async_trait]
impl Resolve<DeleteProcedure, RequestUser> for State {
async fn resolve(
&self,
DeleteProcedure { id }: DeleteProcedure,
user: RequestUser,
) -> anyhow::Result<DeleteProcedureResponse> {
// needs to pull its id from all container procedures
if self.action_states.procedure.busy(&id).await {
return Err(anyhow!("procedure busy"));
}
let procedure: Procedure = self
.get_resource_check_permissions(
&id,
&user,
PermissionLevel::Update,
)
.await?;
let mut update =
make_update(&procedure, Operation::DeleteProcedure, &user);
update.in_progress();
update.id = self.add_update(update.clone()).await?;
let res = delete_one_by_id(&self.db.procedures, &id, None)
.await
.context("failed to delete build from database");
let log = match res {
Ok(_) => Log::simple(
"delete procedure",
format!("deleted procedure {}", procedure.name),
),
Err(e) => Log::error(
"delete procedure",
format!("failed to delete procedure\n{e:#?}"),
),
};
update.logs.push(log);
update.finalize();
self.update_update(update).await?;
self.remove_from_recently_viewed(&procedure).await?;
Ok(procedure)
}
}

View File

@@ -31,10 +31,10 @@ pub struct InnerRequestUser {
}
impl InnerRequestUser {
pub fn admin() -> InnerRequestUser {
pub fn procedure() -> InnerRequestUser {
InnerRequestUser {
id: String::from("admin"),
username: String::from("admin"),
id: String::from("procedure"),
username: String::from("procedure"),
is_admin: true,
create_build_permissions: true,
create_server_permissions: true,

View File

@@ -230,10 +230,10 @@ impl State {
.update_many(
doc! {},
doc! {
"$pull": {
"recently_viewed":
to_bson(&resource).context("failed to convert resource to bson")?
}
"$pull": {
"recently_viewed":
to_bson(&resource).context("failed to convert resource to bson")?
}
},
None
)

View File

@@ -164,9 +164,10 @@ impl State {
&self,
execution: Execution,
) -> anyhow::Result<()> {
let user: Arc<_> = InnerRequestUser::admin().into();
let user: Arc<_> = InnerRequestUser::procedure().into();
let update =
match execution {
Execution::None(_) => return Ok(()),
Execution::RunBuild(req) => self
.resolve(req, user)
.await

View File

@@ -10,6 +10,7 @@ use monitor_client::entities::{
build::BuildActionState,
config::CoreConfig,
deployment::{DeploymentActionState, DockerContainerState},
procedure::ProcedureActionState,
repo::RepoActionState,
server::ServerActionState,
update::UpdateListItem,
@@ -167,5 +168,6 @@ pub struct ActionStates {
pub deployment: Cache<String, DeploymentActionState>,
pub server: Cache<String, ServerActionState>,
pub repo: Cache<String, RepoActionState>,
pub procedure: Cache<String, ProcedureActionState>,
// pub command: Cache<CommandActionState>,
}

View File

@@ -20,6 +20,9 @@ pub trait MonitorExecuteRequest: HasResponse {}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", content = "params")]
pub enum Execution {
/// For new executions upon instantiation
None(None),
// PROCEDURE
RunProcedure(RunProcedure),
@@ -42,3 +45,7 @@ pub enum Execution {
PruneDockerImages(PruneDockerImages),
PruneDockerContainers(PruneDockerContainers),
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct None {}

View File

@@ -8,6 +8,7 @@ mod alerter;
mod build;
mod builder;
mod deployment;
mod procedure;
mod repo;
mod search;
mod server;
@@ -19,6 +20,7 @@ pub use alerter::*;
pub use build::*;
pub use builder::*;
pub use deployment::*;
pub use procedure::*;
pub use repo::*;
pub use search::*;
pub use server::*;

View File

@@ -0,0 +1,89 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::derive::Request;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::{
procedure::{Procedure, ProcedureActionState, ProcedureListItem},
MongoDocument,
};
use super::MonitorReadRequest;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(GetProcedureResponse)]
pub struct GetProcedure {
pub id: String,
}
#[typeshare]
pub type GetProcedureResponse = Procedure;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(ListProceduresResponse)]
pub struct ListProcedures {
pub query: Option<MongoDocument>,
}
#[typeshare]
pub type ListProceduresResponse = Vec<ProcedureListItem>;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(ListProceduresByIdsResponse)]
pub struct ListProceduresByIds {
pub ids: Vec<String>,
}
#[typeshare]
pub type ListProceduresByIdsResponse = Vec<ProcedureListItem>;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(GetProceduresSummaryResponse)]
pub struct GetProceduresSummary {}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GetProceduresSummaryResponse {
pub total: u32,
}
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(GetProcedureActionStateResponse)]
pub struct GetProcedureActionState {
pub id: String,
}
#[typeshare]
pub type GetProcedureActionStateResponse = ProcedureActionState;
//

View File

@@ -5,6 +5,7 @@ mod deployment;
mod description;
mod launch;
mod permissions;
mod procedure;
mod repo;
mod secret;
mod server;
@@ -18,11 +19,11 @@ pub use deployment::*;
pub use description::*;
pub use launch::*;
pub use permissions::*;
pub use procedure::*;
pub use repo::*;
use resolver_api::HasResponse;
pub use secret::*;
pub use server::*;
pub use tags::*;
pub use user::*;
pub trait MonitorWriteRequest: HasResponse {}
pub trait MonitorWriteRequest: resolver_api::HasResponse {}

View File

@@ -0,0 +1,71 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::derive::Request;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::procedure::{Procedure, ProcedureConfig};
use super::MonitorWriteRequest;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorWriteRequest)]
#[response(CreateProcedureResponse)]
pub struct CreateProcedure {
pub name: String,
pub config: ProcedureConfig,
}
#[typeshare]
pub type CreateProcedureResponse = Procedure;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorWriteRequest)]
#[response(CopyProcedureResponse)]
pub struct CopyProcedure {
pub name: String,
pub id: String,
}
#[typeshare]
pub type CopyProcedureResponse = Procedure;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorWriteRequest)]
#[response(DeleteProcedureResponse)]
pub struct DeleteProcedure {
pub id: String,
}
#[typeshare]
pub type DeleteProcedureResponse = Procedure;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorWriteRequest)]
#[response(UpdateProcedureResponse)]
pub struct UpdateProcedure {
pub id: String,
pub config: ProcedureConfig,
}
#[typeshare]
pub type UpdateProcedureResponse = Procedure;

View File

@@ -1,6 +1,7 @@
use crate::entities::{
build::BuildActionState, deployment::DeploymentActionState,
repo::RepoActionState, server::ServerActionState,
procedure::ProcedureActionState, repo::RepoActionState,
server::ServerActionState,
};
pub trait Busy {
@@ -38,3 +39,9 @@ impl Busy for RepoActionState {
self.cloning || self.pulling || self.updating || self.deleting
}
}
impl Busy for ProcedureActionState {
fn busy(&self) -> bool {
self.running
}
}

View File

@@ -394,6 +394,9 @@ pub enum Operation {
DeleteAlerter,
// procedure
CreateProcedure,
UpdateProcedure,
DeleteProcedure,
RunProcedure,
// user

View File

@@ -45,3 +45,9 @@ impl From<&ProcedureConfig> for ProcedureConfigVariant {
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ProcedureActionState {
pub running: bool
}