finish sync backend?

This commit is contained in:
mbecker20
2024-06-07 19:00:03 -07:00
parent 42c486807c
commit 45eafd10b9
8 changed files with 1073 additions and 80 deletions

View File

@@ -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(),
);

View File

@@ -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?;

View File

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

View File

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

View File

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

View 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)
))
}
}
}

View 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))
}

View File

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