From 37abf15b80798f7bd42381e95b1301dcd714b7d0 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 27 Jul 2022 16:08:26 -0400 Subject: [PATCH] yumdnf: Add a `yum image rebase` subcommand This wraps `rpm-ostree rebase --experimental ` - but notice that it *only* supports container flows. While here, we also remove the confusing `ostree-unverified-registry:` type prefix strings. Instead, the new happy path for a *signed* image (via e.g. sigstore) looks just like `yum image rebase quay.io/examplecorp/my-coreos:latest` and that's it! Instead of the string prefixes here, we have e.g. `yum image rebase --no-signature-verification quay.io/examplecorp/my-coreos:latest` which *translates* today to the existing `rpm-ostree rebase ostree-unverified-registry:quay.io/examplecorp/my-coreos:latest` The high level goal here is that in a cliwrap-based world, to get away from typing `rpm-ostree` and more broadly rebrand this interface and flow as "image-based yum". --- rust/src/cliwrap/yumdnf.rs | 95 +++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/rust/src/cliwrap/yumdnf.rs b/rust/src/cliwrap/yumdnf.rs index fbbda38670..170444df1e 100644 --- a/rust/src/cliwrap/yumdnf.rs +++ b/rust/src/cliwrap/yumdnf.rs @@ -68,6 +68,42 @@ enum Cmd { Clean { subargs: Vec, }, + /// Operate on ostree-based bootable container images + Image { + #[clap(subcommand)] + cmd: ImageCmd, + }, +} + +/// Switch the booted container image. +#[derive(Debug, Parser)] +#[clap(rename_all = "kebab-case")] +struct RebaseCmd { + /// Explicitly opt-out of requiring any form of signature verification. + #[clap(long)] + no_signature_verification: bool, + + /// Opt-in notice this is still experimental + #[clap(long)] + experimental: bool, + + /// Use this ostree remote for signature verification + #[clap(long)] + ostree_remote: Option, + + /// The transport; e.g. oci, oci-archive. Defaults to `registry`. + #[clap(long, default_value = "registry")] + transport: String, + + /// Target container image reference + imgref: String, +} + +/// Operations on container images +#[derive(Debug, clap::Subcommand)] +#[clap(rename_all = "kebab-case")] +enum ImageCmd { + Rebase(RebaseCmd), } #[derive(Debug, PartialEq, Eq)] @@ -75,9 +111,31 @@ enum RunDisposition { ExecRpmOstree(Vec), UseSomethingElse, NotImplementedYet(&'static str), + OnlySupportedOn(SystemHostType), Unsupported, } +impl RebaseCmd { + fn to_ostree_container_ref(&self) -> Result { + let transport = ostree_ext::container::Transport::try_from(self.transport.as_str())?; + let imgref = ostree_ext::container::ImageReference { + transport, + name: self.imgref.to_string(), + }; + use ostree_ext::container::SignatureSource; + let sigverify = if self.no_signature_verification { + SignatureSource::ContainerPolicyAllowInsecure + } else { + if let Some(remote) = self.ostree_remote.as_ref() { + SignatureSource::OstreeRemote(remote.to_string()) + } else { + SignatureSource::ContainerPolicy + } + }; + Ok(ostree_ext::container::OstreeImageReference { sigverify, imgref }) + } +} + fn run_clean(argv: &Vec) -> Result { let arg = if let Some(subarg) = argv.get(0) { subarg @@ -109,6 +167,18 @@ fn disposition(opt: Opt, hosttype: SystemHostType) -> Result { Package search is not yet implemented. For now, it's recommended to use e.g. `toolbox` and `dnf search` inside there. "##}), + Cmd::Image { cmd } => { + match cmd { + ImageCmd::Rebase(rebase) => { + let container_ref = rebase.to_ostree_container_ref()?; + let container_ref = container_ref.to_string(); + let experimental = rebase.experimental.then(|| "--experimental"); + let cmd = ["rebase"].into_iter().chain(experimental).chain([container_ref.as_str()]) + .map(|s| s.to_string()).collect::>(); + RunDisposition::ExecRpmOstree(cmd) + } + } + } } }, SystemHostType::OstreeContainer => match opt.cmd { @@ -124,7 +194,10 @@ fn disposition(opt: Opt, hosttype: SystemHostType) -> Result { Cmd::Status => RunDisposition::ExecRpmOstree(vec!["status".into()]), Cmd::Search { .. } => { RunDisposition::NotImplementedYet("Package search is not yet implemented.") - } + }, + Cmd::Image { .. } => { + RunDisposition::OnlySupportedOn(SystemHostType::OstreeHost) + }, }, _ => RunDisposition::Unsupported }; @@ -165,6 +238,10 @@ pub(crate) fn main(hosttype: SystemHostType, argv: &[&str]) -> Result<()> { RunDisposition::Unsupported => Err(anyhow!( "This command is only supported on ostree-based systems." )), + RunDisposition::OnlySupportedOn(platform) => { + let platform = crate::client::system_host_type_str(&platform); + Err(anyhow!("This command is only supported on {platform}")) + } RunDisposition::NotImplementedYet(msg) => Err(anyhow!("{}\n{}", IMAGEBASED, msg)), } } @@ -187,6 +264,13 @@ mod tests { )); } + let rebasecmd = &[ + "image", + "rebase", + "--experimental", + "quay.io/example/os:latest", + ]; + // Tests for the ostree host case let host = SystemHostType::OstreeHost; assert!(matches!( @@ -201,6 +285,10 @@ mod tests { testrun(host, &["install", "foo", "bar"])?, RunDisposition::UseSomethingElse )); + assert!(matches!( + testrun(host, rebasecmd).unwrap(), + RunDisposition::ExecRpmOstree(_) + )); fn strvec(s: impl IntoIterator) -> Vec { s.into_iter().map(|s| String::from(s)).collect() @@ -216,6 +304,11 @@ mod tests { testrun(host, &["clean", "all"])?, RunDisposition::ExecRpmOstree(strvec(["cleanup", "-m"])) ); + assert!(matches!( + testrun(host, rebasecmd).unwrap(), + RunDisposition::OnlySupportedOn(SystemHostType::OstreeHost) + )); + assert!(matches!( testrun(host, &["upgrade"])?, RunDisposition::NotImplementedYet(_)