Skip to content

Commit

Permalink
update_agent: support rebasing to OCI pullspec
Browse files Browse the repository at this point in the history
Parse rpm-ostree status to detect if the current booted deployment
is an OCI image. If so, query the OCI graph for cincinnati and
rebase to the correct OCI image.

Requires coreos/fedora-coreos-cincinnati#99
and coreos/rpm-ostree#5120

Part of: coreos/fedora-coreos-tracker#1823
  • Loading branch information
jbtrystram committed Dec 16, 2024
1 parent ee1579f commit 7000352
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 21 deletions.
3 changes: 3 additions & 0 deletions src/cincinnati/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 10 additions & 0 deletions src/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub(crate) struct Identity {
pub(crate) rollout_wariness: Option<NotNan<f64>>,
/// Stream label.
pub(crate) stream: String,
/// Wether the current deployment is from an OCI container or an ostree reference.
pub(crate) oci: bool,
}

impl Identity {
Expand Down Expand Up @@ -104,6 +106,9 @@ impl Identity {
let platform = platform::read_id("/proc/cmdline")?;
let stream = rpm_ostree::parse_booted_updates_stream(&status)
.context("failed to introspect OS updates stream")?;
let oci = rpm_ostree::parse_booted_oci_reference(&status)
.context("failed to introspect booted OCI reference")?
.is_some();

let id = Self {
basearch,
Expand All @@ -113,6 +118,7 @@ impl Identity {
group: DEFAULT_GROUP.to_string(),
node_uuid,
rollout_wariness: None,
oci,
};
Ok(id)
}
Expand All @@ -139,6 +145,9 @@ impl Identity {
vars.insert("node_uuid".to_string(), self.node_uuid.lower_hex());
vars.insert("platform".to_string(), self.platform.clone());
vars.insert("stream".to_string(), self.stream.clone());
if self.oci {
vars.insert("oci".to_string(), "true".to_string());
}
if let Some(rw) = self.rollout_wariness {
vars.insert("rollout_wariness".to_string(), format!("{:.06}", rw));
}
Expand All @@ -159,6 +168,7 @@ impl Identity {
platform: "mock-azure".to_string(),
rollout_wariness: Some(NotNan::new(0.5).unwrap()),
stream: "mock-stable".to_string(),
oci: false,
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/rpm_ostree/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -53,7 +55,7 @@ impl Handler<StageDeployment> 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
}
Expand Down
26 changes: 16 additions & 10 deletions src/rpm_ostree/cli_deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ static REGISTER_DRIVER_FAILURES: Lazy<IntCounter> = Lazy::new(|| {
});

/// Deploy an upgrade (by checksum) and leave the new deployment locked.
pub fn deploy_locked(release: Release, allow_downgrade: bool) -> Result<Release> {
pub fn deploy_locked(release: Release, allow_downgrade: bool, oci: bool) -> Result<Release> {
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();
}
Expand Down Expand Up @@ -94,16 +94,22 @@ fn invoke_cli_register() -> Result<()> {
}

/// CLI executor for deploying upgrades.
fn invoke_cli_deploy(release: Release, allow_downgrade: bool) -> Result<Release> {
fn invoke_cli_deploy(release: Release, allow_downgrade: bool, oci: bool) -> Result<Release> {
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");
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
19 changes: 13 additions & 6 deletions src/rpm_ostree/cli_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,6 @@ impl Deployment {
/// Parse the booted deployment from status object.
pub fn parse_booted(status: &Status) -> Result<Release> {
let status = booted_status(status)?;
if let Some(img) = status.container_image_reference.as_ref() {
let msg = format!("Automatic updates disabled; booted into container image {img}");
crate::utils::notify_ready();
crate::utils::update_unit_status(&msg);
return Err(anyhow::Error::new(SystemInoperable(msg)));
}
Ok(status.into_release())
}

Expand All @@ -124,6 +118,11 @@ pub fn parse_booted_updates_stream(status: &Status) -> Result<String> {
fedora_coreos_stream_from_deployment(&json)
}

/// Parse oci image reference for booted deployment from status object.
pub fn parse_booted_oci_reference(status: &Status) -> Result<Option<String>> {
booted_status(status).map(|s| s.container_image_reference)
}

/// Parse pending deployment from status object.
pub fn parse_pending_deployment(status: &Status) -> Result<Option<(Release, String)>> {
// There can be at most one staged/pending rpm-ostree deployment,
Expand Down Expand Up @@ -258,6 +257,14 @@ mod tests {
let deployments = parse_local_deployments(&status, true);
assert_eq!(deployments.len(), 1);
}
{
let status = mock_status("tests/fixtures/rpm-ostree-oci-status.json").unwrap();
let deployments = parse_local_deployments(&status, false);
assert_eq!(deployments.len(), 1);
assert!(parse_booted_oci_reference(&status).unwrap().is_some());
assert_eq!(parse_booted_oci_reference(&status).unwrap().unwrap(),
"ostree-unverified-registry:quay.io/fedora/fedora-coreos@sha256:d12dd2fcb57ecfde0941be604f4dcd43ce0409b86e5ee4e362184c802b80fb84")
}
}

#[test]
Expand Down
7 changes: 4 additions & 3 deletions src/rpm_ostree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ mod cli_deploy;
mod cli_finalize;
mod cli_status;
pub use cli_status::{
invoke_cli_status, parse_booted, parse_booted_updates_stream, SystemInoperable,
invoke_cli_status, parse_booted, parse_booted_oci_reference, parse_booted_updates_stream,
SystemInoperable,
};

mod actor;
Expand All @@ -14,7 +15,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;
Expand Down Expand Up @@ -70,7 +71,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
);
Expand Down
1 change: 1 addition & 0 deletions src/update_agent/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ impl UpdateAgentInfo {
let msg = rpm_ostree::StageDeployment {
release,
allow_downgrade: self.allow_downgrade,
oci: self.identity.oci,
};

self.rpm_ostree_actor
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/00-config-sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ length_minutes = 120
[[updates.periodic.window]]
days = [ "Wed" ]
start_time = "23:30"
length_minutes = 25
length_minutes = 25
38 changes: 38 additions & 0 deletions tests/fixtures/rpm-ostree-oci-status.json

Large diffs are not rendered by default.

0 comments on commit 7000352

Please sign in to comment.