in sources/api/storewolf/src/main.rs [205:376]
fn populate_default_datastore<P: AsRef<Path>>(
base_path: P,
version: Option<Version>,
) -> Result<()> {
// NOTE: Variables prefixed with "def" refer to values from defaults.
//
// Variables prefixed with "existing..." refer to values from the
// existing datastore.
// There's a chain of symlinks that point to the directory where data
// actually lives. This is the start of the chain, whose name never
// changes, so it can be used consistently by the rest of the OS.
let datastore_path = base_path.as_ref().join("current");
let mut datastore = FilesystemDataStore::new(&datastore_path);
let mut existing_data = HashSet::new();
let mut existing_metadata = HashMap::new();
// If the "live" path of the datastore exists, query it for populated
// meta/data. Otherwise, create the datastore path.
let live_path = &datastore_path.join("live");
if live_path.exists() {
debug!("Gathering existing data from the datastore");
existing_metadata = datastore
.list_populated_metadata("", &None as &Option<&str>)
.context(error::QueryMetadata)?;
existing_data = datastore
.list_populated_keys("", &datastore::Committed::Live)
.context(error::QueryData)?;
} else {
info!("Creating datastore at: {}", &live_path.display());
create_new_datastore(&base_path, version).context(error::DatastoreCreation)?;
}
// Here we read in the merged settings file built by build.rs.
let defaults_str = include_str!(concat!(env!("OUT_DIR"), "/defaults.toml"));
let mut defaults_val: toml::Value =
toml::from_str(defaults_str).context(error::DefaultsFormatting {
path: concat!(env!("OUT_DIR"), "/defaults.toml"),
})?;
// Check if we have metadata and settings. If so, pull them out
// of `shared_defaults_val`
let table = defaults_val
.as_table_mut()
.context(error::DefaultsNotTable)?;
let maybe_metadata_val = table.remove("metadata");
let maybe_settings_val = table.remove("settings");
// If there are default settings, write them to the datastore in the shared pending
// transaction. This ensures the settings will go through a commit cycle when first-boot
// services run, which will create config files for default keys that require them.
if let Some(def_settings_val) = maybe_settings_val {
debug!("Serializing default settings and writing new ones to datastore");
let def_settings_table = def_settings_val
.as_table()
.context(error::DefaultSettingsNotTable)?;
// The default settings were removed from the "settings" key of the
// defaults table above. We still need them under a "settings" key
// before serializing so we have full dotted keys like
// "settings.foo.bar" and not just "foo.bar". We use a HashMap
// to rebuild the nested structure.
let def_settings = to_pairs_with_prefix("settings", &def_settings_table).context(
error::Serialization {
given: "default settings",
},
)?;
// For each of the default settings, check if it exists in the
// datastore. If not, add it to the map of settings to write
let mut settings_to_write = HashMap::new();
for (key, val) in def_settings {
if !existing_data.contains(&key) {
settings_to_write.insert(key, val);
}
}
trace!(
"Writing default settings to datastore: {:#?}",
&settings_to_write
);
let pending = datastore::Committed::Pending {
tx: constants::LAUNCH_TRANSACTION.to_string(),
};
datastore
.set_keys(&settings_to_write, &pending)
.context(error::WriteKeys)?;
}
// If we have metadata, write it out to the datastore in Live state
if let Some(def_metadata_val) = maybe_metadata_val {
debug!("Serializing metadata and writing new keys to datastore");
// Create a Vec<Metadata> from the metadata toml::Value
let def_metadatas = parse_metadata_toml(def_metadata_val)?;
// Before this transformation, `existing_metadata` is a
// map of data key to set of metadata keys:
// HashMap(dataKey => HashSet(metaKey)).
//
// To make comparison easier, we
// flatten the map to a HashSet of tuples:
// HashSet((dataKey, metaKey)).
let existing_metadata: HashSet<(&Key, &Key)> = existing_metadata
.iter()
.flat_map(|data| data.1.iter().map(move |md_key| (data.0, md_key)))
.collect();
// For each of the default metadatas, check if it exists in the
// datastore. If not, add it to the set of metadatas to write
let mut metadata_to_write = HashSet::new();
for def_metadata in def_metadatas {
let model::Metadata { key, md, val } = def_metadata;
let data_key = Key::new(KeyType::Data, &key).context(error::InvalidKey {
key_type: KeyType::Data,
key,
})?;
let md_key = Key::new(KeyType::Meta, &md).context(error::InvalidKey {
key_type: KeyType::Meta,
key: md,
})?;
// Put the `data_key` and `md_key` tuple into a variable so we
// can more easily read the subsequent `contains()` call
let def_metadata_keypair = (&data_key, &md_key);
if !existing_metadata.contains(&def_metadata_keypair) {
let value =
datastore::serialize_scalar::<_, ScalarError>(&val).with_context(|| {
error::SerializeScalar {
given: format!("metadata value '{}'", val),
}
})?;
metadata_to_write.insert((md_key, data_key, value));
}
}
trace!(
"Writing default metadata to datastore: {:#?}",
metadata_to_write
);
for metadata in metadata_to_write {
let (md, key, val) = metadata;
datastore
.set_metadata(&md, &key, val)
.context(error::WriteMetadata)?;
}
}
// If any other defaults remain (configuration files, services, etc),
// write them to the datastore in Live state
debug!("Serializing other defaults and writing new ones to datastore");
let defaults = to_pairs(&defaults_val).context(error::Serialization {
given: "other defaults",
})?;
let mut other_defaults_to_write = HashMap::new();
if !defaults.is_empty() {
for (key, val) in defaults {
if !existing_data.contains(&key) {
other_defaults_to_write.insert(key, val);
}
}
trace!(
"Writing other default data to datastore: {:#?}",
&other_defaults_to_write
);
datastore
.set_keys(&other_defaults_to_write, &datastore::Committed::Live)
.context(error::WriteKeys)?;
}
Ok(())
}