mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-27 15:57:09 +00:00
runtime-rs: add core toml::Value tree merging
This is the core functionality of merging config file fragments into the base config file. Our TOML parser crate doesn't seem to allow working at the level of TomlConfig instances like BurntSushi, used in the Golang runtime, does so we implement the required functionality at the level of toml::Value trees. Tests to verify basic requirements are included. Values set by a base config file and not touched by a subsequent drop-in should be preserved. Drop-in config file fragments should be able to change values set by the base config file and add settings not present in the base. Conversion of a merged tree into a mock TomlConfig-style structure is tested as well. Signed-off-by: Pavel Mores <pmores@redhat.com>
This commit is contained in:
parent
5457deb034
commit
cf785a1a23
214
src/libs/kata-types/src/config/drop_in.rs
Normal file
214
src/libs/kata-types/src/config/drop_in.rs
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright Red Hat
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
mod toml_tree_ops {
|
||||
// The following pair of functions implement toml::Value tree merging, with
|
||||
// the second argument being merged into the first one and consumed in the
|
||||
// process. The toml parser crate in use here doesn't support parsing into
|
||||
// a pre-existing (possibly pre-filled) TomlConfig instance but can parse
|
||||
// into a toml::Value tree so we use that instead. All files (base and
|
||||
// drop-ins) are initially parsed into toml::Value trees which are
|
||||
// subsequently merged. Only when the fully merged tree is computed it is
|
||||
// converted to a TomlConfig instance.
|
||||
|
||||
fn merge_tables(base_table: &mut toml::value::Table, dropin_table: toml::value::Table) {
|
||||
for (key, val) in dropin_table.into_iter() {
|
||||
match base_table.get_mut(&key) {
|
||||
Some(base_val) => merge(base_val, val),
|
||||
None => {
|
||||
base_table.insert(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(base: &mut toml::Value, dropin: toml::Value) {
|
||||
match dropin {
|
||||
toml::Value::Table(dropin_table) => {
|
||||
if let toml::Value::Table(base_table) = base {
|
||||
merge_tables(base_table, dropin_table);
|
||||
} else {
|
||||
*base = toml::Value::Table(dropin_table);
|
||||
}
|
||||
}
|
||||
|
||||
_ => *base = dropin,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Mock config structure to stand in for TomlConfig for low-level
|
||||
// toml::Value trees merging.
|
||||
#[derive(Deserialize, Debug, Default, PartialEq)]
|
||||
struct SubConfig {
|
||||
#[serde(default)]
|
||||
another_string: String,
|
||||
#[serde(default)]
|
||||
yet_another_number: i32,
|
||||
#[serde(default)]
|
||||
sub_array: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default, PartialEq)]
|
||||
struct Config {
|
||||
#[serde(default)]
|
||||
number: i32,
|
||||
#[serde(default)]
|
||||
string: String,
|
||||
#[serde(default)]
|
||||
another_number: u8,
|
||||
#[serde(default)]
|
||||
array: Vec<i32>,
|
||||
|
||||
#[serde(default)]
|
||||
sub: SubConfig,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropin_does_not_interfere_with_base() {
|
||||
let mut base: toml::Value = toml::from_str(
|
||||
r#"
|
||||
number = 42
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dropin: toml::Value = toml::from_str(
|
||||
r#"
|
||||
string = "foo"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge(&mut base, dropin);
|
||||
|
||||
assert_eq!(
|
||||
base.try_into(),
|
||||
Ok(Config {
|
||||
number: 42,
|
||||
string: "foo".into(),
|
||||
sub: Default::default(),
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropin_overrides_base() {
|
||||
let mut base: toml::Value = toml::from_str(
|
||||
r#"
|
||||
number = 42
|
||||
[sub]
|
||||
another_string = "foo"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dropin: toml::Value = toml::from_str(
|
||||
r#"
|
||||
number = 43
|
||||
[sub]
|
||||
another_string = "bar"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge(&mut base, dropin);
|
||||
|
||||
assert_eq!(
|
||||
base.try_into(),
|
||||
Ok(Config {
|
||||
number: 43,
|
||||
sub: SubConfig {
|
||||
another_string: "bar".into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropin_extends_base() {
|
||||
let mut base: toml::Value = toml::from_str(
|
||||
r#"
|
||||
number = 42
|
||||
[sub]
|
||||
another_string = "foo"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dropin: toml::Value = toml::from_str(
|
||||
r#"
|
||||
string = "hello"
|
||||
[sub]
|
||||
yet_another_number = 13
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge(&mut base, dropin);
|
||||
|
||||
assert_eq!(
|
||||
base.try_into(),
|
||||
Ok(Config {
|
||||
number: 42,
|
||||
string: "hello".into(),
|
||||
sub: SubConfig {
|
||||
another_string: "foo".into(),
|
||||
yet_another_number: 13,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Drop-ins can change the type of a value. This might look weird but at
|
||||
// this level we have no idea about semantics so we just do what the
|
||||
// .toml's tell us. The final type check is only performed by try_into().
|
||||
// Also, we don't necessarily test this because it's a desired feature.
|
||||
// It's just something that seems to follow from the way Value tree
|
||||
// merging is implemented so why not acknowledge and verify it.
|
||||
#[test]
|
||||
fn dropin_overrides_base_type() {
|
||||
let mut base: toml::Value = toml::from_str(
|
||||
r#"
|
||||
number = "foo"
|
||||
[sub]
|
||||
another_string = 42
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dropin: toml::Value = toml::from_str(
|
||||
r#"
|
||||
number = 42
|
||||
[sub]
|
||||
another_string = "foo"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge(&mut base, dropin);
|
||||
|
||||
assert_eq!(
|
||||
base.try_into(),
|
||||
Ok(Config {
|
||||
number: 42,
|
||||
sub: SubConfig {
|
||||
another_string: "foo".into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user