Repository: bottlerocket-os/bottlerocket
File: sources/api/migration/migration-helpers/src/common_migrations.rs (Rust)

Code before:
/// them and error.
pub struct RemoveSettingsMigration<'a>(pub &'a [&'static str]);

impl Migration for RemoveSettingsMigration<'_> {
    /// Newer versions don't know about the settings; we remove them so that new versions don't see
    /// them and fail deserialization.  (The settings must be defaulted or generated in old versions,
    /// and safe to remove.)
    fn forward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
        for setting in self.0 {
            if let Some(data) = input.data.remove(*setting) {
                println!("Removed {setting}, which was set to '{data}'");
            } else {
                println!("Found no {setting} to remove");
            }
        }
        Ok(input)
    }

    /// Old versions must either have a default for the settings or generate it; we don't need to
    /// do anything.
    fn backward(&mut self, input: MigrationData) -> Result<MigrationData> {
        println!(
            "RemoveSettingsMigration({:?}) has no work to do on downgrade.",
            self.0
        );
        Ok(input)
    }
}

// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

/// We use this migration when we replace a setting's old string value with a new string value.
pub struct ReplaceStringMigration {
    pub setting: &'static str,
    pub old_val: &'static str,
    pub new_val: &'static str,
}

impl Migration for ReplaceStringMigration {
    fn forward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
        if let Some(data) = input.data.get_mut(self.setting) {
            match data {
                serde_json::Value::String(data) => {
                    if data == self.old_val {
                        self.new_val.clone_into(data);
                        println!(
                            "Changed value of '{}' from '{}' to '{}' on upgrade",
                            self.setting, self.old_val, self.new_val
                        );
                    } else {
                        println!(

Review comment (suggestion):
The migration's regex (.+) captures any characters, which is more than we allow in [host/bootstrap containers ](https://github.com/bottlerocket-os/bottlerocket-settings-sdk/blob/develop/bottlerocket-settings-models/modeled-types/src/shared.rs#L384-L392)today. 

But this makes sense to make it more extensible for future situations, since this is in `common`



Code after:
    pub prefix: &'static str,
    pub suffix: &'static str,
}
pub struct AddPrefixSuffixMigration(pub Vec<PrefixSuffix>);

impl Migration for AddPrefixSuffixMigration {
    /// New versions must either have a default for the settings or generate them; we don't need to
    /// do anything.
    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
        println!(
            "AddPrefixSuffixMigration({:?}) has no work to do on upgrade.",
            self.0
                .iter()
                .map(|ps| format!("{}*{}", ps.prefix, ps.suffix))
                .collect::<Vec<_>>()
        );
        Ok(input)
    }

    /// Older versions don't know about the settings; we remove them so that old versions don't see
    /// them and fail deserialization.
    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
        let mut compiled_patterns = Vec::new();
        for pattern in &self.0 {
            let regex_pattern = format!(
                r"^{}\.(.+)\.{}$",
                regex::escape(pattern.prefix),
                regex::escape(pattern.suffix)
            );
            let regex =
                Regex::new(&regex_pattern).context(error::InvalidPrefixSuffixPatternSnafu {
                    prefix: pattern.prefix,
                    suffix: pattern.suffix,
                })?;
            compiled_patterns.push(regex);
        }

        let settings = input
            .data
            .keys()
            .filter(|k| compiled_patterns.iter().any(|regex| regex.is_match(k)))
            .cloned()
            .collect::<Vec<_>>();
        for setting in settings {
            if let Some(data) = input.data.remove(&setting) {
                println!("Removed {setting}, which was set to '{data}'");
            }
        }
        Ok(input)
    }
}