treefile: Support multiple includes

I'm working on having Silverblue inherit from Fedora CoreOS.  But
conceptually it also inherits from (parts of) Workstation.
It is just easier if we support multiple inheritance, then I don't
need to think too hard about how to make it a single inheritance chain.

Closes: #1870
Approved by: jlebon
This commit is contained in:
Colin Walters 2019-07-14 18:09:41 +00:00 committed by Atomic Bot
parent 4418589ca1
commit 00bd491fe2
3 changed files with 49 additions and 13 deletions

View File

@ -216,11 +216,13 @@ It supports the following parameters:
are provided, then `postprocess-script` will be executed after all are provided, then `postprocess-script` will be executed after all
other `postprocess`. other `postprocess`.
* `include`: string, optional: Path to another treefile which will be * `include`: string or array of string, optional: Path(s) to treefiles which will be
used as an inheritance base. The semantics for inheritance are: used as an inheritance base. The semantics for inheritance are:
Non-array values in child values override parent values. Array Non-array values in child values override parent values. Array
values are concatenated. Filenames will be resolved relative to values are concatenated. Filenames will be resolved relative to
the including treefile. the including treefile. Since rpm-ostree 2019.5, this value may
also be an array of strings. Including the same file multiple times
is an error.
* `container`: boolean, optional: Defaults to `false`. If `true`, then * `container`: boolean, optional: Defaults to `false`. If `true`, then
rpm-ostree will not do any special handling of kernel, initrd or the rpm-ostree will not do any special handling of kernel, initrd or the

View File

