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 Sep 13, 2023
1 parent f0882b9 commit bb29ca0
Show file tree
Hide file tree
Showing 4 changed files with 387 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 let Some(map) = migrated_config_json {
info!("Migrating config.json...");
write_config_json(&args.config_json_path, &map)?;
}

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
128 changes: 128 additions & 0 deletions src/migrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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;
use std::collections::HashMap;

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.clone();

handle_update_directives(
schema,
&migration_config.overrides,
config_json,
&mut new_config,
);

Ok(new_config)
}

fn handle_update_directives(
schema: &OsConfigSchema,
to_update: &HashMap<String, serde_json::Value>,
config_json: &ConfigMap,
new_config: &mut ConfigMap,
) {
for key in to_update.keys() {
if !schema.config.whitelist.contains(key) {
debug!("Key `{}` not in whitelist, skipping", key);
continue;
}

if let Some(future) = to_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 {
debug!(
"Key `{}` found with current value `{}` equal to update value `{}`, skipping",
key, current, future
);
}
}
}
}
}

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

let schema = r#"
{
"services": [
],
"keys": [],
"config": {
"whitelist": [
"deadbeef",
"deadca1f",
"deadca2f",
"deadca3f",
"deadca4f"
]
}
}
"#
.to_string();

let configuration = unindent::unindent(
r#"
{
"overrides": {
"deadbeef": 2,
"deadca1f": "3",
"deadca2f": false,
"deadca3f": "string0",
"deadca4f": "new_field",
"not_on_whitelist1": "not_on_whitelist"
}
}
"#,
);

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("not_on_whitelist1").is_none());
}
}
Loading

0 comments on commit bb29ca0

Please sign in to comment.