mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-11 17:44:19 -05:00
finish sync backend?
This commit is contained in:
@@ -238,6 +238,7 @@ pub async fn get_updates(
|
||||
"adding".dimmed()
|
||||
))
|
||||
}
|
||||
println!("{}", lines.join("\n-------------------\n"));
|
||||
to_update.push(UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
@@ -248,7 +249,7 @@ pub async fn get_updates(
|
||||
|
||||
for d in &to_delete {
|
||||
println!(
|
||||
"\n{}: variable: '{}'\n-------------------",
|
||||
"\n{}: user group: '{}'\n-------------------",
|
||||
"DELETE".red(),
|
||||
d.name.bold(),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::execute::RunSync,
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
entities::{
|
||||
self,
|
||||
alerter::Alerter,
|
||||
@@ -15,17 +15,21 @@ use monitor_client::{
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
update::{Log, Update},
|
||||
user::User,
|
||||
user::{sync_user, User},
|
||||
},
|
||||
};
|
||||
use mungos::by_id::update_one_by_id;
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
sync::{
|
||||
colored,
|
||||
resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
},
|
||||
},
|
||||
update::update_update,
|
||||
},
|
||||
@@ -130,7 +134,6 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (
|
||||
resource_syncs_to_create,
|
||||
resource_syncs_to_update,
|
||||
@@ -142,10 +145,90 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
variables_to_create,
|
||||
variables_to_update,
|
||||
variables_to_delete,
|
||||
) = crate::helpers::sync::variables::get_updates_for_execution(
|
||||
resources.variables,
|
||||
sync.config.delete,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
user_groups_to_create,
|
||||
user_groups_to_update,
|
||||
user_groups_to_delete,
|
||||
) = crate::helpers::sync::user_groups::get_updates_for_execution(
|
||||
resources.user_groups,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if resource_syncs_to_create.is_empty()
|
||||
&& resource_syncs_to_update.is_empty()
|
||||
&& resource_syncs_to_delete.is_empty()
|
||||
&& server_templates_to_create.is_empty()
|
||||
&& server_templates_to_update.is_empty()
|
||||
&& server_templates_to_delete.is_empty()
|
||||
&& servers_to_create.is_empty()
|
||||
&& servers_to_update.is_empty()
|
||||
&& servers_to_delete.is_empty()
|
||||
&& deployments_to_create.is_empty()
|
||||
&& deployments_to_update.is_empty()
|
||||
&& deployments_to_delete.is_empty()
|
||||
&& builds_to_create.is_empty()
|
||||
&& builds_to_update.is_empty()
|
||||
&& builds_to_delete.is_empty()
|
||||
&& builders_to_create.is_empty()
|
||||
&& builders_to_update.is_empty()
|
||||
&& builders_to_delete.is_empty()
|
||||
&& alerters_to_create.is_empty()
|
||||
&& alerters_to_update.is_empty()
|
||||
&& alerters_to_delete.is_empty()
|
||||
&& repos_to_create.is_empty()
|
||||
&& repos_to_update.is_empty()
|
||||
&& repos_to_delete.is_empty()
|
||||
&& procedures_to_create.is_empty()
|
||||
&& procedures_to_update.is_empty()
|
||||
&& procedures_to_delete.is_empty()
|
||||
&& user_groups_to_create.is_empty()
|
||||
&& user_groups_to_update.is_empty()
|
||||
&& user_groups_to_delete.is_empty()
|
||||
&& variables_to_create.is_empty()
|
||||
&& variables_to_update.is_empty()
|
||||
&& variables_to_delete.is_empty()
|
||||
{
|
||||
update.push_simple_log(
|
||||
"No Changes",
|
||||
format!("{}. exiting.", colored("nothing to do", "green")),
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
// =================
|
||||
|
||||
// No deps
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
crate::helpers::sync::variables::run_updates(
|
||||
variables_to_create,
|
||||
variables_to_update,
|
||||
variables_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
crate::helpers::sync::user_groups::run_updates(
|
||||
user_groups_to_create,
|
||||
user_groups_to_update,
|
||||
user_groups_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
entities::sync::ResourceSync::run_updates(
|
||||
@@ -256,6 +339,23 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
)
|
||||
}
|
||||
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
RefreshResourceSyncPending { sync: sync.id },
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("failed to refresh sync {} after run | {e:#}", sync.name);
|
||||
update.push_error_log(
|
||||
"refresh sync",
|
||||
format!(
|
||||
"failed to refresh sync pending after run | {}",
|
||||
serialize_error_pretty(&e)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
|
||||
@@ -183,6 +183,21 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
)
|
||||
.await
|
||||
.context("failed to get resource sync updates")?,
|
||||
variable_updates:
|
||||
crate::helpers::sync::variables::get_updates_for_view(
|
||||
resources.variables,
|
||||
sync.config.delete,
|
||||
)
|
||||
.await
|
||||
.context("failed to get variable updates")?,
|
||||
user_group_updates:
|
||||
crate::helpers::sync::user_groups::get_updates_for_view(
|
||||
resources.user_groups,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
)
|
||||
.await
|
||||
.context("failed to get user group updates")?,
|
||||
};
|
||||
|
||||
let pending = to_document(&pending)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod remote;
|
||||
pub mod resource;
|
||||
pub mod user_groups;
|
||||
pub mod variables;
|
||||
|
||||
mod file;
|
||||
mod resources;
|
||||
@@ -12,6 +14,6 @@ fn bold(content: &str) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
fn colored(content: &str, color: &str) -> String {
|
||||
pub fn colored(content: &str, color: &str) -> String {
|
||||
format!("<span class=\"text-{color}-500\">{content}</span>")
|
||||
}
|
||||
|
||||
@@ -80,9 +80,8 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
{
|
||||
Ok(resource) => resource.id,
|
||||
Err(e) => {
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: failed to create {} '{}' | {e:#}",
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
@@ -136,7 +135,6 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
}
|
||||
|
||||
if !resource.config.is_none() {
|
||||
log.push('\n');
|
||||
if let Err(e) = crate::resource::update::<Self>(
|
||||
&id,
|
||||
resource.config,
|
||||
@@ -145,14 +143,14 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"{}: failed to update config on {} '{}' | {e:#}",
|
||||
"\n{}: failed to update config on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}' configuration",
|
||||
"\n{}: {} {} '{}' configuration",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
Self::resource_type(),
|
||||
@@ -163,19 +161,18 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
}
|
||||
|
||||
for resource in to_delete {
|
||||
log.push('\n');
|
||||
if let Err(e) =
|
||||
crate::resource::delete::<Self>(&resource, sync_user()).await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"{}: failed to delete {} '{}' | {e:#}",
|
||||
"\n{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&resource),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}'",
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
Self::resource_type(),
|
||||
@@ -251,7 +248,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
any_change = true;
|
||||
|
||||
log.push_str(&format!(
|
||||
"\n{}: {}: '{}'\n-------------------",
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name)
|
||||
@@ -296,7 +293,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
None => {
|
||||
any_change = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
|
||||
"\n\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name),
|
||||
@@ -314,7 +311,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
|
||||
for name in to_delete {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {}: '{}'\n-------------------",
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
Resource::resource_type(),
|
||||
bold(&name)
|
||||
@@ -379,47 +376,6 @@ pub async fn get_updates_for_execution<Resource: ResourceSync>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// println!(
|
||||
// "\n{}: {}: '{}'\n-------------------",
|
||||
// "UPDATE".blue(),
|
||||
// Resource::display(),
|
||||
// resource.name.bold(),
|
||||
// );
|
||||
// let mut lines = Vec::<String>::new();
|
||||
// if resource.description != original.description {
|
||||
// lines.push(format!(
|
||||
// "{}: 'description'\n{}: {}\n{}: {}",
|
||||
// "field".dimmed(),
|
||||
// "from".dimmed(),
|
||||
// original.description.red(),
|
||||
// "to".dimmed(),
|
||||
// resource.description.green()
|
||||
// ))
|
||||
// }
|
||||
// if resource.tags != original_tags {
|
||||
// let from = format!("{:?}", original_tags).red();
|
||||
// let to = format!("{:?}", resource.tags).green();
|
||||
// lines.push(format!(
|
||||
// "{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
// "field".dimmed(),
|
||||
// "from".dimmed(),
|
||||
// "to".dimmed(),
|
||||
// ));
|
||||
// }
|
||||
// lines.extend(diff.iter_field_diffs().map(
|
||||
// |FieldDiff { field, from, to }| {
|
||||
// format!(
|
||||
// "{}: '{field}'\n{}: {}\n{}: {}",
|
||||
// "field".dimmed(),
|
||||
// "from".dimmed(),
|
||||
// from.red(),
|
||||
// "to".dimmed(),
|
||||
// to.green()
|
||||
// )
|
||||
// },
|
||||
// ));
|
||||
// println!("{}", lines.join("\n-------------------\n"));
|
||||
|
||||
// Minimizes updates through diffing.
|
||||
resource.config = diff.into();
|
||||
|
||||
@@ -433,21 +389,7 @@ pub async fn get_updates_for_execution<Resource: ResourceSync>(
|
||||
|
||||
to_update.push(update);
|
||||
}
|
||||
None => {
|
||||
// println!(
|
||||
// "\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
|
||||
// "CREATE".green(),
|
||||
// Resource::display(),
|
||||
// resource.name.bold().green(),
|
||||
// "description".dimmed(),
|
||||
// resource.description,
|
||||
// "tags".dimmed(),
|
||||
// resource.tags,
|
||||
// "config".dimmed(),
|
||||
// serde_json::to_string_pretty(&resource.config)?
|
||||
// );
|
||||
to_create.push(resource);
|
||||
}
|
||||
None => to_create.push(resource),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +403,6 @@ pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
log: &mut String,
|
||||
) {
|
||||
// Update tags
|
||||
log.push('\n');
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateTagsOnResource {
|
||||
@@ -473,14 +414,14 @@ pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"{}: failed to update tags on {} '{}' | {e:#}",
|
||||
"\n{}: failed to update tags on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}' tags",
|
||||
"\n{}: {} {} '{}' tags",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
Resource::resource_type(),
|
||||
@@ -495,7 +436,6 @@ pub async fn run_update_description<Resource: ResourceSync>(
|
||||
description: String,
|
||||
log: &mut String,
|
||||
) {
|
||||
log.push('\n');
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateDescription {
|
||||
@@ -507,14 +447,14 @@ pub async fn run_update_description<Resource: ResourceSync>(
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"{}: failed to update description on {} '{}' | {e:#}",
|
||||
"\n{}: failed to update description on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}' description",
|
||||
"\n{}: {} {} '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
Resource::resource_type(),
|
||||
|
||||
630
bin/core/src/helpers/sync/user_groups.rs
Normal file
630
bin/core/src/helpers/sync/user_groups.rs
Normal file
@@ -0,0 +1,630 @@
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::{
|
||||
read::ListUserTargetPermissions,
|
||||
write::{
|
||||
CreateUserGroup, DeleteUserGroup, SetUsersInUserGroup,
|
||||
UpdatePermissionOnTarget,
|
||||
},
|
||||
},
|
||||
entities::{
|
||||
permission::UserTarget,
|
||||
toml::{PermissionToml, UserGroupToml},
|
||||
update::{Log, ResourceTarget},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted, resource::AllResourcesById};
|
||||
|
||||
pub struct UpdateItem {
|
||||
user_group: UserGroupToml,
|
||||
update_users: bool,
|
||||
update_permissions: bool,
|
||||
}
|
||||
|
||||
pub struct DeleteItem {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_view(
|
||||
user_groups: Vec<UserGroupToml>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
let map = find_collect(&db_client().await.user_groups, None, None)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.into_iter()
|
||||
.map(|ug| (ug.name.clone(), ug))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
|
||||
if delete {
|
||||
for user_group in map.values() {
|
||||
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
|
||||
to_delete.push(user_group.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut log = String::from("User Group Updates");
|
||||
|
||||
if user_groups.is_empty() {
|
||||
if to_delete.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
for name in &to_delete {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
return Ok(Some(log));
|
||||
}
|
||||
|
||||
let id_to_user = find_collect(&db_client().await.users, None, None)
|
||||
.await
|
||||
.context("failed to query db for Users")?
|
||||
.into_iter()
|
||||
.map(|user| (user.id.clone(), user))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for mut user_group in user_groups {
|
||||
let original = match map.get(&user_group.name).cloned() {
|
||||
Some(original) => original,
|
||||
None => {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: user group: {}\n{}: {:?}\n{}: {:?}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&user_group.name, "green"),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("permissions"),
|
||||
user_group.permissions,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut original_users = original
|
||||
.users
|
||||
.into_iter()
|
||||
.filter_map(|user_id| {
|
||||
id_to_user.get(&user_id).map(|u| u.username.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut original_permissions = State
|
||||
.resolve(
|
||||
ListUserTargetPermissions {
|
||||
user_target: UserTarget::UserGroup(original.id),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query for existing UserGroup permissions")?
|
||||
.into_iter()
|
||||
.map(|mut p| {
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
ResourceTarget::System(_) => {}
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = all_resources
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all_resources
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all_resources
|
||||
.deployments
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
*id = all_resources
|
||||
.servers
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = all_resources
|
||||
.repos
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = all_resources
|
||||
.alerters
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
*id = all_resources
|
||||
.procedures
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = all_resources
|
||||
.templates
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
*id = all_resources
|
||||
.syncs
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
PermissionToml {
|
||||
target: p.resource_target,
|
||||
level: p.level,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
original_users.sort();
|
||||
user_group.users.sort();
|
||||
|
||||
user_group.permissions.sort_by(sort_permissions);
|
||||
original_permissions.sort_by(sort_permissions);
|
||||
|
||||
let update_users = user_group.users != original_users;
|
||||
let update_permissions =
|
||||
user_group.permissions != original_permissions;
|
||||
|
||||
// only push update after failed diff
|
||||
if update_users || update_permissions {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
bold(&user_group.name),
|
||||
));
|
||||
let mut lines = Vec::<String>::new();
|
||||
if update_users {
|
||||
let adding = user_group
|
||||
.users
|
||||
.iter()
|
||||
.filter(|user| !original_users.contains(user))
|
||||
.map(|user| user.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
};
|
||||
let removing = original_users
|
||||
.iter()
|
||||
.filter(|user| !user_group.users.contains(user))
|
||||
.map(|user| user.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'users'\n{}: {removing}\n{}: {adding}",
|
||||
muted("field"),
|
||||
muted("removing"),
|
||||
muted("adding"),
|
||||
))
|
||||
}
|
||||
if update_permissions {
|
||||
let adding = user_group
|
||||
.permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!original_permissions.contains(permission)
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
};
|
||||
let removing = original_permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!user_group.permissions.contains(permission)
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'permissions'\n{}: {removing}\n{}: {adding}",
|
||||
muted("field"),
|
||||
muted("removing"),
|
||||
muted("adding"),
|
||||
))
|
||||
}
|
||||
log.push('\n');
|
||||
log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
}
|
||||
|
||||
for name in &to_delete {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Some(log))
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_execution(
|
||||
user_groups: Vec<UserGroupToml>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<(
|
||||
Vec<UserGroupToml>,
|
||||
Vec<UpdateItem>,
|
||||
Vec<DeleteItem>,
|
||||
)> {
|
||||
let map = find_collect(&db_client().await.user_groups, None, None)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.into_iter()
|
||||
.map(|ug| (ug.name.clone(), ug))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = Vec::<UserGroupToml>::new();
|
||||
let mut to_update = Vec::<UpdateItem>::new();
|
||||
let mut to_delete = Vec::<DeleteItem>::new();
|
||||
|
||||
if delete {
|
||||
for user_group in map.values() {
|
||||
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
|
||||
to_delete.push(DeleteItem {
|
||||
id: user_group.id.clone(),
|
||||
name: user_group.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user_groups.is_empty() {
|
||||
return Ok((to_create, to_update, to_delete));
|
||||
}
|
||||
|
||||
let id_to_user = find_collect(&db_client().await.users, None, None)
|
||||
.await
|
||||
.context("failed to query db for Users")?
|
||||
.into_iter()
|
||||
.map(|user| (user.id.clone(), user))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for mut user_group in user_groups {
|
||||
let original = match map.get(&user_group.name).cloned() {
|
||||
Some(original) => original,
|
||||
None => {
|
||||
to_create.push(user_group);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut original_users = original
|
||||
.users
|
||||
.into_iter()
|
||||
.filter_map(|user_id| {
|
||||
id_to_user.get(&user_id).map(|u| u.username.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut original_permissions = State
|
||||
.resolve(
|
||||
ListUserTargetPermissions {
|
||||
user_target: UserTarget::UserGroup(original.id),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query for existing UserGroup permissions")?
|
||||
.into_iter()
|
||||
.map(|mut p| {
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
ResourceTarget::System(_) => {}
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = all_resources
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all_resources
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all_resources
|
||||
.deployments
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
*id = all_resources
|
||||
.servers
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = all_resources
|
||||
.repos
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = all_resources
|
||||
.alerters
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
*id = all_resources
|
||||
.procedures
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = all_resources
|
||||
.templates
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
*id = all_resources
|
||||
.syncs
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
PermissionToml {
|
||||
target: p.resource_target,
|
||||
level: p.level,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
original_users.sort();
|
||||
user_group.users.sort();
|
||||
|
||||
user_group.permissions.sort_by(sort_permissions);
|
||||
original_permissions.sort_by(sort_permissions);
|
||||
|
||||
let update_users = user_group.users != original_users;
|
||||
let update_permissions =
|
||||
user_group.permissions != original_permissions;
|
||||
|
||||
// only push update after failed diff
|
||||
if update_users || update_permissions {
|
||||
to_update.push(UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
update_permissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
/// order permissions in deterministic way
|
||||
fn sort_permissions(
|
||||
a: &PermissionToml,
|
||||
b: &PermissionToml,
|
||||
) -> Ordering {
|
||||
let (a_t, a_id) = a.target.extract_variant_id();
|
||||
let (b_t, b_id) = b.target.extract_variant_id();
|
||||
match (a_t.cmp(&b_t), a_id.cmp(b_id)) {
|
||||
(Ordering::Greater, _) => Ordering::Greater,
|
||||
(Ordering::Less, _) => Ordering::Less,
|
||||
(_, Ordering::Greater) => Ordering::Greater,
|
||||
(_, Ordering::Less) => Ordering::Less,
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_updates(
|
||||
to_create: Vec<UserGroupToml>,
|
||||
to_update: Vec<UpdateItem>,
|
||||
to_delete: Vec<DeleteItem>,
|
||||
) -> Option<Log> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut log = String::from("running updates on UserGroups");
|
||||
|
||||
// Create the non-existant user groups
|
||||
for user_group in to_create {
|
||||
// Create the user group
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
CreateUserGroup {
|
||||
name: user_group.name.clone(),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group.name)
|
||||
));
|
||||
continue;
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
};
|
||||
|
||||
set_users(user_group.name.clone(), user_group.users, &mut log)
|
||||
.await;
|
||||
run_update_permissions(
|
||||
user_group.name,
|
||||
user_group.permissions,
|
||||
&mut log,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Update the existing user groups
|
||||
for UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
update_permissions,
|
||||
} in to_update
|
||||
{
|
||||
if update_users {
|
||||
set_users(user_group.name.clone(), user_group.users, &mut log)
|
||||
.await;
|
||||
}
|
||||
if update_permissions {
|
||||
run_update_permissions(
|
||||
user_group.name,
|
||||
user_group.permissions,
|
||||
&mut log,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
for user_group in to_delete {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
DeleteUserGroup { id: user_group.id },
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Some(Log::simple("Update UserGroups", log))
|
||||
}
|
||||
|
||||
async fn set_users(
|
||||
user_group: String,
|
||||
users: Vec<String>,
|
||||
log: &mut String,
|
||||
) {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
SetUsersInUserGroup {
|
||||
user_group: user_group.clone(),
|
||||
users,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set users in group {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' users",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_update_permissions(
|
||||
user_group: String,
|
||||
permissions: Vec<PermissionToml>,
|
||||
log: &mut String,
|
||||
) {
|
||||
for PermissionToml { target, level } in permissions {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdatePermissionOnTarget {
|
||||
user_target: UserTarget::UserGroup(user_group.clone()),
|
||||
resource_target: target.clone(),
|
||||
permission: level,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set permssion in group {} | target: {target:?} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' permissions",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
301
bin/core/src/helpers/sync/variables.rs
Normal file
301
bin/core/src/helpers/sync/variables.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
CreateVariable, DeleteVariable, UpdateVariableDescription,
|
||||
UpdateVariableValue,
|
||||
},
|
||||
entities::{update::Log, user::sync_user, variable::Variable},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub variable: Variable,
|
||||
pub update_value: bool,
|
||||
pub update_description: bool,
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_view(
|
||||
variables: Vec<Variable>,
|
||||
delete: bool,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
let map = find_collect(&db_client().await.variables, None, None)
|
||||
.await
|
||||
.context("failed to query db for variables")?
|
||||
.into_iter()
|
||||
.map(|v| (v.name.clone(), v))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
|
||||
if delete {
|
||||
for variable in map.values() {
|
||||
if !variables.iter().any(|v| v.name == variable.name) {
|
||||
to_delete.push(variable.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut log = String::from("Variable Updates");
|
||||
|
||||
if variables.is_empty() {
|
||||
if to_delete.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
for name in &to_delete {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
return Ok(Some(log));
|
||||
}
|
||||
|
||||
for variable in variables {
|
||||
match map.get(&variable.name) {
|
||||
Some(original) => {
|
||||
let item = ToUpdateItem {
|
||||
update_value: original.value != variable.value,
|
||||
update_description: original.description
|
||||
!= variable.description,
|
||||
variable,
|
||||
};
|
||||
if !item.update_value && !item.update_description {
|
||||
continue;
|
||||
}
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
bold(&item.variable.name),
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
|
||||
if item.update_value {
|
||||
lines.push(format!(
|
||||
"{}: 'value'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.value, "red"),
|
||||
muted("to"),
|
||||
colored(&item.variable.value, "green")
|
||||
))
|
||||
}
|
||||
|
||||
if item.update_description {
|
||||
lines.push(format!(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
muted("to"),
|
||||
colored(&item.variable.description, "green")
|
||||
))
|
||||
}
|
||||
|
||||
log.push('\n');
|
||||
log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
None => {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&variable.name, "green"),
|
||||
muted("description"),
|
||||
variable.description,
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in &to_delete {
|
||||
log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Some(log))
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_execution(
|
||||
variables: Vec<Variable>,
|
||||
delete: bool,
|
||||
) -> anyhow::Result<(Vec<Variable>, Vec<ToUpdateItem>, Vec<String>)> {
|
||||
let map = find_collect(&db_client().await.variables, None, None)
|
||||
.await
|
||||
.context("failed to query db for variables")?
|
||||
.into_iter()
|
||||
.map(|v| (v.name.clone(), v))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = Vec::<Variable>::new();
|
||||
let mut to_update = Vec::<ToUpdateItem>::new();
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
|
||||
if delete {
|
||||
for variable in map.values() {
|
||||
if !variables.iter().any(|v| v.name == variable.name) {
|
||||
to_delete.push(variable.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for variable in variables {
|
||||
match map.get(&variable.name) {
|
||||
Some(original) => {
|
||||
let item = ToUpdateItem {
|
||||
update_value: original.value != variable.value,
|
||||
update_description: original.description
|
||||
!= variable.description,
|
||||
variable,
|
||||
};
|
||||
if !item.update_value && !item.update_description {
|
||||
continue;
|
||||
}
|
||||
|
||||
to_update.push(item);
|
||||
}
|
||||
None => to_create.push(variable),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
pub async fn run_updates(
|
||||
to_create: Vec<Variable>,
|
||||
to_update: Vec<ToUpdateItem>,
|
||||
to_delete: Vec<String>,
|
||||
) -> Option<Log> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut log = String::from("running updates on Variables");
|
||||
|
||||
for variable in to_create {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
CreateVariable {
|
||||
name: variable.name.clone(),
|
||||
value: variable.value,
|
||||
description: variable.description,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable.name)
|
||||
));
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
for ToUpdateItem {
|
||||
variable,
|
||||
update_value,
|
||||
update_description,
|
||||
} in to_update
|
||||
{
|
||||
if update_value {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateVariableValue {
|
||||
name: variable.name.clone(),
|
||||
value: variable.value,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable value for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' value",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
}
|
||||
if update_description {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateVariableDescription {
|
||||
name: variable.name.clone(),
|
||||
description: variable.description,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable description for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for variable in to_delete {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
DeleteVariable {
|
||||
name: variable.clone(),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
bold(&variable)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Some(Log::simple("Update Variables", log))
|
||||
}
|
||||
@@ -90,6 +90,10 @@ pub struct PendingUpdates {
|
||||
pub server_template_updates: Option<String>,
|
||||
/// Readable log of any pending resource sync updates
|
||||
pub resource_sync_updates: Option<String>,
|
||||
/// Readable log of any pending variable updates
|
||||
pub variable_updates: Option<String>,
|
||||
/// Readable log of any pending user group updates
|
||||
pub user_group_updates: Option<String>,
|
||||
}
|
||||
|
||||
#[typeshare(serialized_as = "Partial<ResourceSyncConfig>")]
|
||||
|
||||
Reference in New Issue
Block a user