mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-11 08:01:13 +00:00
minimize update diffs
This commit is contained in:
@@ -0,0 +1,737 @@
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::write::CreateTag,
|
||||
entities::{
|
||||
alerter::{
|
||||
Alerter, AlerterConfig, AlerterConfigVariant, AlerterInfo,
|
||||
AlerterListItem, AlerterListItemInfo, AlerterQuerySpecifics,
|
||||
},
|
||||
build::{
|
||||
Build, BuildConfig, BuildInfo, BuildListItem,
|
||||
BuildListItemInfo, BuildQuerySpecifics,
|
||||
},
|
||||
builder::{
|
||||
Builder, BuilderConfig, BuilderConfigVariant, BuilderListItem,
|
||||
BuilderListItemInfo, BuilderQuerySpecifics,
|
||||
},
|
||||
deployment::{
|
||||
Deployment, DeploymentConfig, DeploymentImage,
|
||||
DeploymentListItem, DeploymentListItemInfo,
|
||||
DeploymentQuerySpecifics,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
procedure::{
|
||||
Procedure, ProcedureConfig, ProcedureListItem,
|
||||
ProcedureListItemInfo, ProcedureQuerySpecifics,
|
||||
},
|
||||
repo::{
|
||||
Repo, RepoConfig, RepoInfo, RepoListItem, RepoListItemInfo,
|
||||
RepoQuerySpecifics,
|
||||
},
|
||||
resource::{AddFilters, Resource, ResourceQuery},
|
||||
server::{
|
||||
Server, ServerConfig, ServerListItem, ServerListItemInfo,
|
||||
ServerQuerySpecifics,
|
||||
},
|
||||
server_template::{
|
||||
ServerTemplate, ServerTemplateConfig,
|
||||
ServerTemplateConfigVariant, ServerTemplateListItem,
|
||||
ServerTemplateListItemInfo, ServerTemplateQuerySpecifics,
|
||||
},
|
||||
update::{ResourceTarget, ResourceTargetVariant},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{
|
||||
bson::{doc, oid::ObjectId, Document},
|
||||
Collection,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::{
|
||||
helpers::query::user_target_query,
|
||||
state::State,
|
||||
state::{db_client, deployment_status_cache, server_status_cache},
|
||||
};
|
||||
|
||||
use super::query::get_tag;
|
||||
|
||||
pub trait StateResource {
|
||||
type ListItem: Serialize + Send;
|
||||
type Config: Send
|
||||
+ Sync
|
||||
+ Unpin
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ 'static;
|
||||
type Info: Send
|
||||
+ Sync
|
||||
+ Unpin
|
||||
+ Default
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ 'static;
|
||||
type QuerySpecifics: AddFilters + Default + std::fmt::Debug;
|
||||
|
||||
fn name() -> &'static str;
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant;
|
||||
|
||||
async fn coll(
|
||||
) -> &'static Collection<Resource<Self::Config, Self::Info>>;
|
||||
|
||||
async fn to_list_item(
|
||||
resource: Resource<Self::Config, Self::Info>,
|
||||
) -> anyhow::Result<Self::ListItem>;
|
||||
|
||||
async fn get_resource(
|
||||
id_or_name: &str,
|
||||
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
|
||||
let filter = match ObjectId::from_str(id_or_name) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": id_or_name },
|
||||
};
|
||||
Self::coll()
|
||||
.await
|
||||
.find_one(filter, None)
|
||||
.await
|
||||
.context("failed to query db for resource")?
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"did not find any {} matching {id_or_name}",
|
||||
Self::name()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_resource_check_permissions(
|
||||
id_or_name: &str,
|
||||
user: &User,
|
||||
permission_level: PermissionLevel,
|
||||
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
|
||||
let resource = Self::get_resource(id_or_name).await?;
|
||||
if user.admin {
|
||||
return Ok(resource);
|
||||
}
|
||||
let permissions =
|
||||
Self::get_user_permission_on_resource(&user.id, &resource.id)
|
||||
.await?;
|
||||
if permissions >= permission_level {
|
||||
Ok(resource)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"user does not have required permissions on this {}",
|
||||
Self::name()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_permission_on_resource(
|
||||
user_id: &str,
|
||||
resource_id: &str,
|
||||
) -> anyhow::Result<PermissionLevel> {
|
||||
get_user_permission_on_resource(
|
||||
user_id,
|
||||
Self::resource_target_variant(),
|
||||
resource_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_resource_ids_for_non_admin(
|
||||
user_id: &str,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
get_resource_ids_for_non_admin(
|
||||
user_id,
|
||||
Self::resource_target_variant(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_resource_list_items_for_user(
|
||||
mut query: ResourceQuery<Self::QuerySpecifics>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Self::ListItem>> {
|
||||
validate_resource_query_tags(&mut query).await;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
Self::query_resource_list_items_for_user(filters, user).await
|
||||
}
|
||||
|
||||
async fn query_resource_list_items_for_user(
|
||||
filters: Document,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Self::ListItem>> {
|
||||
let list = Self::query_resources_for_user(filters, user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| Self::to_list_item(resource));
|
||||
|
||||
let list = join_all(list)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.context(format!(
|
||||
"failed to convert {} list item",
|
||||
Self::name()
|
||||
))?;
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
async fn list_resources_for_user(
|
||||
mut query: ResourceQuery<Self::QuerySpecifics>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Resource<Self::Config, Self::Info>>> {
|
||||
validate_resource_query_tags(&mut query).await;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
Self::query_resources_for_user(filters, user).await
|
||||
}
|
||||
|
||||
async fn query_resources_for_user(
|
||||
mut filters: Document,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Resource<Self::Config, Self::Info>>> {
|
||||
if !user.admin {
|
||||
let ids = Self::get_resource_ids_for_non_admin(&user.id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect::<Vec<_>>();
|
||||
filters.insert("_id", doc! { "$in": ids });
|
||||
}
|
||||
find_collect(Self::coll().await, filters, None)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to pull {}s from mongo", Self::name())
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_description(
|
||||
id_or_name: &str,
|
||||
description: &str,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Self::get_resource_check_permissions(
|
||||
id_or_name,
|
||||
user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
let filter = match ObjectId::from_str(id_or_name) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": id_or_name },
|
||||
};
|
||||
Self::coll()
|
||||
.await
|
||||
.update_one(
|
||||
filter,
|
||||
doc! { "$set": { "description": description } },
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_tags_on_resource(
|
||||
id_or_name: &str,
|
||||
tags: Vec<String>,
|
||||
user: User,
|
||||
) -> anyhow::Result<()> {
|
||||
let futures = tags.iter().map(|tag| async {
|
||||
match get_tag(tag).await {
|
||||
Ok(tag) => Ok(tag.id),
|
||||
Err(_) => State
|
||||
.resolve(
|
||||
CreateTag {
|
||||
name: tag.to_string(),
|
||||
},
|
||||
user.clone(),
|
||||
)
|
||||
.await
|
||||
.map(|tag| tag.id),
|
||||
}
|
||||
});
|
||||
let tags = join_all(futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
Self::coll()
|
||||
.await
|
||||
.update_one(
|
||||
id_or_name_filter(id_or_name),
|
||||
doc! { "$set": { "tags": tags } },
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_tag_from_resources(
|
||||
tag_id: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
Self::coll()
|
||||
.await
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! { "$pull": { "tags": tag_id } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to remove tag from resources")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn id_or_name_filter(id_or_name: &str) -> Document {
|
||||
match ObjectId::from_str(id_or_name) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": id_or_name },
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Server {
|
||||
type ListItem = ServerListItem;
|
||||
type Config = ServerConfig;
|
||||
type Info = ();
|
||||
type QuerySpecifics = ServerQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"server"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Server
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Server> {
|
||||
&db_client().await.servers
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
server: Server,
|
||||
) -> anyhow::Result<ServerListItem> {
|
||||
let status = server_status_cache().get(&server.id).await;
|
||||
Ok(ServerListItem {
|
||||
name: server.name,
|
||||
created_at: ObjectId::from_str(&server.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: server.id,
|
||||
tags: server.tags,
|
||||
resource_type: ResourceTargetVariant::Server,
|
||||
info: ServerListItemInfo {
|
||||
status: status.map(|s| s.status).unwrap_or_default(),
|
||||
region: server.config.region,
|
||||
send_unreachable_alerts: server
|
||||
.config
|
||||
.send_unreachable_alerts,
|
||||
send_cpu_alerts: server.config.send_cpu_alerts,
|
||||
send_mem_alerts: server.config.send_mem_alerts,
|
||||
send_disk_alerts: server.config.send_disk_alerts,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Deployment {
|
||||
type ListItem = DeploymentListItem;
|
||||
type Config = DeploymentConfig;
|
||||
type Info = ();
|
||||
type QuerySpecifics = DeploymentQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"deployment"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Deployment
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Deployment> {
|
||||
&db_client().await.deployments
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
deployment: Deployment,
|
||||
) -> anyhow::Result<DeploymentListItem> {
|
||||
let status = deployment_status_cache().get(&deployment.id).await;
|
||||
let (image, build_id) = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = Build::get_resource(&build_id).await?;
|
||||
let version = if version.is_none() {
|
||||
build.config.version.to_string()
|
||||
} else {
|
||||
version.to_string()
|
||||
};
|
||||
(format!("{}:{version}", build.name), Some(build_id))
|
||||
}
|
||||
DeploymentImage::Image { image } => (image, None),
|
||||
};
|
||||
Ok(DeploymentListItem {
|
||||
name: deployment.name,
|
||||
created_at: ObjectId::from_str(&deployment.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: deployment.id,
|
||||
tags: deployment.tags,
|
||||
resource_type: ResourceTargetVariant::Deployment,
|
||||
info: DeploymentListItemInfo {
|
||||
state: status
|
||||
.as_ref()
|
||||
.map(|s| s.curr.state)
|
||||
.unwrap_or_default(),
|
||||
status: status.as_ref().and_then(|s| {
|
||||
s.curr.container.as_ref().and_then(|c| c.status.to_owned())
|
||||
}),
|
||||
image,
|
||||
server_id: deployment.config.server_id,
|
||||
build_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Build {
|
||||
type ListItem = BuildListItem;
|
||||
type Config = BuildConfig;
|
||||
type Info = BuildInfo;
|
||||
type QuerySpecifics = BuildQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"build"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Build
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Build> {
|
||||
&db_client().await.builds
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
build: Build,
|
||||
) -> anyhow::Result<BuildListItem> {
|
||||
Ok(BuildListItem {
|
||||
name: build.name,
|
||||
created_at: ObjectId::from_str(&build.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: build.id,
|
||||
tags: build.tags,
|
||||
resource_type: ResourceTargetVariant::Build,
|
||||
info: BuildListItemInfo {
|
||||
last_built_at: build.info.last_built_at,
|
||||
version: build.config.version,
|
||||
repo: build.config.repo,
|
||||
branch: build.config.branch,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Repo {
|
||||
type ListItem = RepoListItem;
|
||||
type Config = RepoConfig;
|
||||
type Info = RepoInfo;
|
||||
type QuerySpecifics = RepoQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"repo"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Repo
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Repo> {
|
||||
&db_client().await.repos
|
||||
}
|
||||
|
||||
async fn to_list_item(repo: Repo) -> anyhow::Result<RepoListItem> {
|
||||
Ok(RepoListItem {
|
||||
name: repo.name,
|
||||
created_at: ObjectId::from_str(&repo.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: repo.id,
|
||||
tags: repo.tags,
|
||||
resource_type: ResourceTargetVariant::Repo,
|
||||
info: RepoListItemInfo {
|
||||
last_pulled_at: repo.info.last_pulled_at,
|
||||
repo: repo.config.repo,
|
||||
branch: repo.config.branch,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Builder {
|
||||
type ListItem = BuilderListItem;
|
||||
type Config = BuilderConfig;
|
||||
type Info = ();
|
||||
type QuerySpecifics = BuilderQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"builder"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Builder
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Builder> {
|
||||
&db_client().await.builders
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
builder: Builder,
|
||||
) -> anyhow::Result<BuilderListItem> {
|
||||
let (builder_type, instance_type) = match builder.config {
|
||||
BuilderConfig::Server(config) => (
|
||||
BuilderConfigVariant::Server.to_string(),
|
||||
Some(config.server_id),
|
||||
),
|
||||
BuilderConfig::Aws(config) => (
|
||||
BuilderConfigVariant::Aws.to_string(),
|
||||
Some(config.instance_type),
|
||||
),
|
||||
};
|
||||
|
||||
Ok(BuilderListItem {
|
||||
name: builder.name,
|
||||
created_at: ObjectId::from_str(&builder.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: builder.id,
|
||||
tags: builder.tags,
|
||||
resource_type: ResourceTargetVariant::Builder,
|
||||
info: BuilderListItemInfo {
|
||||
builder_type,
|
||||
instance_type,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Alerter {
|
||||
type ListItem = AlerterListItem;
|
||||
type Config = AlerterConfig;
|
||||
type Info = AlerterInfo;
|
||||
type QuerySpecifics = AlerterQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"alerter"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Alerter
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Alerter> {
|
||||
&db_client().await.alerters
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
alerter: Alerter,
|
||||
) -> anyhow::Result<AlerterListItem> {
|
||||
let (alerter_type, enabled) = match alerter.config {
|
||||
AlerterConfig::Custom(config) => {
|
||||
(AlerterConfigVariant::Custom.to_string(), config.enabled)
|
||||
}
|
||||
AlerterConfig::Slack(config) => {
|
||||
(AlerterConfigVariant::Slack.to_string(), config.enabled)
|
||||
}
|
||||
};
|
||||
Ok(AlerterListItem {
|
||||
name: alerter.name,
|
||||
created_at: ObjectId::from_str(&alerter.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: alerter.id,
|
||||
tags: alerter.tags,
|
||||
resource_type: ResourceTargetVariant::Alerter,
|
||||
info: AlerterListItemInfo {
|
||||
alerter_type: alerter_type.to_string(),
|
||||
is_default: alerter.info.is_default,
|
||||
enabled,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for Procedure {
|
||||
type ListItem = ProcedureListItem;
|
||||
type Config = ProcedureConfig;
|
||||
type Info = ();
|
||||
type QuerySpecifics = ProcedureQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"procedure"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Procedure
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<Procedure> {
|
||||
&db_client().await.procedures
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
procedure: Procedure,
|
||||
) -> anyhow::Result<ProcedureListItem> {
|
||||
Ok(ProcedureListItem {
|
||||
name: procedure.name,
|
||||
created_at: ObjectId::from_str(&procedure.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: procedure.id,
|
||||
tags: procedure.tags,
|
||||
resource_type: ResourceTargetVariant::Procedure,
|
||||
info: ProcedureListItemInfo {
|
||||
procedure_type: procedure.config.procedure_type,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateResource for ServerTemplate {
|
||||
type ListItem = ServerTemplateListItem;
|
||||
type Config = ServerTemplateConfig;
|
||||
type Info = ();
|
||||
type QuerySpecifics = ServerTemplateQuerySpecifics;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"server_template"
|
||||
}
|
||||
|
||||
fn resource_target_variant() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Alerter
|
||||
}
|
||||
|
||||
async fn coll() -> &'static Collection<ServerTemplate> {
|
||||
&db_client().await.server_templates
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
server_template: ServerTemplate,
|
||||
) -> anyhow::Result<ServerTemplateListItem> {
|
||||
let (template_type, instance_type) = match server_template.config
|
||||
{
|
||||
ServerTemplateConfig::Aws(config) => (
|
||||
ServerTemplateConfigVariant::Aws.to_string(),
|
||||
Some(config.instance_type),
|
||||
),
|
||||
};
|
||||
Ok(ServerTemplateListItem {
|
||||
name: server_template.name,
|
||||
created_at: ObjectId::from_str(&server_template.id)?
|
||||
.timestamp()
|
||||
.timestamp_millis(),
|
||||
id: server_template.id,
|
||||
tags: server_template.tags,
|
||||
resource_type: ResourceTargetVariant::ServerTemplate,
|
||||
info: ServerTemplateListItemInfo {
|
||||
provider: template_type.to_string(),
|
||||
instance_type,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user_permission_on_resource(
|
||||
user_id: &str,
|
||||
resource_variant: ResourceTargetVariant,
|
||||
resource_id: &str,
|
||||
) -> anyhow::Result<PermissionLevel> {
|
||||
let permission = find_collect(
|
||||
&db_client().await.permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(user_id).await?,
|
||||
"resource_target.type": resource_variant.as_ref(),
|
||||
"resource_target.id": resource_id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for permissions")?
|
||||
.into_iter()
|
||||
// get the max permission user has between personal / any user groups
|
||||
.fold(PermissionLevel::None, |level, permission| {
|
||||
if permission.level > level {
|
||||
permission.level
|
||||
} else {
|
||||
level
|
||||
}
|
||||
});
|
||||
Ok(permission)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn delete_all_permissions_on_resource<T>(target: T)
|
||||
where
|
||||
T: Into<ResourceTarget> + std::fmt::Debug,
|
||||
{
|
||||
let target: ResourceTarget = target.into();
|
||||
let (variant, id) = target.extract_variant_id();
|
||||
if let Err(e) = db_client()
|
||||
.await
|
||||
.permissions
|
||||
.delete_many(
|
||||
doc! {
|
||||
"resource_target.type": variant.as_ref(),
|
||||
"resource_target.id": &id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("failed to delete_many permissions matching target {target:?} | {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_resource_ids_for_non_admin(
|
||||
user_id: &str,
|
||||
resource_type: ResourceTargetVariant,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let permissions = find_collect(
|
||||
&db_client().await.permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(user_id).await?,
|
||||
"resource_target.type": resource_type.as_ref(),
|
||||
"level": { "$in": ["Read", "Execute", "Write"] }
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to query permissions on db")?
|
||||
.into_iter()
|
||||
.map(|p| p.resource_target.extract_variant_id().1.to_string())
|
||||
// collect into hashset first to remove any duplicates
|
||||
.collect::<HashSet<_>>();
|
||||
Ok(permissions.into_iter().collect())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn validate_resource_query_tags<
|
||||
T: Default + std::fmt::Debug,
|
||||
>(
|
||||
query: &mut ResourceQuery<T>,
|
||||
) {
|
||||
let futures = query.tags.iter().map(|tag| get_tag(tag));
|
||||
let res = join_all(futures).await;
|
||||
query.tags = res.into_iter().flatten().map(|tag| tag.id).collect();
|
||||
}
|
||||
@@ -9,9 +9,12 @@ use monitor_client::entities::{
|
||||
resource::Resource,
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::User,
|
||||
Operation,
|
||||
MergePartial, Operation,
|
||||
};
|
||||
use mungos::mongodb::{
|
||||
bson::{oid::ObjectId, to_document, Document},
|
||||
Collection,
|
||||
};
|
||||
use mungos::mongodb::{bson::oid::ObjectId, Collection};
|
||||
|
||||
use crate::state::db_client;
|
||||
|
||||
@@ -103,13 +106,21 @@ impl super::MonitorResource for Alerter {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
_id: &str,
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_document(
|
||||
original: Resource<Self::Config, Self::Info>,
|
||||
config: Self::PartialConfig,
|
||||
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
|
||||
let config = original.config.merge_partial(config);
|
||||
to_document(&config)
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
_updated: &Self,
|
||||
_update: &mut Update,
|
||||
|
||||
@@ -96,7 +96,7 @@ impl super::MonitorResource for Build {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
_id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
|
||||
@@ -12,10 +12,10 @@ use monitor_client::entities::{
|
||||
server::Server,
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::User,
|
||||
Operation,
|
||||
MergePartial, Operation,
|
||||
};
|
||||
use mungos::mongodb::{
|
||||
bson::{doc, oid::ObjectId},
|
||||
bson::{doc, oid::ObjectId, to_document, Document},
|
||||
Collection,
|
||||
};
|
||||
|
||||
@@ -101,13 +101,21 @@ impl super::MonitorResource for Builder {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
_id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
validate_config(config, user).await
|
||||
}
|
||||
|
||||
fn update_document(
|
||||
original: Resource<Self::Config, Self::Info>,
|
||||
config: Self::PartialConfig,
|
||||
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
|
||||
let config = original.config.merge_partial(config);
|
||||
to_document(&config)
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
_updated: &Self,
|
||||
_update: &mut Update,
|
||||
|
||||
@@ -124,7 +124,7 @@ impl super::MonitorResource for Deployment {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
_id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
|
||||
@@ -22,6 +22,7 @@ use mungos::{
|
||||
Collection,
|
||||
},
|
||||
};
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
use resolver_api::Resolve;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serror::serialize_error_pretty;
|
||||
@@ -55,8 +56,9 @@ pub trait MonitorResource {
|
||||
+ Unpin
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ PartialDiff<Self::PartialConfig>
|
||||
+ 'static;
|
||||
type PartialConfig: Serialize + Into<Self::Config>;
|
||||
type PartialConfig: Into<Self::Config> + Serialize + MaybeNone;
|
||||
type Info: Send
|
||||
+ Sync
|
||||
+ Unpin
|
||||
@@ -107,11 +109,19 @@ pub trait MonitorResource {
|
||||
fn update_operation() -> Operation;
|
||||
|
||||
async fn validate_update_config(
|
||||
current: Resource<Self::Config, Self::Info>,
|
||||
id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Should be overridden for enum configs, eg Alerter, Builder, ...
|
||||
fn update_document(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
config: Self::PartialConfig,
|
||||
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
|
||||
to_document(&config)
|
||||
}
|
||||
|
||||
/// Run any required task after resource updated in database but
|
||||
/// before the request resolves.
|
||||
async fn post_update(
|
||||
@@ -339,11 +349,23 @@ pub async fn update<T: MonitorResource>(
|
||||
return Err(anyhow!("{} busy", T::resource_type()));
|
||||
}
|
||||
|
||||
T::validate_update_config(&resource.id, &mut config, user).await?;
|
||||
|
||||
// This minimizes the update against the existing config
|
||||
let config = resource.config.partial_diff(config);
|
||||
|
||||
if config.is_none() {
|
||||
return Err(anyhow!(
|
||||
"Partial update has no changes to database state"
|
||||
));
|
||||
}
|
||||
|
||||
let config_for_log = serde_json::to_string_pretty(&config)
|
||||
.context("failed to serialize config to json")?;
|
||||
|
||||
let id = resource.id.clone();
|
||||
|
||||
T::validate_update_config(resource, &mut config, user).await?;
|
||||
|
||||
let config_doc = to_document(&config)
|
||||
let config_doc = T::update_document(resource, config)
|
||||
.context("failed to serialize config to bson document")?;
|
||||
|
||||
update_one_by_id(
|
||||
@@ -361,11 +383,7 @@ pub async fn update<T: MonitorResource>(
|
||||
user,
|
||||
);
|
||||
|
||||
update.push_simple_log(
|
||||
"update config",
|
||||
serde_json::to_string_pretty(&config)
|
||||
.context("failed to serialize config to json")?,
|
||||
);
|
||||
update.push_simple_log("update config", config_for_log);
|
||||
|
||||
let updated = get::<T>(id_or_name).await?;
|
||||
|
||||
|
||||
@@ -97,11 +97,11 @@ impl super::MonitorResource for Procedure {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
original: Resource<Self::Config, Self::Info>,
|
||||
id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
validate_config(config, user, Some(&original.id)).await
|
||||
validate_config(config, user, Some(id)).await
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
|
||||
@@ -99,7 +99,7 @@ impl super::MonitorResource for Repo {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
_id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
|
||||
@@ -104,7 +104,7 @@ impl super::MonitorResource for Server {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Server,
|
||||
_id: &str,
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
|
||||
@@ -10,9 +10,12 @@ use monitor_client::entities::{
|
||||
},
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::User,
|
||||
Operation,
|
||||
MergePartial, Operation,
|
||||
};
|
||||
use mungos::mongodb::{
|
||||
bson::{oid::ObjectId, to_document, Document},
|
||||
Collection,
|
||||
};
|
||||
use mungos::mongodb::{bson::oid::ObjectId, Collection};
|
||||
|
||||
use crate::state::db_client;
|
||||
|
||||
@@ -73,7 +76,7 @@ impl super::MonitorResource for ServerTemplate {
|
||||
|
||||
async fn validate_create_config(
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -92,13 +95,21 @@ impl super::MonitorResource for ServerTemplate {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_original: Resource<Self::Config, Self::Info>,
|
||||
_id: &str,
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_document(
|
||||
original: Resource<Self::Config, Self::Info>,
|
||||
config: Self::PartialConfig,
|
||||
) -> Result<Document, mungos::mongodb::bson::ser::Error> {
|
||||
let config = original.config.merge_partial(config);
|
||||
to_document(&config)
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
_updated: &Self,
|
||||
_update: &mut Update,
|
||||
|
||||
@@ -7,8 +7,9 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use super::resource::{
|
||||
AddFilters, Resource, ResourceListItem, ResourceQuery,
|
||||
use super::{
|
||||
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
|
||||
MergePartial,
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
@@ -131,8 +132,9 @@ impl From<AlerterConfig> for PartialAlerterConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl AlerterConfig {
|
||||
pub fn merge_partial(
|
||||
impl MergePartial for AlerterConfig {
|
||||
type Partial = PartialAlerterConfig;
|
||||
fn merge_partial(
|
||||
self,
|
||||
partial: PartialAlerterConfig,
|
||||
) -> AlerterConfig {
|
||||
|
||||
@@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use super::resource::{
|
||||
AddFilters, Resource, ResourceListItem, ResourceQuery,
|
||||
use super::{
|
||||
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
|
||||
MergePartial,
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
@@ -120,8 +121,9 @@ impl From<BuilderConfig> for PartialBuilderConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl BuilderConfig {
|
||||
pub fn merge_partial(
|
||||
impl MergePartial for BuilderConfig {
|
||||
type Partial = PartialBuilderConfig;
|
||||
fn merge_partial(
|
||||
self,
|
||||
partial: PartialBuilderConfig,
|
||||
) -> BuilderConfig {
|
||||
|
||||
@@ -43,6 +43,11 @@ pub type _Serror = Serror;
|
||||
)]
|
||||
pub struct NoData {}
|
||||
|
||||
pub trait MergePartial: Sized {
|
||||
type Partial;
|
||||
fn merge_partial(self, partial: Self::Partial) -> Self;
|
||||
}
|
||||
|
||||
pub fn all_logs_success(logs: &[update::Log]) -> bool {
|
||||
for log in logs {
|
||||
if !log.success {
|
||||
|
||||
@@ -10,6 +10,7 @@ use typeshare::typeshare;
|
||||
use super::{
|
||||
builder::AwsBuilderConfig,
|
||||
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
|
||||
MergePartial,
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
@@ -112,8 +113,9 @@ impl From<ServerTemplateConfig> for PartialServerTemplateConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerTemplateConfig {
|
||||
pub fn merge_partial(
|
||||
impl MergePartial for ServerTemplateConfig {
|
||||
type Partial = PartialServerTemplateConfig;
|
||||
fn merge_partial(
|
||||
self,
|
||||
partial: PartialServerTemplateConfig,
|
||||
) -> ServerTemplateConfig {
|
||||
|
||||
Reference in New Issue
Block a user