/
githubmirror
/
rpm-ostree
Обзор
Документация
Войти
/
githubmirror
/
rpm-ostree
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
rust/src/origin.rs
496 строк
17 KB
Colin Walters
Fix use of deprecated GString::to_str()
15 янв 2025, 22:17
15 янв 2025, 22:17
0c0d9bb
Код
Авторство
О чём код?
//! An "origin" declares how we generated an OSTree commit. /* * Copyright (C) 2020 Red Hat, Inc. * * SPDX-License-Identifier: Apache-2.0 OR MIT */ use crate::cxxrsutil::*; use crate::treefile::Treefile; use anyhow::{anyhow, bail, Result}; use fn_error_context::context; use glib::KeyFile; use ostree_ext::glib; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::result::Result as StdResult; use ostree_ext::container::deploy::ORIGIN_CONTAINER; const ORIGIN: &str = "origin"; const RPMOSTREE: &str = "rpmostree"; const PACKAGES: &str = "packages"; const OVERRIDES: &str = "overrides"; /// The set of keys that we parse as BTreeMap and need to ignore ordering changes. static UNORDERED_LIST_KEYS: phf::Set<&'static str> = phf::phf_set! { "packages/requested", "packages/local", "packages/local-fileoverride", "overrides/remove", "overrides/replace-local" }; #[context("Parsing origin")] pub(crate) fn origin_to_treefile_inner(kf: &KeyFile) -> Result<Box<Treefile>> { let mut cfg: crate::treefile::TreeComposeConfig = Default::default(); let base_refspec = if let Some(r) = keyfile_get_optional_string(kf, ORIGIN, "refspec")? { Some(r) } else { keyfile_get_optional_string(kf, ORIGIN, "baserefspec")? }; let container_image_reference = keyfile_get_optional_string(kf, ORIGIN, ORIGIN_CONTAINER)?; match (base_refspec, container_image_reference) { (Some(_), Some(_)) => bail!("Found both refspec/baserefspec and {}", ORIGIN_CONTAINER), (None, None) => bail!( "Failed to find refspec/baserefspec/{} in origin", ORIGIN_CONTAINER ), (Some(s), None) => cfg.derive.base_refspec = Some(s), (None, Some(s)) => cfg.derive.container_image_reference = Some(s), } cfg.packages = parse_stringlist(kf, PACKAGES, "requested")?; cfg.derive.packages_local = parse_localpkglist(kf, PACKAGES, "requested-local")?; cfg.derive.packages_local_fileoverride = parse_localpkglist(kf, PACKAGES, "requested-local-fileoverride")?; cfg.derive.override_remove = parse_stringlist(kf, OVERRIDES, "remove")?; cfg.derive.override_replace_local = parse_localpkglist(kf, OVERRIDES, "replace-local")?; cfg.derive.unconfigured_state = keyfile_get_optional_string(kf, ORIGIN, "unconfigured-state")?; if let Some(strv) = parse_stringlist::<Vec<String>>(kf, OVERRIDES, "replace")? { let mut override_replace = Vec::new(); for s in strv { let mut split = s.split(','); let from = split .next() .ok_or_else(|| anyhow!("Invalid repo replacement: {}", s))?; let from_parsed = crate::daemon::parse_override_source(from)?; let source = match from_parsed.kind { crate::ffi::OverrideReplacementType::Repo => { crate::treefile::RemoteOverrideReplaceFrom::Repo(from_parsed.name) } _ => bail!("Unknown repo replacement source: {}", from), }; override_replace.push(crate::treefile::RemoteOverrideReplace { from: source, packages: split.map(|s| s.to_string()).collect(), }); } cfg.derive.override_replace = Some(override_replace); } let regenerate_initramfs = kf .boolean(RPMOSTREE, "regenerate-initramfs") .unwrap_or_default(); let initramfs_etc = parse_stringlist(kf, RPMOSTREE, "initramfs-etc")?; let initramfs_args = parse_stringlist(kf, RPMOSTREE, "initramfs-args")?; if regenerate_initramfs || initramfs_etc.is_some() || initramfs_args.is_some() { let initramfs = crate::treefile::DeriveInitramfs { regenerate: regenerate_initramfs, etc: initramfs_etc, args: initramfs_args, }; cfg.derive.initramfs = Some(initramfs); } if let Some(url) = keyfile_get_optional_string(kf, ORIGIN, "custom-url")? { let description = keyfile_get_optional_string(kf, ORIGIN, "custom-description")?; cfg.derive.custom = Some(crate::treefile::DeriveCustom { url, description }) } if map_keyfile_optional(kf.boolean(RPMOSTREE, "ex-cliwrap"))?.unwrap_or_default() { cfg.cliwrap = Some(true) } cfg.derive.override_commit = keyfile_get_optional_string(kf, ORIGIN, "override-commit")?; Ok(Box::new(Treefile::new_from_config(cfg)?)) } /// Convert an origin keyfile to a treefile config. /// /// For historical reasons, rpm-ostree has two file formats to represent /// state. This bridges parts of an origin file to a treefile that /// is understood by the core. pub(crate) fn origin_to_treefile(kf: &crate::ffi::GKeyFile) -> CxxResult<Box<Treefile>> { Ok(origin_to_treefile_inner(&kf.glib_reborrow())?) } /// Convert a treefile config to an origin keyfile. pub(crate) fn treefile_to_origin(tf: &Treefile) -> Result<*mut crate::FFIGKeyFile> { let kf = treefile_to_origin_inner(tf)?; let p = kf.as_ptr(); std::mem::forget(kf); Ok(p as *mut _) } /// Set a keyfile value to a string list. fn kf_set_string_list_optional<'a>( kf: &glib::KeyFile, group: impl AsRef<str>, k: impl AsRef<str>, vals: impl IntoIterator<Item = &'a str>, ) { let mut v = String::new(); for elt in vals { v.push_str(elt); v.push(';'); } if !v.is_empty() { kf.set_value(group.as_ref(), k.as_ref(), v.as_str()) } } fn set_sha256_nevra_pkgs( kf: &glib::KeyFile, group: &str, k: &str, pkgs: &BTreeMap<String, String>, ) { let pkgs: Vec<_> = pkgs .iter() .map(|(nevra, sha256)| format!("{}:{}", sha256, nevra)) .collect(); let pkgs = pkgs.iter().map(|s| s.as_str()); kf_set_string_list_optional(kf, group, k, pkgs) } /// Convert a treefile to an origin file. #[context("Parsing treefile origin")] fn treefile_to_origin_inner(tf: &Treefile) -> Result<glib::KeyFile> { let may_require_local_assembly = tf.may_require_local_assembly(); let tf = &tf.parsed; let kf = glib::KeyFile::new(); if let Some(r) = tf.derive.base_refspec.as_deref() { let k = if may_require_local_assembly { "baserefspec" } else { "refspec" }; kf.set_string(ORIGIN, k, r); } else if let Some(r) = tf.derive.container_image_reference.as_deref() { kf.set_string(ORIGIN, ORIGIN_CONTAINER, r); } else { unreachable!(); } // Packages if let Some(pkgs) = tf.packages.as_ref() { let pkgs = pkgs.iter().map(|s| s.as_str()); kf_set_string_list_optional(&kf, PACKAGES, "requested", pkgs) } if let Some(pkgs) = tf.derive.packages_local.as_ref() { set_sha256_nevra_pkgs(&kf, PACKAGES, "requested-local", pkgs) } if let Some(pkgs) = tf.derive.packages_local_fileoverride.as_ref() { set_sha256_nevra_pkgs(&kf, PACKAGES, "requested-local-fileoverride", pkgs) } if let Some(pkgs) = tf.derive.override_remove.as_ref() { let pkgs = pkgs.iter().map(|s| s.as_str()); kf_set_string_list_optional(&kf, OVERRIDES, "remove", pkgs) } if let Some(pkgs) = tf.derive.override_replace_local.as_ref() { set_sha256_nevra_pkgs(&kf, OVERRIDES, "replace-local", pkgs) } if let Some(v) = tf.derive.override_replace.as_ref() { let pkgs: Vec<String> = v .iter() .map(|ovr| { let src = ovr.from.to_string(); let mut v = vec![src.as_str()]; v.extend(ovr.packages.iter().map(|s| s.as_str())); v.join(",") }) .collect(); let pkgs = pkgs.iter().map(|s| s.as_str()); kf_set_string_list_optional(&kf, OVERRIDES, "replace", pkgs); } // Initramfs bits if let Some(initramfs) = tf.derive.initramfs.as_ref() { if initramfs.regenerate { kf.set_boolean(RPMOSTREE, "regenerate-initramfs", true); } if let Some(etc) = initramfs.etc.as_ref() { let etc = etc.iter().map(|s| s.as_str()); kf_set_string_list_optional(&kf, RPMOSTREE, "initramfs-etc", etc) } if let Some(args) = initramfs.args.as_deref() { let args = args.iter().map(|s| s.as_str()); kf_set_string_list_optional(&kf, RPMOSTREE, "initramfs-args", args) } } // Custom origin if let Some(custom) = tf.derive.custom.as_ref() { kf.set_string(ORIGIN, "custom-url", custom.url.as_str()); if let Some(desc) = custom.description.as_deref() { kf.set_string(ORIGIN, "custom-description", desc); } } if tf.cliwrap.unwrap_or_default() { kf.set_boolean(RPMOSTREE, "ex-cliwrap", true) } if let Some(c) = tf.derive.override_commit.as_deref() { kf.set_string(ORIGIN, "override-commit", c); } Ok(kf) } fn kf_diff_value(group: &str, key: &str, a: &str, b: &str) -> bool { if a == b { return true; } let path = format!("{}/{}", group, key); if !UNORDERED_LIST_KEYS.contains(path.as_str()) { return false; } let a: BTreeSet<_> = a.split(';').collect(); let b: BTreeSet<_> = b.split(';').collect(); a == b } /// Diff two key files. fn kf_diff(kf: &glib::KeyFile, newkf: &glib::KeyFile) -> Result<()> { let mut errs = Vec::new(); for grp in kf.groups().iter().map(|g| g.as_str()) { for k in kf.keys(grp)?.iter().map(|g| g.as_str()) { let origv = kf.value(grp, k)?; match newkf.value(grp, k) { Ok(newv) => { if !kf_diff_value(grp, k, origv.as_str(), newv.as_str()) { errs.push(format!( "Mismatched value for {}/{}: {} vs {}", grp, k, origv, newv )); } } Err(e) => errs.push(format!("Fetching {}/{}: {}", grp, k, e)), } } } for grp in newkf.groups().iter().map(|g| g.as_str()) { for k in newkf.keys(grp)?.iter().map(|g| g.as_str()) { if !kf.has_key(grp, k)? { errs.push(format!("Unexpected new key: {}/{}", grp, k)); } } } if !errs.is_empty() { return Err(anyhow!(errs.join("; "))); } Ok(()) } fn origin_validate_roundtrip_inner(kf: &glib::KeyFile) -> Result<()> { // Make a copy of our input so we can remove the transient fields. let kf_copy = glib::KeyFile::new(); kf_copy.load_from_data(kf.to_data().as_str(), glib::KeyFileFlags::NONE)?; let kf = kf_copy; // We don't translate transient fields drop(kf.remove_group("libostree-transient")); let tf = origin_to_treefile_inner(&kf)?; let newkf = treefile_to_origin_inner(&tf)?; // Compare the two origin keyfiles. This is the core check. kf_diff(&kf, &newkf)?; // And finally, triple-check things by round-tripping the origin // back to a treefile and asserting it's identical. // At the moment, we don't accept user-supplied treefiles as input // to this code. For now we fatally error if somehow they differed. // But in the future this check should be part of validating treefile // options that don't make sense on the client side. let newtf = origin_to_treefile_inner(&newkf)?; assert_eq!(tf.parsed, newtf.parsed); Ok(()) } /// Validate that an origin keyfile can be losslessly converted to a treefile config. /// /// For historical reasons, rpm-ostree has two file formats to represent /// state. This bridges parts of an origin file to a treefile that /// is understood by the core. pub(crate) fn origin_validate_roundtrip(kf: &crate::ffi::GKeyFile) { if let Some(e) = origin_validate_roundtrip_inner(&kf.glib_reborrow()).err() { tracing::debug!("Failed to roundtrip origin: {}", e); } } fn map_keyfile_optional<T>(res: StdResult<T, glib::Error>) -> StdResult<Option<T>, glib::Error> { match res { Ok(v) => Ok(Some(v)), Err(e) => { if let Some(t) = e.kind::<glib::KeyFileError>() { match t { glib::KeyFileError::GroupNotFound | glib::KeyFileError::KeyNotFound => Ok(None), _ => Err(e), } } else { Err(e) } } } } fn parse_stringlist<T>(kf: &KeyFile, group: &str, key: &str) -> Result<Option<T>> where T: std::iter::FromIterator<String>, { let r = map_keyfile_optional(kf.string_list(group, key))? .map(|o| o.into_iter().map(|s| s.to_string()).collect()); Ok(r) } fn parse_localpkglist( kf: &KeyFile, group: &str, key: &str, ) -> Result<Option<BTreeMap<String, String>>> { if let Some(v) = map_keyfile_optional(kf.string_list(group, key))? { let mut r = BTreeMap::new(); for s in v { let (nevra, sha256) = crate::utils::decompose_sha256_nevra(s.as_str())?; r.insert(nevra.to_string(), sha256.to_string()); } Ok(Some(r)) } else { Ok(None) } } fn keyfile_get_optional_string(kf: &KeyFile, group: &str, key: &str) -> Result<Option<String>> { Ok(map_keyfile_optional(kf.string(group, key))?.map(|v| v.to_string())) } #[cfg(test)] pub(crate) mod test { use super::*; use indoc::indoc; macro_rules! assert_err_containing { ( $e:expr, $expected_msg:expr ) => {{ let msg = $e.unwrap_err().to_string(); let expected = $expected_msg; if !msg.contains(expected) { panic!("Expected error to contain {}\nfound: {}", expected, msg) } }}; } pub(crate) const BASE: &str = indoc! {" [origin] refspec=foo:bar/x86_64/baz "}; pub(crate) const COMPLEX: &str = indoc! {" [origin] baserefspec=fedora:fedora/34/x86_64/silverblue override-commit=41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3 [rpmostree] regenerate-initramfs=true initramfs-args=-I;/etc/foobar.conf; initramfs-etc=/etc/cmdline.d/foobar.conf; [packages] requested=libvirt;fish; requested-local=4ed748ba060fce4571e7ef19f3f5ed6209f67dbac8327af0d38ea70b96d2f723:foo-1.2-3.x86_64; [overrides] remove=docker; replace-local=0c7072500af2758e7dc7d7700fed82c3c5f4da7453b4d416e79f75384eee96b0:rpm-ostree-devel-2021.1-2.fc33.x86_64;648ab3ff4d4b708ea180269297de5fa3e972f4481d47b7879c6329272e474d68:rpm-ostree-2021.1-2.fc33.x86_64;8b29b78d0ade6ec3aedb8e3846f036f6f28afe64635d83cb6a034f1004607678:rpm-ostree-libs-2021.1-2.fc33.x86_64; replace=repo=foobar,systemd;repo=bazboo,kernel,kernel-core,kernel-modules; [libostree-transient] pinned=true "}; pub(crate) fn kf_from_str(s: &str) -> Result<glib::KeyFile> { let kf = glib::KeyFile::new(); kf.load_from_data(s, glib::KeyFileFlags::KEEP_COMMENTS)?; Ok(kf) } #[test] fn test_kf_diff() -> Result<()> { let kf = kf_from_str(BASE)?; let kf2 = kf_from_str(BASE)?; kf_diff(&kf, &kf2).expect("No difference"); kf2.set_string(ORIGIN, "refspec", "foo:bar/x86_64/whee"); assert_err_containing!(kf_diff(&kf, &kf2), "Mismatched value"); let kf2 = kf_from_str(BASE)?; kf2.set_string(ORIGIN, "foospec", "foo:bar/x86_64/whee"); assert_err_containing!(kf_diff(&kf, &kf2), "Unexpected new key: origin/foospec"); Ok(()) } #[test] fn test_origin_parse() -> Result<()> { let kf = kf_from_str("[origin]\n")?; assert!(origin_to_treefile_inner(&kf).is_err()); let kf = kf_from_str(BASE)?; let tf = origin_to_treefile_inner(&kf)?; assert_eq!( tf.parsed.derive.base_refspec.as_ref().unwrap(), "foo:bar/x86_64/baz" ); let kf = kf_from_str(indoc! {" [origin] baserefspec=fedora/33/x86_64/silverblue [packages] requested=virt-manager;libvirt;pcsc-lite-ccid "})?; let tf = origin_to_treefile_inner(&kf)?; assert_eq!( tf.parsed.derive.base_refspec.as_ref().unwrap(), "fedora/33/x86_64/silverblue" ); let pkgs = tf.parsed.packages.as_ref().unwrap(); assert_eq!(pkgs.len(), 3); assert!(pkgs.contains("libvirt")); let kf = kf_from_str(COMPLEX)?; let tf = origin_to_treefile_inner(&kf)?; assert_eq!( tf.parsed.derive.override_commit.unwrap(), "41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3" ); assert_eq!( tf.parsed.derive.override_replace, Some(vec![ crate::treefile::RemoteOverrideReplace { from: crate::treefile::RemoteOverrideReplaceFrom::Repo("foobar".into()), packages: maplit::btreeset!("systemd".into()), }, crate::treefile::RemoteOverrideReplace { from: crate::treefile::RemoteOverrideReplaceFrom::Repo("bazboo".into()), packages: maplit::btreeset!( "kernel".into(), "kernel-core".into(), "kernel-modules".into() ), } ]) ); Ok(()) } #[test] fn test_origin_roundtrip() -> Result<()> { let kf = kf_from_str(BASE)?; origin_validate_roundtrip_inner(&kf).expect("validating BASE"); let kf = kf_from_str(COMPLEX)?; origin_validate_roundtrip_inner(&kf).expect("validating COMPLEX"); Ok(()) } }