Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migration-helpers: AddSettingsMigration, RemoveSettingsMigration for migrating multiple settings at once #958

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sources/api/migration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ If we're confident no user configuration has changed, or if the user wants to di
Say we add a new open-source application, like rngd.
We can add data regarding settings, services, and configuration-files of the application to our data model.

In this case, we can use the existing helper `AddSettingMigration`.
In this case, we can use the existing helper `AddSettingsMigration`.
It doesn't need to do anything on upgrade, because the new key will be populated by its default value.
On downgrade, it removes the setting, so that the old data store model doesn't see an unexpected key and reject the data.

### Application version upgrade

If we upgrade an important application, its available and required settings may change.
This means we'd have to update the data model to include any new or changed settings, and we'd write migrations to transform data from the old settings to the new.
This can likely be handled by existing helpers `AddSettingMigration`, `RemoveSettingMigration`, `ReplaceStringMigration`, and `ReplaceTemplateMigration`.
This can likely be handled by existing helpers `AddSettingsMigration`, `RemoveSettingsMigration`, `ReplaceStringMigration`, and `ReplaceTemplateMigration`.

### Data store implementation change

Expand Down
92 changes: 68 additions & 24 deletions sources/api/migration/migration-helpers/src/common_migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,104 @@ use serde::Serialize;
use snafu::{OptionExt, ResultExt};
use std::collections::HashMap;

/// We use this migration when we add a setting and want to make sure it's removed before we go
/// back to old versions that don't understand it.
pub struct AddSettingMigration(pub &'static str);
/// We use this migration when we add settings and want to make sure they're removed before we go
/// back to old versions that don't understand them.
pub struct AddSettingsMigration<'a>(pub &'a [&'static str]);

impl Migration for AddSettingMigration {
/// New versions must either have a default for the setting or generate it; we don't need to
impl Migration for AddSettingsMigration<'_> {
/// 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!("AddSettingMigration({}) has no work to do on upgrade.", self.0);
println!(
"AddSettingsMigration({:?}) has no work to do on upgrade.",
self.0
);
Ok(input)
}

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

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

/// We use this migration when we remove a setting from the model, so the new version doesn't see
/// it and error.
pub struct RemoveSettingMigration(pub &'static str);
/// Similar to the above, this migration is for when we add a single setting.
/// We are retaining this migration helper in case there are migrations already using it.
#[deprecated(note = "Please use `AddSettingsMigration` instead")]
pub struct AddSettingMigration(pub &'static str);

impl Migration for RemoveSettingMigration {
/// Newer versions don't know about the setting; we remove it so that new versions don't see
/// it and fail deserialization. (The setting must be defaulted or generated in old versions,
impl Migration for AddSettingMigration {
fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
AddSettingsMigration(&[self.0]).forward(input)
}

fn backward(&mut self, input: MigrationData) -> Result<MigrationData> {
AddSettingsMigration(&[self.0]).backward(input)
}
}

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

/// We use this migration when we remove settings from the model, so the new version doesn't see
/// 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> {
if let Some(data) = input.data.remove(self.0) {
println!("Removed {}, which was set to '{}'", self.0, data);
} else {
println!("Found no {} to remove", self.0);
for setting in self.0 {
if let Some(data) = input.data.remove(*setting) {
println!("Removed {}, which was set to '{}'", setting, data);
} else {
println!("Found no {} to remove", setting);
}
}
Ok(input)
}

/// Old versions must either have a default for the setting or generate it; we don't need to
/// 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!("RemoveSettingMigration({}) has no work to do on downgrade.", self.0);
println!(
"RemoveSettingsMigration({:?}) has no work to do on downgrade.",
self.0
);
Ok(input)
}
}

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

/// Similar to the above, this migration is for when we need to remove a single setting.
/// We are retaining this migration helper in case there are migrations already using it.
#[deprecated(note = "Please use `RemoveSettingsMigration` instead")]
pub struct RemoveSettingMigration(pub &'static str);

impl Migration for RemoveSettingMigration {
fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
RemoveSettingsMigration(&[self.0]).forward(input)
}

fn backward(&mut self, input: MigrationData) -> Result<MigrationData> {
RemoveSettingsMigration(&[self.0]).backward(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,
Expand Down