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

Allow for variant-specific default settings #613

Merged
merged 1 commit into from
Dec 20, 2019
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
123 changes: 117 additions & 6 deletions workspaces/api/storewolf/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use std::os::unix::fs::symlink;
use std::path::Path;
use std::str::FromStr;
use std::{env, fs, process};
use toml::{map::Entry, Value};

use apiserver::datastore::key::{Key, KeyType};
use apiserver::datastore::serialization::{to_pairs, to_pairs_with_prefix};
Expand Down Expand Up @@ -57,8 +58,11 @@ mod error {
source: DataStoreVersionError,
},

#[snafu(display("defaults.toml is not valid TOML: {}", source))]
DefaultsFormatting { source: toml::de::Error },
#[snafu(display("{} is not valid TOML: {}", file, source))]
DefaultsFormatting {
file: String,
source: toml::de::Error,
},

#[snafu(display("defaults.toml is not a TOML table"))]
DefaultsNotTable {},
Expand All @@ -72,6 +76,9 @@ mod error {
#[snafu(display("defaults.toml's metadata has unexpected types"))]
DefaultsMetadataUnexpectedFormat {},

#[snafu(display("defaults.toml data types do not match types defined in current variant's override-defaults.toml"))]
DefaultsVariantDoesNotMatch {},

#[snafu(display("Error querying datstore for populated keys: {}", source))]
QueryData { source: datastore::Error },

Expand Down Expand Up @@ -274,6 +281,56 @@ fn parse_metadata_toml(md_toml_val: toml::Value) -> Result<Vec<model::Metadata>>
Ok(def_metadatas)
}

/// This modifies the first given toml Value by inserting any values from the second Value.
///
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
/// This is done recursively. Any time a scalar or array is seen, the left side is set to the
/// right side. Any time a table is seen, we iterate through the keys of the tables; if the left
/// side does not have the key from the right side, it's inserted, otherwise we recursively merge
/// the values in each table for that key.
///
/// If at any point in the recursion the data types of the two values does not match, we error.
fn merge_values<'a>(merge_into: &'a mut Value, merge_from: &'a Value) -> Result<()> {
// If the types of left and right don't match, we have inconsistent models, and shouldn't try
// to merge them.
ensure!(
merge_into.same_type(&merge_from),
error::DefaultsVariantDoesNotMatch
);

match merge_from {
// If we see a scalar, we replace the left with the right. We treat arrays like scalars so
// behavior is clear - no question about whether we're appending right onto left, etc.
Value::String(_)
| Value::Integer(_)
| Value::Float(_)
| Value::Boolean(_)
| Value::Datetime(_)
| Value::Array(_) => *merge_into = merge_from.clone(),

// If we see a table, we recursively merge each key.
Value::Table(t2) => {
// We know the other side is a table because of the `ensure` above.
let t1 = merge_into.as_table_mut().unwrap();
for (k2, v2) in t2.iter() {
// Check if the left has the same key as the right.
match t1.entry(k2) {
// If not, we can just insert the value.
Entry::Vacant(e) => {
e.insert(v2.clone());
}
// If so, we need to recursively merge; we don't want to replace an entire
// table, for example, because the left may have some distinct inner keys.
Entry::Occupied(ref mut e) => {
merge_values(e.get_mut(), v2)?;
}
}
}
}
}

Ok(())
}

/// Creates a new FilesystemDataStore at the given path, with data and metadata coming from
/// defaults.toml at compile time.
fn populate_default_datastore<P: AsRef<Path>>(
Expand Down Expand Up @@ -309,13 +366,24 @@ fn populate_default_datastore<P: AsRef<Path>>(
create_new_datastore(&base_path, version)?;
}

// Read and parse defaults
let defaults_str = include_str!("../../../models/src/variant/current/defaults.toml");
// Read and parse shared defaults
let defaults_str = include_str!("../../../models/defaults.toml");
let mut defaults_val: toml::Value =
toml::from_str(defaults_str).context(error::DefaultsFormatting)?;
toml::from_str(defaults_str).context(error::DefaultsFormatting {
file: "defaults.toml",
})?;

// Merge in any defaults for the current variant
let variant_defaults_str =
include_str!("../../../models/src/variant/current/override-defaults.toml");
let variant_defaults_val: toml::Value =
toml::from_str(variant_defaults_str).context(error::DefaultsFormatting {
file: "override_defaults.toml",
})?;
merge_values(&mut defaults_val, &variant_defaults_val)?;

// Check if we have metadata and settings. If so, pull them out
// of `defaults_val`
// of `shared_defaults_val`
let table = defaults_val
.as_table_mut()
.context(error::DefaultsNotTable)?;
Expand Down Expand Up @@ -560,3 +628,46 @@ fn main() {
process::exit(1);
}
}

