From c94bd08b02499a3f2a5804f5c2ba207a0afbda18 Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Tue, 4 Jun 2019 11:09:35 +0200 Subject: [PATCH] treefile: allow ${releasever} in more keys Besides allowing ${releasever}, only do the substitution as the final pass after merging the treefiles for all the keys (currently ${basearch} and ${releasever}) instead of doing it per parse. This way we have the expected semantics where one could do: ``` include: "fedora-coreos.yaml" releasever: "42" ``` and have that releasever used. Fixes #1809 Signed-off-by: Rafael Fonseca Closes: #1848 Approved by: cgwalters --- rust/Cargo.lock | 10 +++++ rust/Cargo.toml | 1 + rust/src/treefile.rs | 87 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 00912c1e08..c3c5b61c7d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -216,6 +216,14 @@ name = "encode_unicode" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "envsubst" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.5" @@ -631,6 +639,7 @@ dependencies = [ "c_utf8 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "curl 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", + "envsubst 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -937,6 +946,7 @@ dependencies = [ "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd" +"checksum envsubst 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13cf19a8d534c83456ea13365a1935810a39f0e43bf1ec9371077e1966da396a" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a45178306a..c6cc943d2d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -23,6 +23,7 @@ c_utf8 = "0.1.0" systemd = "0.4.0" indicatif = "0.11.0" lazy_static = "1.1.0" +envsubst = "0.1.0" [lib] name = "rpmostree_rust" diff --git a/rust/src/treefile.rs b/rust/src/treefile.rs index 3c6f516a67..dfcfc02a30 100644 --- a/rust/src/treefile.rs +++ b/rust/src/treefile.rs @@ -130,19 +130,6 @@ fn treefile_parse_stream( .into()); } - // Substitute ${basearch} - treefile.treeref = match (basearch, treefile.treeref.take()) { - (Some(basearch), Some(treeref)) => { - let mut varsubsts = HashMap::new(); - varsubsts.insert("basearch".to_string(), basearch.to_string()); - Some( - utils::varsubst(&treeref, &varsubsts) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?, - ) - } - (_, v) => v, - }; - // Special handling for packages, since we allow whitespace within items. // We also canonicalize bootstrap_packages to packages here so it's // easier to append the basearch packages after. @@ -410,7 +397,8 @@ impl Treefile { basearch: Option<&str>, workdir: openat::Dir, ) -> Fallible> { - let parsed = treefile_parse_recurse(filename, basearch, 0)?; + let mut parsed = treefile_parse_recurse(filename, basearch, 0)?; + parsed.config = parsed.config.substitute_vars()?; Treefile::validate_config(&parsed.config)?; let dfd = openat::Dir::open(filename.parent().unwrap())?; let (rojig_name, rojig_spec) = if let Some(rojig) = parsed.config.rojig.as_ref() { @@ -718,6 +706,39 @@ impl TreeComposeConfig { Ok(self) } + + /// Look for use of ${variable} and replace it by its proper value + fn substitute_vars(mut self) -> Fallible { + let mut substvars: collections::HashMap = collections::HashMap::new(); + // Substitute ${basearch} and ${releasever} + if let Some(arch) = &self.basearch { + substvars.insert("basearch".to_string(), arch.clone()); + } + if let Some(releasever) = &self.releasever { + substvars.insert("releasever".to_string(), releasever.clone()); + } + envsubst::validate_vars(&substvars)?; + + macro_rules! substitute_field { + ( $field:ident ) => {{ + if let Some(value) = self.$field.take() { + self.$field = if envsubst::is_templated(&value) { + match envsubst::substitute(value, &substvars) { + Ok(s) => Some(s), + Err(e) => return Err(e), + } + } else { + Some(value) + } + } + }}; + }; + substitute_field!(treeref); + substitute_field!(automatic_version_prefix); + substitute_field!(mutate_os_release); + + Ok(self) + } } #[cfg(test)] @@ -753,8 +774,9 @@ packages-s390x: #[test] fn basic_valid() { let mut input = io::BufReader::new(VALID_PRELUDE.as_bytes()); - let treefile = + let mut treefile = treefile_parse_stream(InputFormat::YAML, &mut input, Some(ARCH_X86_64)).unwrap(); + treefile = treefile.substitute_vars().unwrap(); assert!(treefile.treeref.unwrap() == "exampleos/x86_64/blah"); assert!(treefile.packages.unwrap().len() == 5); } @@ -785,8 +807,9 @@ remove-files: #[test] fn basic_js_valid() { let mut input = io::BufReader::new(VALID_PRELUDE_JS.as_bytes()); - let treefile = + let mut treefile = treefile_parse_stream(InputFormat::JSON, &mut input, Some(ARCH_X86_64)).unwrap(); + treefile = treefile.substitute_vars().unwrap(); assert!(treefile.treeref.unwrap() == "exampleos/x86_64/blah"); assert!(treefile.packages.unwrap().len() == 5); } @@ -794,7 +817,8 @@ remove-files: #[test] fn basic_valid_noarch() { let mut input = io::BufReader::new(VALID_PRELUDE.as_bytes()); - let treefile = treefile_parse_stream(InputFormat::YAML, &mut input, None).unwrap(); + let mut treefile = treefile_parse_stream(InputFormat::YAML, &mut input, None).unwrap(); + treefile = treefile.substitute_vars().unwrap(); assert!(treefile.treeref.unwrap() == "exampleos/x86_64/blah"); assert!(treefile.packages.unwrap().len() == 3); } @@ -802,7 +826,9 @@ remove-files: fn append_and_parse(append: &'static str) -> TreeComposeConfig { let buf = VALID_PRELUDE.to_string() + append; let mut input = io::BufReader::new(buf.as_bytes()); - treefile_parse_stream(InputFormat::YAML, &mut input, Some(ARCH_X86_64)).unwrap() + let treefile = + treefile_parse_stream(InputFormat::YAML, &mut input, Some(ARCH_X86_64)).unwrap(); + treefile.substitute_vars().unwrap() } fn test_invalid(data: &'static str) { @@ -817,6 +843,31 @@ remove-files: } } + #[test] + fn basic_valid_releasever() { + let buf = r###" +ref: "exampleos/${basearch}/${releasever}" +releasever: 30 +automatic-version-prefix: ${releasever} +mutate-os-release: ${releasever} +"###; + let mut input = io::BufReader::new(buf.as_bytes()); + let mut treefile = + treefile_parse_stream(InputFormat::YAML, &mut input, Some(ARCH_X86_64)).unwrap(); + treefile = treefile.substitute_vars().unwrap(); + assert!(treefile.treeref.unwrap() == "exampleos/x86_64/30"); + assert!(treefile.releasever.unwrap() == "30"); + assert!(treefile.automatic_version_prefix.unwrap() == "30"); + assert!(treefile.mutate_os_release.unwrap() == "30"); + } + + #[test] + fn test_valid_no_releasever() { + let treefile = append_and_parse("automatic_version_prefix: ${releasever}"); + assert!(treefile.releasever == None); + assert!(treefile.automatic_version_prefix.unwrap() == "${releasever}"); + } + #[test] fn basic_valid_legacy() { let treefile = append_and_parse(