Skip to content

Commit

Permalink
Implement config.json migration mechanism
Browse files Browse the repository at this point in the history
Signed-off-by: Christina Ying Wang <[email protected]>
  • Loading branch information
cywang117 committed Aug 24, 2023
1 parent 8840e5a commit 4258fa4
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 6 deletions.
27 changes: 21 additions & 6 deletions src/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::config_json::{
get_api_endpoint, get_root_certificate, merge_config_json, read_config_json, write_config_json,
ConfigMap,
};
use crate::migrate::generate_config_json_migration;
use crate::remote::{config_url, fetch_configuration, RemoteConfiguration};
use crate::schema::{read_os_config_schema, OsConfigSchema};
use crate::systemd;
Expand Down Expand Up @@ -45,9 +46,12 @@ pub fn reconfigure(args: &Args, config_json: &ConfigMap, joining: bool) -> Resul
!joining,
)?;

let has_config_changes = has_config_changes(&schema, &remote_config)?;
let has_service_config_changes = has_service_config_changes(&schema, &remote_config)?;

if !has_config_changes {
let migrated_config_json =
generate_config_json_migration(&schema, &remote_config.config, &config_json.clone())?;

if !has_service_config_changes && migrated_config_json == *config_json {
info!("No configuration changes");

if !joining {
Expand All @@ -66,8 +70,13 @@ pub fn reconfigure(args: &Args, config_json: &ConfigMap, joining: bool) -> Resul
config_json,
&schema,
&remote_config,
has_config_changes,
has_service_config_changes,
joining,
if migrated_config_json == *config_json {
None
} else {
Some(migrated_config_json)
},
);

if args.supervisor_exists {
Expand All @@ -82,21 +91,27 @@ fn reconfigure_core(
config_json: &ConfigMap,
schema: &OsConfigSchema,
remote_config: &RemoteConfiguration,
has_config_changes: bool,
has_service_config_changes: bool,
joining: bool,
migrated_config_json: Option<ConfigMap>,
) -> Result<()> {
if joining {
write_config_json(&args.config_json_path, config_json)?;
}

if has_config_changes {
if migrated_config_json.is_some() {
info!("Migrating config.json...");
write_config_json(&args.config_json_path, &migrated_config_json.unwrap())?;
}

if has_service_config_changes {
configure_services(schema, remote_config)?;
}

Ok(())
}

fn has_config_changes(
fn has_service_config_changes(
schema: &OsConfigSchema,
remote_config: &RemoteConfiguration,
) -> Result<bool> {
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mod generate;
mod join;
mod leave;
mod logger;
mod migrate;
mod random;
mod remote;
mod schema;
Expand Down
141 changes: 141 additions & 0 deletions src/migrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Config.json migration module
//
// Provides methods for migrating config.json fields based on remote directives
// from /os/vX/config. Limits migrated fields based on os-config.json schema
// whitelist.

use crate::config_json::ConfigMap;
use crate::remote::ConfigMigrationInstructions;
use crate::schema::OsConfigSchema;
use anyhow::Result;

pub fn generate_config_json_migration(
schema: &OsConfigSchema,
migration_config: &ConfigMigrationInstructions,
config_json: &ConfigMap,
) -> Result<ConfigMap> {
info!("Checking for config.json migrations...");

let mut new_config = config_json
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<ConfigMap>();

let keys_to_update = migration_config.update.keys().cloned().collect::<Vec<_>>();

for key in keys_to_update.iter().chain(migration_config.delete.iter()) {
if !schema.config.whitelist.contains(key) {
info!("Key `{}` not in whitelist, skipping", key);
continue;
}

// Key is marked for deletion
if migration_config.delete.contains(key) {
if config_json.contains_key(key) {
info!("Key `{}` found, will delete", key);
new_config.remove(key);
} else {
info!("Key `{}` not found for deletion, skipping", key);
}
} else if migration_config.update.contains_key(key) {
// Key is marked for update
// If key is in both delete & update, delete takes precedence.
if let Some(future) = migration_config.update.get(key) {
if !config_json.contains_key(key) {
info!("Key `{}` not found, will insert", key);
new_config.insert(key.to_string(), future.clone());
} else if let Some(current) = config_json.get(key) {
if current != future {
info!(
"Key `{}` found with current value `{}`, will update to `{}`",
key, current, future
);
new_config.insert(key.to_string(), future.clone());
} else {
info!(
"Key `{}` found with current value `{}` equal to update value `{}`, skipping",
key, current, future
);
}
}
}
}
}

Ok(new_config)
}

mod tests {
#[test]
fn test_generate_config_json_migration() {
let config_json = format!(
r#"
{{
"deadbeef": 1,
"deadca1f": "2",
"deadca2f": true,
"deadca3f": "string1",
"deadca5f": "to_delete",
"on_both_lists": "on_both_lists"
}}
"#
);

let schema = format!(
r#"
{{
"services": [
],
"config": {{
"whitelist": [
"deadbeef",
"deadca1f",
"deadca2f",
"deadca3f",
"deadca4f",
"deadca5f",
"on_both_lists"
],
"leave": []
}}
}}
"#
);

let configuration = unindent::unindent(
r#"
{
"update": {
"deadbeef": 2,
"deadca1f": "3",
"deadca2f": false,
"deadca3f": "string0",
"deadca4f": "new_field",
"not_on_whitelist1": "not_on_whitelist",
"on_both_lists": "on_both_lists2"
},
"delete": ["deadca5f", "not_on_whitelist2", "on_both_lists"]
}
"#,
);

let old_config = serde_json::from_str::<super::ConfigMap>(&config_json).unwrap();

let new_config = super::generate_config_json_migration(
&serde_json::from_str(&schema).unwrap(),
&serde_json::from_str(&configuration).unwrap(),
&old_config,
)
.unwrap();

assert_eq!(new_config.get("deadbeef").unwrap(), 2);
assert_eq!(new_config.get("deadca1f").unwrap(), "3");
assert_eq!(new_config.get("deadca2f").unwrap(), false);
assert_eq!(new_config.get("deadca3f").unwrap(), "string0");
assert_eq!(new_config.get("deadca4f").unwrap(), "new_field");
assert!(new_config.get("deadca5f").is_none());
assert!(new_config.get("not_on_whitelist1").is_none());
assert!(new_config.get("not_on_whitelist2").is_none());
assert!(new_config.get("on_both_lists").is_none());
}
}
Loading

0 comments on commit 4258fa4

Please sign in to comment.