forked from github-starred/komodo
implement sync deployment get updates for view with deploy action
This commit is contained in:
@@ -24,13 +24,10 @@ use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
formatting::{colored, Color},
|
||||
query::get_id_to_tags,
|
||||
sync::{
|
||||
colored,
|
||||
resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
},
|
||||
Color,
|
||||
sync::resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
},
|
||||
update::update_update,
|
||||
},
|
||||
|
||||
@@ -542,6 +542,8 @@ fn convert_resource<R: MonitorResource>(
|
||||
.filter_map(|t| tag_names.get(t).cloned())
|
||||
.collect(),
|
||||
description: resource.description,
|
||||
deploy: false,
|
||||
after: Default::default(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
@@ -33,7 +32,10 @@ use serror::serialize_error_pretty;
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::resource::{get_updates_for_view, AllResourcesById},
|
||||
sync::{
|
||||
deployment,
|
||||
resource::{get_updates_for_view, AllResourcesById},
|
||||
},
|
||||
},
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
@@ -127,7 +129,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
)
|
||||
.await
|
||||
.context("failed to get server updates")?,
|
||||
deployment_updates: get_updates_for_view::<Deployment>(
|
||||
deployment_updates: deployment::get_updates_for_view(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
|
||||
29
bin/core/src/helpers/formatting.rs
Normal file
29
bin/core/src/helpers/formatting.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
pub fn muted(content: &str) -> String {
|
||||
format!("<span class=\"text-muted-foreground\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn bold(content: &str) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn colored(content: &str, color: Color) -> String {
|
||||
format!("<span class=\"{color}\">{content}</span>")
|
||||
}
|
||||
|
||||
pub enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Color::Red => f.write_str("text-red-700 dark:text-red-400"),
|
||||
Color::Green => {
|
||||
f.write_str("text-green-700 dark:text-green-400")
|
||||
}
|
||||
Color::Blue => f.write_str("text-blue-700 dark:text-blue-400"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ pub mod prune;
|
||||
pub mod query;
|
||||
pub mod sync;
|
||||
pub mod update;
|
||||
pub mod formatting;
|
||||
|
||||
// pub mod resource;
|
||||
|
||||
|
||||
332
bin/core/src/helpers/sync/deployment.rs
Normal file
332
bin/core/src/helpers/sync/deployment.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor_client::entities::{
|
||||
deployment::{
|
||||
Deployment, DeploymentConfig, DeploymentState,
|
||||
PartialDeploymentConfig,
|
||||
},
|
||||
sync::SyncUpdate,
|
||||
tag::Tag,
|
||||
toml::ResourceToml,
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use partial_derive2::{Diff, FieldDiff, MaybeNone};
|
||||
|
||||
use crate::{
|
||||
helpers::formatting::{bold, colored, muted, Color},
|
||||
resource::MonitorResource,
|
||||
state::deployment_status_cache,
|
||||
};
|
||||
|
||||
use super::resource::{AllResourcesById, ResourceSync};
|
||||
|
||||
/// Turns all the diffs into a readable string
|
||||
pub async fn get_updates_for_view(
|
||||
resources: Vec<ResourceToml<PartialDeploymentConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<Option<SyncUpdate>> {
|
||||
let map = find_collect(Deployment::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get deployments from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut update = SyncUpdate {
|
||||
log: format!("{} Updates", Deployment::resource_type()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
update.to_delete += 1;
|
||||
to_delete.push(resource.name.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let status_cache = deployment_status_cache();
|
||||
|
||||
for mut resource in resources {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (to_deploy, state) = if resource.deploy {
|
||||
let state = status_cache
|
||||
.get_or_insert_default(&original.id)
|
||||
.await
|
||||
.curr
|
||||
.state;
|
||||
let to_deploy = match state {
|
||||
DeploymentState::Unknown => false,
|
||||
DeploymentState::Running => {
|
||||
// Needs to only check config fields that affect docker run
|
||||
diff.server_id.is_some()
|
||||
|| diff.image.is_some()
|
||||
|| diff.image_registry.is_some()
|
||||
|| diff.skip_secret_interp.is_some()
|
||||
|| diff.network.is_some()
|
||||
|| diff.restart.is_some()
|
||||
|| diff.command.is_some()
|
||||
|| diff.extra_args.is_some()
|
||||
|| diff.ports.is_some()
|
||||
|| diff.volumes.is_some()
|
||||
|| diff.environment.is_some()
|
||||
|| diff.labels.is_some()
|
||||
}
|
||||
// All other cases will require Deploy to enter Running state
|
||||
_ => true,
|
||||
};
|
||||
(to_deploy, state)
|
||||
} else {
|
||||
// The state in this case doesn't matter and won't be read (as long as it isn't 'Unknown' which will log in all cases)
|
||||
(false, DeploymentState::NotDeployed)
|
||||
};
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& !to_deploy
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
if state == DeploymentState::Unknown {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\nDeployment sync actions could not be computed due to Unknown deployment state\n-------------------",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
update.to_update += 1;
|
||||
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", Color::Blue),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
if resource.description != original.description {
|
||||
lines.push(format!(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&resource.description, Color::Green)
|
||||
));
|
||||
}
|
||||
if resource.tags != original_tags {
|
||||
let from =
|
||||
colored(&format!("{:?}", original_tags), Color::Red);
|
||||
let to =
|
||||
colored(&format!("{:?}", resource.tags), Color::Green);
|
||||
lines.push(format!(
|
||||
"{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
muted("to"),
|
||||
));
|
||||
}
|
||||
lines.extend(diff.iter_field_diffs().map(
|
||||
|FieldDiff { field, from, to }| {
|
||||
format!(
|
||||
"{}: '{field}'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&from, Color::Red),
|
||||
muted("to"),
|
||||
colored(&to, Color::Green)
|
||||
)
|
||||
},
|
||||
));
|
||||
if state == DeploymentState::Unknown {
|
||||
lines.push(format!(
|
||||
"{}: Deployment sync actions {} due to Unknown deployment state",
|
||||
colored("ERROR", Color::Red),
|
||||
bold("could not be computed")
|
||||
));
|
||||
} else if to_deploy {
|
||||
let mut line = format!(
|
||||
"{}: deployment is currently in {} state, {}",
|
||||
muted("deploy"),
|
||||
colored(&state.to_string(), Color::Red),
|
||||
bold("sync will trigger deploy")
|
||||
);
|
||||
if !resource.after.is_empty() {
|
||||
line.push_str(&format!(
|
||||
"\n{}: {:?}",
|
||||
muted("deploy after"),
|
||||
resource.after
|
||||
));
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
let mut lines = vec![
|
||||
format!(
|
||||
"{}: {}",
|
||||
muted("description"),
|
||||
resource.description,
|
||||
),
|
||||
format!("{}: {:?}", muted("tags"), resource.tags,),
|
||||
format!(
|
||||
"{}: {}",
|
||||
muted("config"),
|
||||
serde_json::to_string_pretty(&resource.config)
|
||||
.context("failed to serialize config to json")?
|
||||
),
|
||||
];
|
||||
if resource.deploy {
|
||||
lines.push(format!(
|
||||
"{}: {}",
|
||||
muted("will deploy"),
|
||||
colored("true", Color::Green)
|
||||
));
|
||||
if !resource.after.is_empty() {
|
||||
lines.push(format!(
|
||||
"{}: {:?}",
|
||||
muted("deploy after"),
|
||||
resource.after
|
||||
));
|
||||
}
|
||||
}
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: {}\n{}",
|
||||
colored("CREATE", Color::Green),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name),
|
||||
lines.join("\n")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
|
||||
let any_change = update.to_create > 0
|
||||
|| update.to_update > 0
|
||||
|| update.to_delete > 0;
|
||||
|
||||
Ok(any_change.then_some(update))
|
||||
}
|
||||
|
||||
// /// Gets all the resources to update. For use in sync execution.
|
||||
// pub async fn get_updates_for_execution<Resource: ResourceSync>(
|
||||
// resources: Vec<ResourceToml<Resource::PartialConfig>>,
|
||||
// delete: bool,
|
||||
// all_resources: &AllResourcesById,
|
||||
// id_to_tags: &HashMap<String, Tag>,
|
||||
// ) -> anyhow::Result<UpdatesResult<Resource::PartialConfig>> {
|
||||
// let map = find_collect(Resource::coll().await, None, None)
|
||||
// .await
|
||||
// .context("failed to get resources from db")?
|
||||
// .into_iter()
|
||||
// .map(|r| (r.name.clone(), r))
|
||||
// .collect::<HashMap<_, _>>();
|
||||
|
||||
// let mut to_create = ToCreate::<Resource::PartialConfig>::new();
|
||||
// let mut to_update = ToUpdate::<Resource::PartialConfig>::new();
|
||||
// let mut to_delete = ToDelete::new();
|
||||
|
||||
// if delete {
|
||||
// for resource in map.values() {
|
||||
// if !resources.iter().any(|r| r.name == resource.name) {
|
||||
// to_delete.push(resource.name.clone());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// for mut resource in resources {
|
||||
// match map.get(&resource.name) {
|
||||
// Some(original) => {
|
||||
// // First merge toml resource config (partial) onto default resource config.
|
||||
// // Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
// let config: Resource::Config = resource.config.into();
|
||||
// resource.config = config.into();
|
||||
|
||||
// Resource::validate_partial_config(&mut resource.config);
|
||||
|
||||
// let mut diff = Resource::get_diff(
|
||||
// original.config.clone(),
|
||||
// resource.config,
|
||||
// all_resources,
|
||||
// )?;
|
||||
|
||||
// Resource::validate_diff(&mut diff);
|
||||
|
||||
// let original_tags = original
|
||||
// .tags
|
||||
// .iter()
|
||||
// .filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// // Only proceed if there are any fields to update,
|
||||
// // or a change to tags / description
|
||||
// if diff.is_none()
|
||||
// && resource.description == original.description
|
||||
// && resource.tags == original_tags
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Minimizes updates through diffing.
|
||||
// resource.config = diff.into();
|
||||
|
||||
// let update = ToUpdateItem {
|
||||
// id: original.id.clone(),
|
||||
// update_description: resource.description
|
||||
// != original.description,
|
||||
// update_tags: resource.tags != original_tags,
|
||||
// resource,
|
||||
// };
|
||||
|
||||
// to_update.push(update);
|
||||
// }
|
||||
// None => to_create.push(resource),
|
||||
// }
|
||||
// }
|
||||
|
||||
// Ok((to_create, to_update, to_delete))
|
||||
// }
|
||||
@@ -4,7 +4,7 @@ use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{toml::ResourcesToml, update::Log};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use super::{colored, muted, Color};
|
||||
use crate::helpers::formatting::{colored, muted, Color};
|
||||
|
||||
pub fn read_resources(
|
||||
path: &Path,
|
||||
|
||||
@@ -7,6 +7,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
pub mod deployment;
|
||||
pub mod remote;
|
||||
pub mod resource;
|
||||
pub mod user_groups;
|
||||
@@ -41,33 +42,3 @@ pub fn spawn_sync_refresh_loop() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn muted(content: &str) -> String {
|
||||
format!("<span class=\"text-muted-foreground\">{content}</span>")
|
||||
}
|
||||
|
||||
fn bold(content: &str) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn colored(content: &str, color: Color) -> String {
|
||||
format!("<span class=\"{color}\">{content}</span>")
|
||||
}
|
||||
|
||||
pub enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Color::Red => f.write_str("text-red-700 dark:text-red-400"),
|
||||
Color::Green => {
|
||||
f.write_str("text-green-700 dark:text-green-400")
|
||||
}
|
||||
Color::Blue => f.write_str("text-blue-700 dark:text-blue-400"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,11 @@ use mungos::find::find_collect;
|
||||
use partial_derive2::{Diff, FieldDiff, MaybeNone};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource::MonitorResource, state::State};
|
||||
|
||||
use super::{bold, colored, muted, Color};
|
||||
use crate::{
|
||||
helpers::formatting::{bold, colored, muted, Color},
|
||||
resource::MonitorResource,
|
||||
state::State,
|
||||
};
|
||||
|
||||
pub type ToUpdate<T> = Vec<ToUpdateItem<T>>;
|
||||
pub type ToCreate<T> = Vec<ResourceToml<T>>;
|
||||
|
||||
@@ -17,13 +17,12 @@ use monitor_client::{
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
|
||||
use crate::{
|
||||
helpers::sync::{
|
||||
bold, colored, muted,
|
||||
resource::{
|
||||
helpers::{
|
||||
formatting::{bold, colored, muted, Color},
|
||||
sync::resource::{
|
||||
run_update_description, run_update_tags, ResourceSync,
|
||||
ToUpdateItem,
|
||||
},
|
||||
Color,
|
||||
},
|
||||
resource::MonitorResource,
|
||||
};
|
||||
|
||||
@@ -20,12 +20,13 @@ use monitor_client::{
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{
|
||||
bold, colored, muted, resource::AllResourcesById, Color,
|
||||
use crate::{
|
||||
helpers::formatting::{bold, colored, muted, Color},
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
use super::resource::AllResourcesById;
|
||||
|
||||
pub struct UpdateItem {
|
||||
user_group: UserGroupToml,
|
||||
update_users: bool,
|
||||
|
||||
@@ -14,9 +14,10 @@ use monitor_client::{
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted, Color};
|
||||
use crate::{
|
||||
helpers::formatting::{bold, colored, muted, Color},
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub variable: Variable,
|
||||
|
||||
@@ -97,19 +97,37 @@ pub struct ResourceToml<PartialConfig: Default> {
|
||||
/// The resource name. Required
|
||||
pub name: String,
|
||||
|
||||
/// The resource description.
|
||||
#[serde(default)]
|
||||
/// The resource description. Optional.
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub description: String,
|
||||
|
||||
/// Tag ids or names
|
||||
#[serde(default)]
|
||||
/// Tag ids or names. Optional
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// Resource specific configuration
|
||||
/// Optional. Only relevant for deployments.
|
||||
///
|
||||
/// Will ensure deployment is running with the latest configuration.
|
||||
/// Deploy actions to achieve this will be included in the sync.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub deploy: bool,
|
||||
|
||||
/// Optional. Only relevant for deployments using the 'deploy' sync feature.
|
||||
///
|
||||
/// Specify other deployments as dependencies.
|
||||
/// The sync will ensure the deployment will only be deployed 'after' its dependencies.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub after: Vec<String>,
|
||||
|
||||
/// Resource specific configuration.
|
||||
#[serde(default)]
|
||||
pub config: PartialConfig,
|
||||
}
|
||||
|
||||
fn is_false(b: &bool) -> bool {
|
||||
!b
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserGroupToml {
|
||||
/// User group name
|
||||
|
||||
Reference in New Issue
Block a user