@ -21,7 +21,7 @@
* */ * */
use c_utf8::CUtf8Buf; use c_utf8::CUtf8Buf;
use failure::Fallible; use failure::{Fallible, bail};
use openat; use openat;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_json; use serde_json;
@ -30,6 +30,8 @@ use std::collections::{HashMap, BTreeMap};
use std::io::prelude::*; use std::io::prelude::*;
use std::path::Path; use std::path::Path;
use std::{collections, fs, io}; use std::{collections, fs, io};
use std::os::unix::fs::MetadataExt;
use std::collections::btree_map::Entry;
use crate::utils; use crate::utils;
@ -213,14 +215,28 @@ fn load_passwd_file<P: AsRef<Path>>(
return Ok(None); return Ok(None);
} }
type IncludeMap = collections::BTreeMap<(u64, u64), String>;
/// Given a treefile filename and an architecture, parse it and also /// Given a treefile filename and an architecture, parse it and also
/// open its external files. /// open its external files.
fn treefile_parse<P: AsRef<Path>>( fn treefile_parse<P: AsRef<Path>>(
filename: P, filename: P,
basearch: Option<&str>, basearch: Option<&str>,
seen_includes: &mut IncludeMap,
) -> Fallible<ConfigAndExternals> { ) -> Fallible<ConfigAndExternals> {
let filename = filename.as_ref(); let filename = filename.as_ref();
let mut f = io::BufReader::new(utils::open_file(filename)?); let f = utils::open_file(filename)?;
let meta = f.metadata()?;
let devino = (meta.dev(), meta.ino());
match seen_includes.entry(devino) {
Entry::Occupied(_) => {
bail!("Include loop detected; {} was already included", filename.to_str().unwrap())
},
Entry::Vacant(e) => {
e.insert(filename.to_str().unwrap().to_string());
}
};
let mut f = io::BufReader::new(f);
let basename = filename let basename = filename
.file_name() .file_name()
.map(|s| s.to_string_lossy()) .map(|s| s.to_string_lossy())
@ -379,11 +395,16 @@ fn treefile_parse_recurse<P: AsRef<Path>>(
filename: P, filename: P,
basearch: Option<&str>, basearch: Option<&str>,
depth: u32, depth: u32,
seen_includes: &mut IncludeMap,
) -> Fallible<ConfigAndExternals> { ) -> Fallible<ConfigAndExternals> {
let filename = filename.as_ref(); let filename = filename.as_ref();
let mut parsed = treefile_parse(filename, basearch)?; let mut parsed = treefile_parse(filename, basearch, seen_includes)?;
let include_path = parsed.config.include.take(); let include = parsed.config.include.take().unwrap_or_else(|| Include::Multiple(Vec::new()));
if let &Some(ref include_path) = &include_path { let includes = match include {
Include::Single(v) => vec![v],
Include::Multiple(v) => v,
};
for include_path in includes.iter() {
if depth == INCLUDE_MAXDEPTH { if depth == INCLUDE_MAXDEPTH {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
@ -393,7 +414,7 @@ fn treefile_parse_recurse<P: AsRef<Path>>(
} }
let parent = filename.parent().unwrap(); let parent = filename.parent().unwrap();
let include_path = parent.join(include_path); let include_path = parent.join(include_path);
let mut included = treefile_parse_recurse(include_path, basearch, depth + 1)?; let mut included = treefile_parse_recurse(include_path, basearch, depth + 1, seen_includes)?;
treefile_merge(&mut parsed.config, &mut included.config); treefile_merge(&mut parsed.config, &mut included.config);
treefile_merge_externals(&mut parsed.externals, &mut included.externals); treefile_merge_externals(&mut parsed.externals, &mut included.externals);
} }
@ -419,7 +440,8 @@ impl Treefile {
basearch: Option<&str>, basearch: Option<&str>,
workdir: openat::Dir, workdir: openat::Dir,
) -> Fallible<Box<Treefile>> { ) -> Fallible<Box<Treefile>> {
let mut parsed = treefile_parse_recurse(filename, basearch, 0)?; let mut seen_includes = collections::BTreeMap::new();
let mut parsed = treefile_parse_recurse(filename, basearch, 0, &mut seen_includes)?;
parsed.config = parsed.config.substitute_vars()?; parsed.config = parsed.config.substitute_vars()?;
Treefile::validate_config(&parsed.config)?; Treefile::validate_config(&parsed.config)?;
let dfd = openat::Dir::open(filename.parent().unwrap())?; let dfd = openat::Dir::open(filename.parent().unwrap())?;
@ -612,6 +634,13 @@ struct Rojig {
description: Option<String>, description: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum Include {
Single(String),
Multiple(Vec<String>)
}
// Because of how we handle includes, *everything* here has to be // Because of how we handle includes, *everything* here has to be
// Option<T>. The defaults live in the code (e.g. machineid-compat defaults // Option<T>. The defaults live in the code (e.g. machineid-compat defaults
// to `true`). // to `true`).
@ -634,7 +663,7 @@ struct TreeComposeConfig {
#[serde(rename = "gpg-key")] #[serde(rename = "gpg-key")]
gpg_key: Option<String>, gpg_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
include: Option<String>, include: Option<Include>,
// Core content // Core content
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@ -6,15 +6,20 @@ dn=$(cd $(dirname $0) && pwd)
. ${dn}/libcomposetest.sh . ${dn}/libcomposetest.sh
prepare_compose_test "misc-tweaks" prepare_compose_test "misc-tweaks"
# No docs # No docs, also test multi includes
pysetjsonmember "documentation" "False" cat >composedata/documentation.yaml <<'EOF'
documentation: false
EOF
cat > composedata/recommends.yaml <<'EOF'
recommends: false
EOF
pysetjsonmember "include" '["documentation.yaml", "recommends.yaml"]'
# Note this overrides: # Note this overrides:
# $ rpm -q systemd # $ rpm -q systemd
# systemd-238-8.git0e0aa59.fc28.x86_64 # systemd-238-8.git0e0aa59.fc28.x86_64
# $ rpm -qlv systemd|grep -F 'system/default.target ' # $ rpm -qlv systemd|grep -F 'system/default.target '
# lrwxrwxrwx 1 root root 16 May 11 06:59 /usr/lib/systemd/system/default.target -> graphical.target # lrwxrwxrwx 1 root root 16 May 11 06:59 /usr/lib/systemd/system/default.target -> graphical.target
pysetjsonmember "default_target" '"multi-user.target"' pysetjsonmember "default_target" '"multi-user.target"'
pysetjsonmember "recommends" 'False'
pysetjsonmember "units" '["tuned.service"]' pysetjsonmember "units" '["tuned.service"]'
# And test adding/removing files # And test adding/removing files
pysetjsonmember "add-files" '[["foo.txt", "/usr/etc/foo.txt"], pysetjsonmember "add-files" '[["foo.txt", "/usr/etc/foo.txt"],