From 1ca8a877e128c68911ef79997b2449b8ccb85773 Mon Sep 17 00:00:00 2001 From: jbtrystram Date: Wed, 4 Dec 2024 15:00:04 +0100 Subject: [PATCH] update_agent: support rebaseing to OCI pullspec Add a configuration knob in `update` to optionnaly use OCI pullspec instead of ostree checksums. This will default to false. Requires https://github.com/coreos/fedora-coreos-cincinnati/pull/99 and https://github.com/coreos/rpm-ostree/pull/5120 --- src/cincinnati/mock_tests.rs | 1 + src/cincinnati/mod.rs | 21 ++++++++++++++++++--- src/config/fragments.rs | 3 +++ src/config/inputs.rs | 7 +++++++ src/config/mod.rs | 6 +++++- src/rpm_ostree/actor.rs | 4 +++- src/rpm_ostree/cli_deploy.rs | 26 ++++++++++++++++---------- src/rpm_ostree/mock_tests.rs | 2 ++ src/rpm_ostree/mod.rs | 4 ++-- src/strategy/fleet_lock.rs | 2 ++ src/update_agent/actor.rs | 1 + src/update_agent/mod.rs | 3 +++ tests/fixtures/00-config-sample.toml | 3 ++- 13 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/cincinnati/mock_tests.rs b/src/cincinnati/mock_tests.rs index fdb54f50..ac8da5e5 100644 --- a/src/cincinnati/mock_tests.rs +++ b/src/cincinnati/mock_tests.rs @@ -19,6 +19,7 @@ fn test_empty_graph() { let id = Identity::mock_default(); let client = Cincinnati { base_url: server.url(), + oci_param: false, }; let update = runtime.block_on(client.next_update(&id, BTreeSet::new(), false)); m_graph.assert(); diff --git a/src/cincinnati/mod.rs b/src/cincinnati/mod.rs index ee8ff65f..a9f3fca8 100644 --- a/src/cincinnati/mod.rs +++ b/src/cincinnati/mod.rs @@ -35,6 +35,9 @@ pub static DEADEND_REASON_KEY: &str = "org.fedoraproject.coreos.updates.deadend_ /// Metadata value for "checksum" payload scheme. pub static CHECKSUM_SCHEME: &str = "checksum"; +/// Metadata value for "oci" payload scheme. +pub static OCI_SCHEME: &str = "oci"; + lazy_static::lazy_static! { static ref GRAPH_NODES: IntGauge = register_int_gauge!(opts!( "zincati_cincinnati_graph_nodes_count", @@ -102,12 +105,18 @@ impl DeadEndState { pub struct Cincinnati { /// Service base URL. pub base_url: String, + /// Wether to pass `oci` query parameter + pub oci_param: bool, } impl Cincinnati { /// Process Cincinnati configuration. #[context("failed to validate cincinnati configuration")] - pub(crate) fn with_config(cfg: inputs::CincinnatiInput, id: &Identity) -> Result { + pub(crate) fn with_config( + cfg: inputs::CincinnatiInput, + id: &Identity, + use_oci: bool, + ) -> Result { if cfg.base_url.is_empty() { anyhow::bail!("empty Cincinnati base URL"); } @@ -122,7 +131,10 @@ impl Cincinnati { }; log::info!("Cincinnati service: {}", &base_url); - let c = Self { base_url }; + let c = Self { + base_url, + oci_param: use_oci, + }; Ok(c) } @@ -156,7 +168,10 @@ impl Cincinnati { allow_downgrade: bool, ) -> Pin, CincinnatiError>>>> { let booted = id.current_os.clone(); - let params = id.cincinnati_params(); + let mut params = id.cincinnati_params(); + if self.oci_param { + let _ = params.insert(String::from("oci"), String::from("true")); + } let client = client::ClientBuilder::new(self.base_url.to_string()) .query_params(Some(params)) .build() diff --git a/src/config/fragments.rs b/src/config/fragments.rs index 33aca454..07d7e9a2 100644 --- a/src/config/fragments.rs +++ b/src/config/fragments.rs @@ -63,6 +63,8 @@ pub(crate) struct UpdateFragment { pub(crate) fleet_lock: Option, /// `periodic` strategy config. pub(crate) periodic: Option, + /// Wether to pull updates from OCI images + pub(crate) use_oci: Option, } /// Config fragment for `fleet_lock` update strategy. @@ -141,6 +143,7 @@ mod tests { ]), time_zone: Some("localtime".to_string()), }), + use_oci: Some(false), }), }; diff --git a/src/config/inputs.rs b/src/config/inputs.rs index 06cccd27..9e1d9662 100644 --- a/src/config/inputs.rs +++ b/src/config/inputs.rs @@ -162,6 +162,8 @@ pub(crate) struct UpdateInput { pub(crate) fleet_lock: FleetLockInput, /// `periodic` strategy config. pub(crate) periodic: PeriodicInput, + /// Wether to pull updates from OCI images + pub(crate) use_oci: bool, } /// Config for "fleet_lock" strategy. @@ -201,6 +203,7 @@ impl UpdateInput { intervals: vec![], time_zone: "UTC".to_string(), }; + let mut use_oci = false; for snip in fragments { if let Some(a) = snip.allow_downgrade { @@ -234,6 +237,9 @@ impl UpdateInput { } } } + if let Some(oci) = snip.use_oci { + use_oci = oci; + } } Self { @@ -242,6 +248,7 @@ impl UpdateInput { strategy, fleet_lock, periodic, + use_oci, } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 3111f899..22b9829d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -38,6 +38,8 @@ pub(crate) struct Settings { pub(crate) identity: Identity, /// Agent update strategy. pub(crate) strategy: UpdateStrategy, + /// Wether to pull updates from OCI images + pub(crate) use_oci: bool, } impl Settings { @@ -68,10 +70,11 @@ impl Settings { fn validate(cfg: inputs::ConfigInput) -> Result { let allow_downgrade = cfg.updates.allow_downgrade; let enabled = cfg.updates.enabled; + let use_oci = cfg.updates.use_oci; let steady_interval_secs = cfg.agent.steady_interval_secs; let identity = Identity::with_config(cfg.identity)?; let strategy = UpdateStrategy::with_config(cfg.updates, &identity)?; - let cincinnati = Cincinnati::with_config(cfg.cincinnati, &identity)?; + let cincinnati = Cincinnati::with_config(cfg.cincinnati, &identity, use_oci)?; Ok(Self { allow_downgrade, @@ -80,6 +83,7 @@ impl Settings { cincinnati, identity, strategy, + use_oci, }) } } diff --git a/src/rpm_ostree/actor.rs b/src/rpm_ostree/actor.rs index 261776c8..70847ca8 100644 --- a/src/rpm_ostree/actor.rs +++ b/src/rpm_ostree/actor.rs @@ -42,6 +42,8 @@ pub struct StageDeployment { pub allow_downgrade: bool, /// Release to be staged. pub release: Release, + /// If the release is an OCI image pullspec. + pub oci: bool, } impl Message for StageDeployment { @@ -53,7 +55,7 @@ impl Handler for RpmOstreeClient { fn handle(&mut self, msg: StageDeployment, _ctx: &mut Self::Context) -> Self::Result { trace!("request to stage release: {:?}", msg.release); - let release = super::cli_deploy::deploy_locked(msg.release, msg.allow_downgrade); + let release = super::cli_deploy::deploy_locked(msg.release, msg.allow_downgrade, msg.oci); trace!("rpm-ostree CLI returned: {:?}", release); release } diff --git a/src/rpm_ostree/cli_deploy.rs b/src/rpm_ostree/cli_deploy.rs index 665ef9ca..247e2911 100644 --- a/src/rpm_ostree/cli_deploy.rs +++ b/src/rpm_ostree/cli_deploy.rs @@ -32,10 +32,10 @@ static REGISTER_DRIVER_FAILURES: Lazy = Lazy::new(|| { }); /// Deploy an upgrade (by checksum) and leave the new deployment locked. -pub fn deploy_locked(release: Release, allow_downgrade: bool) -> Result { +pub fn deploy_locked(release: Release, allow_downgrade: bool, oci: bool) -> Result { DEPLOY_ATTEMPTS.inc(); - let result = invoke_cli_deploy(release, allow_downgrade); + let result = invoke_cli_deploy(release, allow_downgrade, oci); if result.is_err() { DEPLOY_FAILURES.inc(); } @@ -94,16 +94,22 @@ fn invoke_cli_register() -> Result<()> { } /// CLI executor for deploying upgrades. -fn invoke_cli_deploy(release: Release, allow_downgrade: bool) -> Result { +fn invoke_cli_deploy(release: Release, allow_downgrade: bool, oci: bool) -> Result { fail_point!("deploy_locked_err", |_| bail!("deploy_locked_err")); fail_point!("deploy_locked_ok", |_| Ok(release.clone())); let mut cmd = std::process::Command::new("rpm-ostree"); - cmd.arg("deploy") - .arg("--lock-finalization") - .arg("--skip-branch-check") - .arg(format!("revision={}", release.checksum)) - .env("RPMOSTREE_CLIENT_ID", "zincati"); + if oci { + // TODO use --custom-origin-url and --custom-origin-description + cmd.arg("rebase") + .arg(format!("ostree-unverified-registry:{}", release.checksum)); + } else { + cmd.arg("deploy") + .arg("--lock-finalization") + .arg("--skip-branch-check") + .arg(format!("revision={}", release.checksum)); + } + cmd.env("RPMOSTREE_CLIENT_ID", "zincati"); if !allow_downgrade { cmd.arg("--disallow-downgrade"); } @@ -149,7 +155,7 @@ mod tests { checksum: "bar".to_string(), age_index: None, }; - let result = deploy_locked(release, true); + let result = deploy_locked(release, true, false); assert!(result.is_err()); assert!(DEPLOY_ATTEMPTS.get() >= 1); assert!(DEPLOY_FAILURES.get() >= 1); @@ -166,7 +172,7 @@ mod tests { checksum: "bar".to_string(), age_index: None, }; - let result = deploy_locked(release.clone(), true).unwrap(); + let result = deploy_locked(release.clone(), true, false).unwrap(); assert_eq!(result, release); assert!(DEPLOY_ATTEMPTS.get() >= 1); } diff --git a/src/rpm_ostree/mock_tests.rs b/src/rpm_ostree/mock_tests.rs index 6a9b760e..41e9a6cc 100644 --- a/src/rpm_ostree/mock_tests.rs +++ b/src/rpm_ostree/mock_tests.rs @@ -47,6 +47,7 @@ fn test_simple_graph() { let id = Identity::mock_default(); let client = Cincinnati { base_url: server.url(), + oci_param: false, }; let update = runtime.block_on(client.fetch_update_hint(&id, BTreeSet::new(), false)); m_graph.assert(); @@ -99,6 +100,7 @@ fn test_downgrade() { let id = Identity::mock_default(); let client = Cincinnati { base_url: server.url(), + oci_param: false, }; // Downgrades denied. diff --git a/src/rpm_ostree/mod.rs b/src/rpm_ostree/mod.rs index f2c4e48c..b0bf9cb6 100644 --- a/src/rpm_ostree/mod.rs +++ b/src/rpm_ostree/mod.rs @@ -14,7 +14,7 @@ pub use actor::{ #[cfg(test)] mod mock_tests; -use crate::cincinnati::{Node, AGE_INDEX_KEY, CHECKSUM_SCHEME, SCHEME_KEY}; +use crate::cincinnati::{Node, AGE_INDEX_KEY, CHECKSUM_SCHEME, OCI_SCHEME, SCHEME_KEY}; use anyhow::{anyhow, ensure, Context, Result}; use serde::Serialize; use std::cmp::Ordering; @@ -70,7 +70,7 @@ impl Release { .ok_or_else(|| anyhow!("missing metadata key: {}", SCHEME_KEY))?; ensure!( - scheme == CHECKSUM_SCHEME, + scheme == CHECKSUM_SCHEME || scheme == OCI_SCHEME, "unexpected payload scheme: {}", scheme ); diff --git a/src/strategy/fleet_lock.rs b/src/strategy/fleet_lock.rs index 20c69315..c901349e 100644 --- a/src/strategy/fleet_lock.rs +++ b/src/strategy/fleet_lock.rs @@ -107,6 +107,7 @@ mod tests { intervals: vec![], time_zone: "UTC".to_string(), }, + use_oci: false, }; let res = StrategyFleetLock::new(input, &id); @@ -127,6 +128,7 @@ mod tests { intervals: vec![], time_zone: "localtime".to_string(), }, + use_oci: false, }; let res = StrategyFleetLock::new(input, &id); diff --git a/src/update_agent/actor.rs b/src/update_agent/actor.rs index 67cc6439..dad3416d 100644 --- a/src/update_agent/actor.rs +++ b/src/update_agent/actor.rs @@ -418,6 +418,7 @@ impl UpdateAgentInfo { let msg = rpm_ostree::StageDeployment { release, allow_downgrade: self.allow_downgrade, + oci: self.use_oci, }; self.rpm_ostree_actor diff --git a/src/update_agent/mod.rs b/src/update_agent/mod.rs index a4111d75..f3323aa9 100644 --- a/src/update_agent/mod.rs +++ b/src/update_agent/mod.rs @@ -431,6 +431,8 @@ pub(crate) struct UpdateAgentInfo { rpm_ostree_actor: Addr, /// Update strategy. strategy: UpdateStrategy, + /// Wether or not to use OCI for transport + use_oci: bool, } impl UpdateAgent { @@ -448,6 +450,7 @@ impl UpdateAgent { rpm_ostree_actor: rpm_ostree_addr, steady_interval: Duration::from_secs(steady_secs), strategy: cfg.strategy, + use_oci: cfg.use_oci, }, } } diff --git a/tests/fixtures/00-config-sample.toml b/tests/fixtures/00-config-sample.toml index af71c07d..c46341af 100644 --- a/tests/fixtures/00-config-sample.toml +++ b/tests/fixtures/00-config-sample.toml @@ -13,6 +13,7 @@ base_url = "http://cincinnati.example.com:80/" allow_downgrade = true enabled = false strategy = "fleet_lock" +use_oci = false [updates.fleet_lock] base_url = "http://fleet-lock.example.com:8080/" @@ -28,4 +29,4 @@ length_minutes = 120 [[updates.periodic.window]] days = [ "Wed" ] start_time = "23:30" -length_minutes = 25 \ No newline at end of file +length_minutes = 25