#[cfg(test)]
mod test {
use super::merge_values;
use toml::toml;

#[test]
fn merge() {
let mut left = toml! {
top1 = "left top1"
top2 = "left top2"
[settings.inner]
inner_setting1 = "left inner_setting1"
inner_setting2 = "left inner_setting2"
};
let right = toml! {
top1 = "right top1"
[settings]
setting = "right setting"
[settings.inner]
inner_setting1 = "right inner_setting1"
inner_setting3 = "right inner_setting3"
};
// Can't comment inside this toml, unfortunately.
// "top1" is being overwritten from right.
// "top2" is only in the left and remains.
// "setting" is only in the right side.
// "inner" tests that recursion works; inner_setting1 is replaced, 2 is untouched, and
// 3 is new.
let expected = toml! {
top1 = "right top1"
top2 = "left top2"
[settings]
setting = "right setting"
[settings.inner]
inner_setting1 = "right inner_setting1"
inner_setting2 = "left inner_setting2"
inner_setting3 = "right inner_setting3"
};
merge_values(&mut left, &right).unwrap();
assert_eq!(left, expected);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# OS-level defaults.
# Should match the structures in the model definition.
# Here we define default values for the settings in the API model.
# Variant builds can override or supplement these in
# src/VARIANT/override-defaults.toml.

# The structures, fields, and types here need to match those of the API model,
# as defined in src/VARIANT/mod.rs.

[settings]
timezone = "America/Los_Angeles"
Expand Down Expand Up @@ -28,7 +32,7 @@ restart-commands = []

[configuration-files.containerd-config-toml]
path = "/etc/containerd/config.toml"
template-path = "/usr/share/templates/containerd-config-toml_aws-dev"
template-path = "/usr/share/templates/containerd-config-toml"


# Updog.
Expand Down
3 changes: 3 additions & 0 deletions workspaces/models/src/aws-dev/override-defaults.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[configuration-files.containerd-config-toml]
# No override to path
template-path = "/usr/share/templates/containerd-config-toml_aws-dev"
112 changes: 0 additions & 112 deletions workspaces/models/src/aws-k8s/defaults.toml

This file was deleted.

35 changes: 35 additions & 0 deletions workspaces/models/src/aws-k8s/override-defaults.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[configuration-files.containerd-config-toml]
# No override to path
template-path = "/usr/share/templates/containerd-config-toml_aws-k8s"

# Kubernetes.

[services.kubernetes]
configuration-files = ["kubelet-env", "kubelet-config", "kubelet-kubeconfig", "kubernetes-ca-crt"]
restart-commands = []

[configuration-files.kubelet-env]
path = "/etc/kubernetes/kubelet/env"
template-path = "/usr/share/templates/kubelet-env"

[configuration-files.kubelet-config]
path = "/etc/kubernetes/kubelet/config"
template-path = "/usr/share/templates/kubelet-config"

[configuration-files.kubelet-kubeconfig]
path = "/etc/kubernetes/kubelet/kubeconfig"
template-path = "/usr/share/templates/kubelet-kubeconfig"

[configuration-files.kubernetes-ca-crt]
path = "/etc/kubernetes/pki/ca.crt"
template-path = "/usr/share/templates/kubernetes-ca-crt"

[metadata.settings.kubernetes]
max-pods.setting-generator = "pluto max-pods"
cluster-dns-ip.setting-generator = "pluto cluster-dns-ip"
node-ip.setting-generator = "pluto node-ip"
affected-services = ["kubernetes"]

[metadata.settings.kubernetes.pod-infra-container-image]
setting-generator = "pluto pod-infra-container-image"
affected-services = ["kubernetes", "containerd"]