From f4b3f7f58385373189a99c78d8fba9207f5c92cb Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Mon, 24 Feb 2020 10:16:40 -0800 Subject: [PATCH 1/6] Remove old migrations that no longer apply after 0.3 break --- packages/os/os.spec | 3 + sources/Cargo.lock | 46 --------- sources/Cargo.toml | 6 +- sources/api/migration/migrations/.keep | 0 .../v0.1/migrate-borkseed/Cargo.toml | 12 --- .../v0.1/migrate-borkseed/src/main.rs | 95 ------------------- .../Cargo.toml | 12 --- .../src/main.rs | 38 -------- .../migrate-containerd-config-path/Cargo.toml | 12 --- .../src/main.rs | 32 ------- .../migrate-host-containers-v03/Cargo.toml | 13 --- .../migrate-host-containers-v03/src/main.rs | 39 -------- .../v0.2/migrate-remove-region/Cargo.toml | 12 --- .../v0.2/migrate-remove-region/src/main.rs | 21 ---- tools/rpm2migrations | 2 +- 15 files changed, 5 insertions(+), 338 deletions(-) create mode 100644 sources/api/migration/migrations/.keep delete mode 100644 sources/api/migration/migrations/v0.1/migrate-borkseed/Cargo.toml delete mode 100644 sources/api/migration/migrations/v0.1/migrate-borkseed/src/main.rs delete mode 100644 sources/api/migration/migrations/v0.1/migrate-host-containers-version/Cargo.toml delete mode 100644 sources/api/migration/migrations/v0.1/migrate-host-containers-version/src/main.rs delete mode 100644 sources/api/migration/migrations/v0.2/migrate-containerd-config-path/Cargo.toml delete mode 100644 sources/api/migration/migrations/v0.2/migrate-containerd-config-path/src/main.rs delete mode 100644 sources/api/migration/migrations/v0.2/migrate-host-containers-v03/Cargo.toml delete mode 100644 sources/api/migration/migrations/v0.2/migrate-host-containers-v03/src/main.rs delete mode 100644 sources/api/migration/migrations/v0.2/migrate-remove-region/Cargo.toml delete mode 100644 sources/api/migration/migrations/v0.2/migrate-remove-region/src/main.rs diff --git a/packages/os/os.spec b/packages/os/os.spec index ed920cd2614..75f778007a7 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -200,7 +200,10 @@ done install -d %{buildroot}%{_cross_datadir}/migrations for version_path in %{_builddir}/sources/api/migration/migrations/*; do + [ -e "${version_path}" ] || continue for migration_path in "${version_path}"/*; do + [ -e "${migration_path}" ] || continue + version="${version_path##*/}" crate_name="${migration_path##*/}" migration_binary_name="migrate_${version}_${crate_name#migrate-}" diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 4dc5bb81f2b..6b26cde3508 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1502,52 +1502,6 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "migrate-borkseed" -version = "0.1.0" -dependencies = [ - "migration-helpers 0.1.0", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "migrate-containerd-config-path" -version = "0.1.0" -dependencies = [ - "migration-helpers 0.1.0", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "migrate-host-containers-v03" -version = "0.1.0" -dependencies = [ - "migration-helpers 0.1.0", - "schnauzer 0.1.0", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "migrate-host-containers-version" -version = "0.1.0" -dependencies = [ - "migration-helpers 0.1.0", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "migrate-remove-region" -version = "0.1.0" -dependencies = [ - "migration-helpers 0.1.0", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "migration-helpers" version = "0.1.0" diff --git a/sources/Cargo.toml b/sources/Cargo.toml index ebd48cacf1f..4cab59ace46 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -16,11 +16,7 @@ members = [ "api/migration/migrator", "api/migration/migration-helpers", - "api/migration/migrations/v0.1/migrate-borkseed", - "api/migration/migrations/v0.1/migrate-host-containers-version", - "api/migration/migrations/v0.2/migrate-containerd-config-path", - "api/migration/migrations/v0.2/migrate-remove-region", - "api/migration/migrations/v0.2/migrate-host-containers-v03", + # "api/migration/migrations/vX.Y.Z/... "bottlerocket-release", diff --git a/sources/api/migration/migrations/.keep b/sources/api/migration/migrations/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sources/api/migration/migrations/v0.1/migrate-borkseed/Cargo.toml b/sources/api/migration/migrations/v0.1/migrate-borkseed/Cargo.toml deleted file mode 100644 index 2f1725bd0ae..00000000000 --- a/sources/api/migration/migrations/v0.1/migrate-borkseed/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "migrate-borkseed" -version = "0.1.0" -authors = ["Tom Kirchner "] -license = "Apache-2.0 OR MIT" -edition = "2018" -publish = false - -[dependencies] -migration-helpers = { path = "../../../migration-helpers" } -serde_json = "1.0" -snafu = "0.6" diff --git a/sources/api/migration/migrations/v0.1/migrate-borkseed/src/main.rs b/sources/api/migration/migrations/v0.1/migrate-borkseed/src/main.rs deleted file mode 100644 index 12f77230560..00000000000 --- a/sources/api/migration/migrations/v0.1/migrate-borkseed/src/main.rs +++ /dev/null @@ -1,95 +0,0 @@ -#![deny(rust_2018_idioms)] - -use migration_helpers::{error, migrate, Migration, MigrationData, Result}; -use std::convert::TryFrom; -use std::process; - -/// We moved from String to u32 for the seed value generated by bork and used by updog. -struct BorkSeedIntMigration; - -impl Migration for BorkSeedIntMigration { - fn forward(&mut self, mut input: MigrationData) -> Result { - if let Some(seed_val) = input.data.get_mut("settings.updates.seed") { - // We have the seed setting; check its type to see what we can do. - if let Some(seed_str) = seed_val.as_str() { - // Confirm the string is a valid u32. - let seed: u32 = seed_str.parse().or_else(|e| { - error::Migration { - msg: format!("Existing update seed string is not a valid u32: {}", e), - } - .fail() - })?; - *seed_val = serde_json::Value::Number(seed.into()); - } else if let Some(seed_num) = seed_val.as_u64() { - // We shouldn't find a number because the migration should only run against a - // version with a String seed, but... as long as it's a valid u32, allow it. - let seed = u32::try_from(seed_num).or_else(|e| { - error::Migration { - msg: format!("Existing update seed number(!) is not a valid u32: {}", e), - } - .fail() - })?; - *seed_val = serde_json::Value::Number(seed.into()); - } else { - // Other type, shouldn't happen, error. - return error::Migration { - msg: format!("Unsupported type of existing update seed: '{:?}'", seed_val), - } - .fail(); - } - } else { - // If they don't have a seed, one will be generated on startup. - } - Ok(input) - } - - fn backward(&mut self, mut input: MigrationData) -> Result { - if let Some(seed_val) = input.data.get_mut("settings.updates.seed") { - // We have the seed setting; check its type to see what we can do. - if let Some(seed_num) = seed_val.as_u64() { - // Number back to string, just serialize. - let seed_str = serde_json::to_string(&seed_num).or_else(|e| { - error::Migration { - msg: format!("Existing update seed number failed serialization: {}", e), - } - .fail() - })?; - *seed_val = serde_json::Value::String(seed_str); - } else if let Some(seed_str) = seed_val.as_str() { - // We shouldn't find a string because the migration should only run against a - // version with a number seed, but... as long as it's a valid u32, allow it. - // JUST FOR VALIDATION: - let _seed: u32 = seed_str.parse().or_else(|e| { - error::Migration { - msg: format!("Existing update seed string(!) is not a valid u32: {}", e), - } - .fail() - })?; - // Do nothing; keep the original (valid) string. - } else { - // Other type, shouldn't happen, error. - return error::Migration { - msg: format!("Unsupported type of existing update seed: '{:?}'", seed_val), - } - .fail(); - } - } else { - // If they don't have a seed, one will be generated on startup. - } - Ok(input) - } -} - -fn run() -> Result<()> { - migrate(BorkSeedIntMigration) -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -fn main() { - if let Err(e) = run() { - eprintln!("{}", e); - process::exit(1); - } -} diff --git a/sources/api/migration/migrations/v0.1/migrate-host-containers-version/Cargo.toml b/sources/api/migration/migrations/v0.1/migrate-host-containers-version/Cargo.toml deleted file mode 100644 index 22d874232a9..00000000000 --- a/sources/api/migration/migrations/v0.1/migrate-host-containers-version/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "migrate-host-containers-version" -version = "0.1.0" -authors = ["Erikson Tung "] -license = "Apache-2.0 OR MIT" -edition = "2018" -publish = false - -[dependencies] -migration-helpers = { path = "../../../migration-helpers" } -serde_json = "1.0" -snafu = "0.6" diff --git a/sources/api/migration/migrations/v0.1/migrate-host-containers-version/src/main.rs b/sources/api/migration/migrations/v0.1/migrate-host-containers-version/src/main.rs deleted file mode 100644 index f2a975f642d..00000000000 --- a/sources/api/migration/migrations/v0.1/migrate-host-containers-version/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![deny(rust_2018_idioms)] - -use migration_helpers::{migrate, Result}; -use migration_helpers::common_migrations::ReplaceStringMigration; -use std::process; - -const DEFAULT_ADMIN_CTR_IMG_OLD: &str = - "328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-admin:v0.1"; -const DEFAULT_ADMIN_CTR_IMG_NEW: &str = - "328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-admin:v0.2"; -const DEFAULT_CONTROL_CTR_IMG_OLD: &str = - "328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-control:v0.1"; -const DEFAULT_CONTROL_CTR_IMG_NEW: &str = - "328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-control:v0.2"; - -/// We bumped the versions of the default admin container and the default control container from v0.1 to v0.2 -fn run() -> Result<()> { - migrate(ReplaceStringMigration { - setting: "settings.host-containers.admin.source", - old_val: DEFAULT_ADMIN_CTR_IMG_OLD, - new_val: DEFAULT_ADMIN_CTR_IMG_NEW, - })?; - migrate(ReplaceStringMigration { - setting: "settings.host-containers.control.source", - old_val: DEFAULT_CONTROL_CTR_IMG_OLD, - new_val: DEFAULT_CONTROL_CTR_IMG_NEW, - }) -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -fn main() { - if let Err(e) = run() { - eprintln!("{}", e); - process::exit(1); - } -} diff --git a/sources/api/migration/migrations/v0.2/migrate-containerd-config-path/Cargo.toml b/sources/api/migration/migrations/v0.2/migrate-containerd-config-path/Cargo.toml deleted file mode 100644 index ab5e245036f..00000000000 --- a/sources/api/migration/migrations/v0.2/migrate-containerd-config-path/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "migrate-containerd-config-path" -version = "0.1.0" -authors = ["Tom Kirchner "] -license = "Apache-2.0 OR MIT" -edition = "2018" -publish = false - -[dependencies] -migration-helpers = { path = "../../../migration-helpers" } -serde_json = "1.0" -snafu = "0.6" diff --git a/sources/api/migration/migrations/v0.2/migrate-containerd-config-path/src/main.rs b/sources/api/migration/migrations/v0.2/migrate-containerd-config-path/src/main.rs deleted file mode 100644 index f9510b42ae4..00000000000 --- a/sources/api/migration/migrations/v0.2/migrate-containerd-config-path/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -#![deny(rust_2018_idioms)] - -use migration_helpers::{migrate, Result}; -use migration_helpers::common_migrations::ReplaceStringMigration; -use std::process; - -const SETTING: &str = "configuration-files.containerd-config-toml.template-path"; -// Old version with no variant -const DEFAULT_CTRD_CONFIG_OLD: &str = "/usr/share/templates/containerd-config-toml"; -// Any users coming from old versions would be using the aws-k8s variant because no other existed :) -const DEFAULT_CTRD_CONFIG_NEW: &str = "/usr/share/templates/containerd-config-toml_aws-k8s"; - -/// We changed the path to our containerd configuration template so that we could support image -/// variants with different configs. We need to update old images to the new path, and on -/// downgrade, new images to the old path. -fn run() -> Result<()> { - migrate(ReplaceStringMigration { - setting: SETTING, - old_val: DEFAULT_CTRD_CONFIG_OLD, - new_val: DEFAULT_CTRD_CONFIG_NEW - }) -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -fn main() { - if let Err(e) = run() { - eprintln!("{}", e); - process::exit(1); - } -} diff --git a/sources/api/migration/migrations/v0.2/migrate-host-containers-v03/Cargo.toml b/sources/api/migration/migrations/v0.2/migrate-host-containers-v03/Cargo.toml deleted file mode 100644 index 170b3a591ba..00000000000 --- a/sources/api/migration/migrations/v0.2/migrate-host-containers-v03/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "migrate-host-containers-v03" -version = "0.1.0" -authors = ["Erikson Tung "] -license = "Apache-2.0 OR MIT" -edition = "2018" -publish = false - -[dependencies] -migration-helpers = { path = "../../../migration-helpers" } -schnauzer = { path = "../../../../schnauzer" } -serde_json = "1.0" -snafu = "0.6" diff --git a/sources/api/migration/migrations/v0.2/migrate-host-containers-v03/src/main.rs b/sources/api/migration/migrations/v0.2/migrate-host-containers-v03/src/main.rs deleted file mode 100644 index 69086146aa2..00000000000 --- a/sources/api/migration/migrations/v0.2/migrate-host-containers-v03/src/main.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![deny(rust_2018_idioms)] - -use migration_helpers::common_migrations::ReplaceTemplateMigration; -use migration_helpers::{migrate, Result}; -use std::process; - -const OLD_ADMIN_CTR_TEMPLATE: &str = - "328549459982.dkr.ecr.{{ settings.aws.region }}.amazonaws.com/thar-admin:v0.2"; -const NEW_ADMIN_CTR_TEMPLATE: &str = - "328549459982.dkr.ecr.{{ settings.aws.region }}.amazonaws.com/bottlerocket-admin:v0.3"; -const OLD_CONTROL_CTR_TEMPLATE: &str = - "328549459982.dkr.ecr.{{ settings.aws.region }}.amazonaws.com/thar-control:v0.2"; -const NEW_CONTROL_CTR_TEMPLATE: &str = - "328549459982.dkr.ecr.{{ settings.aws.region }}.amazonaws.com/bottlerocket-control:v0.3"; - -/// We bumped the versions of the default admin container and the default control container from v0.2 to v0.3 -/// This migration also includes a name change for the host-container images -fn run() -> Result<()> { - migrate(ReplaceTemplateMigration { - setting: "settings.host-containers.admin.source", - old_template: OLD_ADMIN_CTR_TEMPLATE, - new_template: NEW_ADMIN_CTR_TEMPLATE, - })?; - migrate(ReplaceTemplateMigration { - setting: "settings.host-containers.control.source", - old_template: OLD_CONTROL_CTR_TEMPLATE, - new_template: NEW_CONTROL_CTR_TEMPLATE, - }) -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -fn main() { - if let Err(e) = run() { - eprintln!("{}", e); - process::exit(1); - } -} diff --git a/sources/api/migration/migrations/v0.2/migrate-remove-region/Cargo.toml b/sources/api/migration/migrations/v0.2/migrate-remove-region/Cargo.toml deleted file mode 100644 index f92aa74f7ea..00000000000 --- a/sources/api/migration/migrations/v0.2/migrate-remove-region/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "migrate-remove-region" -version = "0.1.0" -authors = ["Tom Kirchner "] -license = "Apache-2.0 OR MIT" -edition = "2018" -publish = false - -[dependencies] -migration-helpers = { path = "../../../migration-helpers" } -serde_json = "1.0" -snafu = "0.6" diff --git a/sources/api/migration/migrations/v0.2/migrate-remove-region/src/main.rs b/sources/api/migration/migrations/v0.2/migrate-remove-region/src/main.rs deleted file mode 100644 index 9f9dcb0071c..00000000000 --- a/sources/api/migration/migrations/v0.2/migrate-remove-region/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![deny(rust_2018_idioms)] - -use migration_helpers::{migrate, Result}; -use migration_helpers::common_migrations::AddSettingMigration; -use std::process; - -/// We added a generated setting, "settings.aws.region", and want to make sure it's removed before -/// we go back to old versions that don't understand it. -fn run() -> Result<()> { - migrate(AddSettingMigration("settings.aws.region")) -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -fn main() { - if let Err(e) = run() { - eprintln!("{}", e); - process::exit(1); - } -} diff --git a/tools/rpm2migrations b/tools/rpm2migrations index e222b05426b..8dd995054b6 100755 --- a/tools/rpm2migrations +++ b/tools/rpm2migrations @@ -2,7 +2,6 @@ # # Retrieve migrations from the RPM and output an appropriately named tarball set -eu -o pipefail -shopt -qs failglob for opt in "$@"; do optarg="$(expr "${opt}" : '[^=]*=\(.*\)')" @@ -30,6 +29,7 @@ fi # lz4 compress each migration for migration in "${MIGRATIONS_DIR}"/*; do + [ -e "${migration}" ] || continue lz4 -v "${migration}" "${migration}.lz4" done From ae236a630fbdcbe3f08c2e9c9b8b9398d7522904 Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Mon, 24 Feb 2020 10:17:33 -0800 Subject: [PATCH 2/6] Remove workaround for old migration interface --- .../migration/migration-helpers/src/args.rs | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/sources/api/migration/migration-helpers/src/args.rs b/sources/api/migration/migration-helpers/src/args.rs index 3b96b754c0c..9a9854f543f 100644 --- a/sources/api/migration/migration-helpers/src/args.rs +++ b/sources/api/migration/migration-helpers/src/args.rs @@ -36,7 +36,6 @@ pub(crate) fn parse_args(args: env::Args) -> Result { let mut migration_type = None; let mut source_datastore = None; let mut target_datastore = None; - let mut datastore_path = None; let mut iter = args.skip(1); while let Some(arg) = iter.next() { @@ -55,14 +54,6 @@ pub(crate) fn parse_args(args: env::Args) -> Result { })) } - // Support the argument of the old migrator interface, with some caveats - "--datastore-path" => { - datastore_path = Some( - iter.next() - .unwrap_or_else(|| usage_msg("Did not give argument to --datastore-path")), - ); - } - "--forward" => migration_type = Some(MigrationType::Forward), "--backward" => migration_type = Some(MigrationType::Backward), @@ -70,20 +61,9 @@ pub(crate) fn parse_args(args: env::Args) -> Result { } } - if let Some(datastore_path) = datastore_path { - // For compatibility with old migration interface that had single source+target; other code - // in migration-helpers checks if source==target to see if it needs to do a workaround. - if source_datastore.is_some() || target_datastore.is_some() { - usage_msg("--datastore-path is only for backward compatibility and \ - cannot be used with --source-datastore / --target-datastore"); - } - source_datastore = Some(datastore_path.clone()); - target_datastore = Some(datastore_path); - } else { - // In no other case should they be the same; we use it for compatibility checks. - if source_datastore == target_datastore { - usage_msg("--source-datastore and --target-datastore cannot be the same"); - } + // In no other case should they be the same; we use it for compatibility checks. + if source_datastore == target_datastore { + usage_msg("--source-datastore and --target-datastore cannot be the same"); } Ok(Args { From f24898a21b1cc860b5c5641ea5b63f34997f11dd Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Mon, 24 Feb 2020 11:23:44 -0800 Subject: [PATCH 3/6] Remove migrator remove-key workaround that no longer applies --- .../migration/migration-helpers/src/lib.rs | 11 -- .../migration-helpers/src/workarounds.rs | 168 ------------------ 2 files changed, 179 deletions(-) delete mode 100644 sources/api/migration/migration-helpers/src/workarounds.rs diff --git a/sources/api/migration/migration-helpers/src/lib.rs b/sources/api/migration/migration-helpers/src/lib.rs index dfbb6dddf6e..5f1ea49bab7 100644 --- a/sources/api/migration/migration-helpers/src/lib.rs +++ b/sources/api/migration/migration-helpers/src/lib.rs @@ -15,7 +15,6 @@ mod args; pub mod common_migrations; mod datastore; pub mod error; -mod workarounds; use snafu::ResultExt; use std::collections::HashMap; @@ -28,7 +27,6 @@ pub use apiserver::datastore::{DataStore, FilesystemDataStore}; use args::{parse_args, Args}; use datastore::{get_input_data, set_output_data}; pub use error::Result; -use workarounds::fix_migrated_data; /// The data store implementation currently in use. Used by the simpler `migrate` interface; can /// be overridden by using the `run_migration` interface. @@ -106,15 +104,6 @@ pub fn run_migration(mut migration: impl Migration, args: &Args) -> Result<()> { MigrationType::Backward => migration.backward(migrated), }?; - fix_migrated_data( - &input, - &mut migrated, - &source, - &mut target, - &committed, - &args, - )?; - validate_migrated_data(&migrated)?; set_output_data(&mut target, &migrated, &committed)?; diff --git a/sources/api/migration/migration-helpers/src/workarounds.rs b/sources/api/migration/migration-helpers/src/workarounds.rs deleted file mode 100644 index fb825bf3d59..00000000000 --- a/sources/api/migration/migration-helpers/src/workarounds.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::args::Args; -use crate::{error, MigrationData, Result}; -use apiserver::datastore::{Committed, DataStore, Key, KeyType}; -use snafu::ResultExt; -use std::collections::HashSet; - -/// Here we can fix known issues with migrated data, for example issues related to changes -/// in migration interface that we don't want the migrations to have to deal with. -pub(crate) fn fix_migrated_data( - input: &MigrationData, - output: &MigrationData, - _source_datastore: &D, - target_datastore: &mut D, - committed: &Committed, - args: &Args, -) -> Result<()> { - // If the source and target data store path are the same, we're using the old migrator - // interface, and have to use a workaround to be able to delete keys. They can't just be - // removed from the MigrationData struct, because the old interface used the same data store - // for input and output, and removing from MigrationData just means we won't write it out - // again - but the file will still be there from the input. We need to tell the data store - // to remove it. - if args.source_datastore == args.target_datastore { - // Data keys first - let old_keys: HashSet<_> = input.data.keys().collect(); - let new_keys: HashSet<_> = output.data.keys().collect(); - for removed_key_str in old_keys.difference(&new_keys) { - // We need to make a Key from the key's name to fit the data store interface; we - // don't use Key in MigrationData for the convenience of migration authors. - let removed_key = - Key::new(KeyType::Data, removed_key_str).context(error::InvalidKey { - key_type: KeyType::Data, - key: *removed_key_str, - })?; - target_datastore - .unset_key(&removed_key, &committed) - .context(error::DataStoreRemove { - key: *removed_key_str, - })?; - } - - // Now the same thing for metadata - for (data_key_str, old_metadata) in &input.metadata { - let removed: HashSet<_> = if let Some(new_metadata) = output.metadata.get(data_key_str) - { - // Find which metadata keys the migration removed, for this data key - let old_keys: HashSet<_> = old_metadata.keys().collect(); - let new_keys: HashSet<_> = new_metadata.keys().collect(); - old_keys.difference(&new_keys).map(|&s| s).collect() - } else { - // Migration output has no metadata for this data key, so it was all removed - old_metadata.keys().collect() - }; - - for removed_meta_str in removed { - let removed_meta = - Key::new(KeyType::Meta, removed_meta_str).context(error::InvalidKey { - key_type: KeyType::Meta, - key: removed_meta_str, - })?; - let removed_data = - Key::new(KeyType::Data, data_key_str).context(error::InvalidKey { - key_type: KeyType::Data, - key: data_key_str, - })?; - target_datastore - .unset_metadata(&removed_meta, &removed_data) - .context(error::DataStoreRemove { - key: removed_meta_str, - })?; - } - } - } - - Ok(()) -} - -#[cfg(test)] -mod test { - use super::fix_migrated_data; - use crate::datastore::set_output_data; - use crate::{Args, MigrationData, MigrationType}; - use apiserver::datastore::memory::MemoryDataStore; - use apiserver::datastore::{Committed, DataStore, Key, KeyType}; - use maplit::hashmap; - use serde_json::json; - - #[test] - fn test_fix_migrated_data() { - // Data/metadata starting with "remove" should be removed - let input = MigrationData { - data: hashmap!( - "keepdata".into() => json!("hi"), - "removedata".into() => json!("sup"), - ), - metadata: hashmap!( - "keepdata".into() => hashmap!( - "keepmeta".into() => json!("howdy"), - "removemeta".into() => json!("yo"), - ), - "removedata".into() => hashmap!( - "keepmeta".into() => json!("hello"), - "removemeta".into() => json!("hiya"), - ), - ), - }; - // This represents 'input' after a migration removes some data, so it should match the - // data store after we call fix_migrated_data - let expected = MigrationData { - data: hashmap!( - "keepdata".into() => json!("hi"), - ), - metadata: hashmap!( - "keepdata".into() => hashmap!( - "keepmeta".into() => json!("howdy"), - ), - "removedata".into() => hashmap!( - "keepmeta".into() => json!("hello"), - ), - ), - }; - - // The point of the workaround is affecting the data store directly, so make test stores - let mut source = MemoryDataStore::new(); - set_output_data(&mut source, &input, &Committed::Live).unwrap(); - // To replicate old interface, the target data store starts with the input data, and - // we're going to confirm that removed values are actually removed - let mut target = MemoryDataStore::new(); - set_output_data(&mut target, &input, &Committed::Live).unwrap(); - - // Ensure values are there at the start - let kept_data = Key::new(KeyType::Data, "keepdata").unwrap(); - let removed_data = Key::new(KeyType::Data, "removedata").unwrap(); - let kept_meta = Key::new(KeyType::Meta, "keepmeta").unwrap(); - let removed_meta = Key::new(KeyType::Meta, "removemeta").unwrap(); - assert_eq!(target.get_key(&kept_data, &Committed::Live).unwrap(), Some("\"hi\"".into())); - assert_eq!(target.get_key(&removed_data, &Committed::Live).unwrap(), Some("\"sup\"".into())); - assert_eq!(target.get_metadata(&kept_meta, &kept_data).unwrap(), Some("\"howdy\"".into())); - assert_eq!(target.get_metadata(&kept_meta, &removed_data).unwrap(), Some("\"hello\"".into())); - assert_eq!(target.get_metadata(&removed_meta, &kept_data).unwrap(), Some("\"yo\"".into())); - assert_eq!(target.get_metadata(&removed_meta, &removed_data).unwrap(), Some("\"hiya\"".into())); - - // Same source and target, i.e. using old interface, so we should do our fix - let args = Args { - source_datastore: "same".into(), - target_datastore: "same".into(), - migration_type: MigrationType::Forward, - }; - fix_migrated_data( - &input, - &expected, - &source, - &mut target, - &Committed::Live, - &args, - ) - .unwrap(); - - // Ensure unaffected values were kept - assert_eq!(target.get_key(&kept_data, &Committed::Live).unwrap(), Some("\"hi\"".into())); - assert_eq!(target.get_metadata(&kept_meta, &kept_data).unwrap(), Some("\"howdy\"".into())); - assert_eq!(target.get_metadata(&kept_meta, &removed_data).unwrap(), Some("\"hello\"".into())); - // Ensure removed values were removed - assert_eq!(target.get_key(&removed_data, &Committed::Live).unwrap(), None); - assert_eq!(target.get_metadata(&removed_meta, &kept_data).unwrap(), None); - assert_eq!(target.get_metadata(&removed_meta, &removed_data).unwrap(), None); - } -} From 791fb4817c642e9d9e43c47513abe992b7410920 Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Mon, 24 Feb 2020 11:55:32 -0800 Subject: [PATCH 4/6] Remove comment about version formats supported by old versions --- sources/updater/update_metadata/src/se.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sources/updater/update_metadata/src/se.rs b/sources/updater/update_metadata/src/se.rs index 1ac26624c80..36272594448 100644 --- a/sources/updater/update_metadata/src/se.rs +++ b/sources/updater/update_metadata/src/se.rs @@ -12,7 +12,6 @@ where { let mut map = BTreeMap::new(); for ((from, to), val) in value { - // NOTE: The space in this tuple is required for versions of Bottlerocket < 0.2.0 let key = format!( "({}, {})", serde_plain::to_string(&from).map_err(|e| S::Error::custom(format!( From 606981298fc4dbacdb3ddc1156df8c95ca67bec9 Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Mon, 24 Feb 2020 12:52:23 -0800 Subject: [PATCH 5/6] Replace unused timezone/hostname settings with motd setting It's useful to have an example setting so that users can see it in documentation and follow along, learning the API by actually making calls, without worrying about breaking their system. We were using hostname and timezone for this, but those sounded like real settings (and were originally intended to be real settings) so it was confusing. motd is a "real" setting in that it updates /etc/motd, but that's very low risk and only exposed to the user in any case. Plus, with low risk, it's fun to see an actual impact on your system. --- README.md | 8 ++--- packages/release/hostname.template | 1 - packages/release/motd.template | 1 + packages/release/release.spec | 7 ++-- sources/api/apiclient/README.md | 2 +- sources/api/apiclient/README.tpl | 2 +- .../api/apiserver/src/server/controller.rs | 36 +++++++++---------- sources/api/storewolf/src/main.rs | 6 ++-- sources/api/thar-be-settings/src/service.rs | 9 +++-- sources/models/defaults.toml | 26 +++++++------- sources/models/src/aws-dev/mod.rs | 5 ++- sources/models/src/aws-k8s/mod.rs | 5 ++- 12 files changed, 51 insertions(+), 57 deletions(-) delete mode 100644 packages/release/hostname.template create mode 100644 packages/release/motd.template diff --git a/README.md b/README.md index cd521dc1e7b..2c7a46e9cda 100644 --- a/README.md +++ b/README.md @@ -164,13 +164,13 @@ apiclient -u /settings This will return all of the current settings in JSON format. For example, here's an abbreviated response: ``` -{"timezone":"America/Los_Angeles","kubernetes":{...}} +{"motd":"...", {"kubernetes": ...}} ``` You can change settings by sending back the same type of JSON data in a PATCH request. This can include any number of settings changes. ``` -apiclient -m PATCH -u /settings -d '{"timezone": "America/Thunder_Bay"}' +apiclient -m PATCH -u /settings -d '{"motd": "my own value!"}' ``` This will *stage* the setting in a "pending" area - a transaction. @@ -204,7 +204,7 @@ Here's the user data to change the time zone setting, as we did in the last sect ``` [settings] -timezone = "America/Thunder_Bay" +motd = "my own value!" ``` ### Description of settings @@ -225,7 +225,7 @@ In this format, "settings.kubernetes.cluster-name" refers to the same key as in #### Top-level settings -* `settings.timezone`, `settings.hostname`: These don't function currently, but are intended to let you override the system timezone or the hostname retrieved from DHCP. At the moment they're used as example settings. +* `settings.motd`: This setting is just written out to /etc/motd. It's useful as a way to get familiar with the API! Try changing it. #### Kubernetes settings diff --git a/packages/release/hostname.template b/packages/release/hostname.template deleted file mode 100644 index df561e2718a..00000000000 --- a/packages/release/hostname.template +++ /dev/null @@ -1 +0,0 @@ -{{ settings.hostname }} diff --git a/packages/release/motd.template b/packages/release/motd.template new file mode 100644 index 00000000000..74813dc9fb6 --- /dev/null +++ b/packages/release/motd.template @@ -0,0 +1 @@ +{{ settings.motd }} diff --git a/packages/release/release.spec b/packages/release/release.spec index 8c7656d29ea..7264cc0a4b1 100644 --- a/packages/release/release.spec +++ b/packages/release/release.spec @@ -11,8 +11,7 @@ Source11: nsswitch.conf Source98: release-sysctl.conf Source99: release-tmpfiles.conf -# FIXME What should own system-level file templates? -Source200: hostname.template +Source200: motd.template Source1000: eth0.xml Source1002: configured.target @@ -99,7 +98,7 @@ sed -e 's|PREFIX|%{_cross_prefix}|' %{S:1011} > ${LICENSEPATH}.mount install -p -m 0644 ${LICENSEPATH}.mount %{buildroot}%{_cross_unitdir} install -d %{buildroot}%{_cross_templatedir} -install -p -m 0644 %{S:200} %{buildroot}%{_cross_templatedir}/hostname +install -p -m 0644 %{S:200} %{buildroot}%{_cross_templatedir}/motd %files %{_cross_factorydir}%{_cross_sysconfdir}/hosts @@ -116,6 +115,6 @@ install -p -m 0644 %{S:200} %{buildroot}%{_cross_templatedir}/hostname %{_cross_unitdir}/*-licenses.mount %{_cross_unitdir}/var-lib-bottlerocket.mount %dir %{_cross_templatedir} -%{_cross_templatedir}/hostname +%{_cross_templatedir}/motd %changelog diff --git a/sources/api/apiclient/README.md b/sources/api/apiclient/README.md index 6aeac21ff8d..e8c9579933d 100644 --- a/sources/api/apiclient/README.md +++ b/sources/api/apiclient/README.md @@ -30,7 +30,7 @@ apiclient -m GET -u /settings Changing settings: ``` -apiclient -X PATCH -u /settings -d '{"timezone": "OldLosAngeles"}' +apiclient -X PATCH -u /settings -d '{"motd": "my own value!"}' apiclient -m POST -u /tx/commit_and_apply ``` diff --git a/sources/api/apiclient/README.tpl b/sources/api/apiclient/README.tpl index 3d28f239fd0..b0009e09e01 100644 --- a/sources/api/apiclient/README.tpl +++ b/sources/api/apiclient/README.tpl @@ -30,7 +30,7 @@ apiclient -m GET -u /settings Changing settings: ``` -apiclient -X PATCH -u /settings -d '{"timezone": "OldLosAngeles"}' +apiclient -X PATCH -u /settings -d '{"motd": "my own value!"}' apiclient -m POST -u /tx/commit_and_apply ``` diff --git a/sources/api/apiserver/src/server/controller.rs b/sources/api/apiserver/src/server/controller.rs index dd4ac68baeb..37993df36dc 100644 --- a/sources/api/apiserver/src/server/controller.rs +++ b/sources/api/apiserver/src/server/controller.rs @@ -381,7 +381,7 @@ mod test { let mut ds = MemoryDataStore::new(); // Set directly with data store ds.set_key( - &Key::new(KeyType::Data, "settings.hostname").unwrap(), + &Key::new(KeyType::Data, "settings.motd").unwrap(), "\"json string\"", &Committed::Live, ) @@ -389,7 +389,7 @@ mod test { // Retrieve with helper let settings = get_settings(&ds, &Committed::Live).unwrap(); - assert_eq!(settings.hostname, Some("json string".try_into().unwrap())); + assert_eq!(settings.motd, Some("json string".try_into().unwrap())); } #[test] @@ -397,7 +397,7 @@ mod test { let mut ds = MemoryDataStore::new(); // Set directly with data store ds.set_key( - &Key::new(KeyType::Data, "settings.timezone").unwrap(), + &Key::new(KeyType::Data, "settings.motd").unwrap(), "\"json string\"", &Committed::Live, ) @@ -405,13 +405,13 @@ mod test { // Retrieve with helper let settings = get_settings_prefix(&ds, "", &Committed::Live).unwrap(); - assert_eq!(settings.timezone, Some("json string".try_into().unwrap())); + assert_eq!(settings.motd, Some("json string".try_into().unwrap())); - let settings = get_settings_prefix(&ds, "tim", &Committed::Live).unwrap(); - assert_eq!(settings.timezone, Some("json string".try_into().unwrap())); + let settings = get_settings_prefix(&ds, "mot", &Committed::Live).unwrap(); + assert_eq!(settings.motd, Some("json string".try_into().unwrap())); - let settings = get_settings_prefix(&ds, "timbits", &Committed::Live).unwrap(); - assert_eq!(settings.timezone, None); + let settings = get_settings_prefix(&ds, "motdxxx", &Committed::Live).unwrap(); + assert_eq!(settings.motd, None); } #[test] @@ -419,14 +419,14 @@ mod test { let mut ds = MemoryDataStore::new(); // Set directly with data store ds.set_key( - &Key::new(KeyType::Data, "settings.timezone").unwrap(), + &Key::new(KeyType::Data, "settings.motd").unwrap(), "\"json string 1\"", &Committed::Live, ) .unwrap(); ds.set_key( - &Key::new(KeyType::Data, "settings.hostname").unwrap(), + &Key::new(KeyType::Data, "settings.ntp.time-servers").unwrap(), "\"json string 2\"", &Committed::Live, ) @@ -434,9 +434,9 @@ mod test { // Retrieve with helper let settings = - get_settings_keys(&ds, &hashset!("settings.timezone"), &Committed::Live).unwrap(); - assert_eq!(settings.timezone, Some("json string 1".try_into().unwrap())); - assert_eq!(settings.hostname, None); + get_settings_keys(&ds, &hashset!("settings.motd"), &Committed::Live).unwrap(); + assert_eq!(settings.motd, Some("json string 1".try_into().unwrap())); + assert_eq!(settings.ntp, None); } #[test] @@ -471,7 +471,7 @@ mod test { #[test] fn set_settings_works() { let mut settings = Settings::default(); - settings.timezone = Some("tz".try_into().unwrap()); + settings.motd = Some("tz".try_into().unwrap()); // Set with helper let mut ds = MemoryDataStore::new(); @@ -480,7 +480,7 @@ mod test { set_settings(&mut ds, &settings, tx).unwrap(); // Retrieve directly - let key = Key::new(KeyType::Data, "settings.timezone").unwrap(); + let key = Key::new(KeyType::Data, "settings.motd").unwrap(); assert_eq!( Some("\"tz\"".to_string()), ds.get_key(&key, &pending).unwrap() @@ -541,7 +541,7 @@ mod test { let tx = "test transaction"; let pending = Committed::Pending { tx: tx.into() }; ds.set_key( - &Key::new(KeyType::Data, "settings.hostname").unwrap(), + &Key::new(KeyType::Data, "settings.motd").unwrap(), "\"json string\"", &pending, ) @@ -549,7 +549,7 @@ mod test { // Confirm pending let settings = get_settings(&ds, &pending).unwrap(); - assert_eq!(settings.hostname, Some("json string".try_into().unwrap())); + assert_eq!(settings.motd, Some("json string".try_into().unwrap())); // No live settings yet get_settings(&ds, &Committed::Live).unwrap_err(); @@ -560,6 +560,6 @@ mod test { get_settings(&ds, &pending).unwrap_err(); // Confirm live let settings = get_settings(&ds, &Committed::Live).unwrap(); - assert_eq!(settings.hostname, Some("json string".try_into().unwrap())); + assert_eq!(settings.motd, Some("json string".try_into().unwrap())); } } diff --git a/sources/api/storewolf/src/main.rs b/sources/api/storewolf/src/main.rs index ae3e6d140df..94db763cda8 100644 --- a/sources/api/storewolf/src/main.rs +++ b/sources/api/storewolf/src/main.rs @@ -208,7 +208,7 @@ fn create_new_datastore>(base_path: P, version: Option) // validating the types and structure. The resulting Vec looks like: // // [ -// Metadata {key: "settings.hostname", md: "affected-services", val: Array([ ... ])}, +// Metadata {key: "settings.motd", md: "affected-services", val: Array([ ... ])}, // Metadata { ... }, // ] fn parse_metadata_toml(md_toml_val: toml::Value) -> Result> { @@ -222,7 +222,7 @@ fn parse_metadata_toml(md_toml_val: toml::Value) -> Result> // associated with that key. It ends up looking like: // [ // ( - // ["settings", "hostname"], + // ["settings", "motd"], // toml::Value // ), // ... @@ -259,7 +259,7 @@ fn parse_metadata_toml(md_toml_val: toml::Value) -> Result> msg: "parse_metadata_toml found empty 'path' in the to_process vec - is 'metadata' not a Table?", })?; - // Make sure that the path contains more than 1 item, i.e. ["settings", "hostname"] + // Make sure that the path contains more than 1 item, i.e. ["settings", "motd"] ensure!( path.len() >= 1, error::Internal { diff --git a/sources/api/thar-be-settings/src/service.rs b/sources/api/thar-be-settings/src/service.rs index d234eba477c..9596590dfe6 100644 --- a/sources/api/thar-be-settings/src/service.rs +++ b/sources/api/thar-be-settings/src/service.rs @@ -168,18 +168,17 @@ mod test { #[test] fn test_get_affected_service_names() { let input_map = hashmap!( - "settings.hostname".to_string() => vec![ - "hostname".to_string(), - "timezone".to_string(), + "settings.example".to_string() => vec![ + "example".to_string(), ], "settings.foobar".to_string() => vec![ - "timezone".to_string(), + "example".to_string(), "barbaz".to_string() ] ); let expected_output = - hashset! {"hostname".to_string(), "timezone".to_string(), "barbaz".to_string()}; + hashset! {"example".to_string(), "barbaz".to_string()}; assert_eq!(get_affected_service_names(input_map), expected_output) } diff --git a/sources/models/defaults.toml b/sources/models/defaults.toml index d386bafe04c..90790398824 100644 --- a/sources/models/defaults.toml +++ b/sources/models/defaults.toml @@ -6,23 +6,18 @@ # as defined in src/VARIANT/mod.rs. [settings] -timezone = "America/Los_Angeles" -hostname = "localhost" +motd = "Welcome to Bottlerocket!" -[settings.updates] -metadata-base-url = "https://d25d9m6x9pxh9h.cloudfront.net/45efedef4afe/metadata/" -target-base-url = "https://d25d9m6x9pxh9h.cloudfront.net/45efedef4afe/targets/" +[metadata.settings.motd] +affected-services = ["motd"] -[services.hostname] -configuration-files = ["hostname"] +[services.motd] +configuration-files = ["motd"] restart-commands = [] -[configuration-files.hostname] -path = "/etc/hostname" -template-path = "/usr/share/templates/hostname" - -[metadata.settings.hostname] -affected-services = ["hostname"] +[configuration-files.motd] +path = "/etc/motd" +template-path = "/usr/share/templates/motd" # Container runtime. @@ -34,8 +29,11 @@ restart-commands = [] path = "/etc/containerd/config.toml" template-path = "/usr/share/templates/containerd-config-toml" +# Updates. -# Updog. +[settings.updates] +metadata-base-url = "https://d25d9m6x9pxh9h.cloudfront.net/45efedef4afe/metadata/" +target-base-url = "https://d25d9m6x9pxh9h.cloudfront.net/45efedef4afe/targets/" [services.updog] configuration-files = ["updog-toml"] diff --git a/sources/models/src/aws-dev/mod.rs b/sources/models/src/aws-dev/mod.rs index 73c809a2e84..8baad6cc6f9 100644 --- a/sources/models/src/aws-dev/mod.rs +++ b/sources/models/src/aws-dev/mod.rs @@ -2,15 +2,14 @@ use model_derive::model; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::modeled_types::{Identifier, SingleLineString}; +use crate::modeled_types::Identifier; use crate::{AwsSettings, ContainerImage, NtpSettings, UpdatesSettings}; // Note: we have to use 'rename' here because the top-level Settings structure is the only one // that uses its name in serialization; internal structures use the field name that points to it #[model(rename = "settings", impl_default = true)] struct Settings { - timezone: SingleLineString, - hostname: SingleLineString, + motd: String, updates: UpdatesSettings, host_containers: HashMap, ntp: NtpSettings, diff --git a/sources/models/src/aws-k8s/mod.rs b/sources/models/src/aws-k8s/mod.rs index a321f53b910..7167fec71bf 100644 --- a/sources/models/src/aws-k8s/mod.rs +++ b/sources/models/src/aws-k8s/mod.rs @@ -2,15 +2,14 @@ use model_derive::model; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::modeled_types::{Identifier, SingleLineString}; +use crate::modeled_types::Identifier; use crate::{AwsSettings, ContainerImage, KubernetesSettings, NtpSettings, UpdatesSettings}; // Note: we have to use 'rename' here because the top-level Settings structure is the only one // that uses its name in serialization; internal structures use the field name that points to it #[model(rename = "settings", impl_default = true)] struct Settings { - timezone: SingleLineString, - hostname: SingleLineString, + motd: String, kubernetes: KubernetesSettings, updates: UpdatesSettings, host_containers: HashMap, From 268d5261bb4b4829f080fadfa1ca78f2757f0835 Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Tue, 25 Feb 2020 10:15:13 -0800 Subject: [PATCH 6/6] Update Release.toml for 0.3.0 release This will be a breaking release, so we clear the migrations list. --- Release.toml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Release.toml b/Release.toml index 6e331a7c480..ca0e165fc31 100644 --- a/Release.toml +++ b/Release.toml @@ -1,12 +1,3 @@ -version = "0.2.2" +version = "0.3.0" [migrations] -# Happy path - skip over 0.1, which had an issue with compressed migrations. Normal new migrations go here. -"(0.0, 0.2)" = ["migrate_0.1_borkseed", "migrate_0.1_host-containers-version-migration", "migrate_0.2_containerd-config-path"] -"(0.2, 0.3)" = ["migrate_0.3_remove-region"] -# We don't want to block updating into 0.1 if a user specifically wants the related OS version... -"(0.0, 0.1)" = ["migrate_0.1_borkseed", "migrate_0.1_host-containers-version-migration"] -# ...but to migrate out of the OS with version 0.1 we have to have copies of the migration with the lz4 extension. -# (These filenames are also supported by newer OS versions.) -"(0.1, 0.2)" = ["migrate_0.2_containerd-config-path.lz4"] -"(0.1, 0.3)" = ["migrate_0.2_containerd-config-path.lz4", "migrate_0.3_remove-region.lz4"]