Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jbtrystram committed Feb 7, 2025
1 parent a221fbc commit a99b5bf
Show file tree
Hide file tree
Showing 14 changed files with 1,174 additions and 128 deletions.
861 changes: 840 additions & 21 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ libsystemd = "0.7"
log = "0.4"
maplit = "1.0"
num-traits = "0.2"
oci-spec = "0.7.1"
once_cell = "1.19.0"
ordered-float = { version = "4.5", features = ["serde"] }
prometheus = { version = "0.13", default-features = false }
Expand Down
3 changes: 2 additions & 1 deletion dist/polkit-1/rules.d/zincati.rules
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
polkit.addRule(function(action, subject) {
if ((action.id == "org.projectatomic.rpmostree1.deploy" ||
action.id == "org.projectatomic.rpmostree1.finalize-deployment") ||
action.id == "org.projectatomic.rpmostree1.cleanup" &&
action.id == "org.projectatomic.rpmostree1.cleanup" ||
action.id == "org.projectatomic.rpmostree1.rebase" &&
subject.user == "zincati") {
return polkit.Result.YES;
}
Expand Down
5 changes: 1 addition & 4 deletions src/cincinnati/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,7 @@ impl ClientBuilder {
.timeout(DEFAULT_HTTP_COMPLETION_TIMEOUT)
.build()?,
};
let query_params = match self.query_params {
Some(params) => params,
None => HashMap::new(),
};
let query_params = self.query_params.unwrap_or_default();

let api_base = reqwest::Url::parse(&self.api_base)
.context(format!("failed to parse '{}'", &self.api_base))?;
Expand Down
67 changes: 46 additions & 21 deletions src/cincinnati/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod mock_tests;

use crate::config::inputs;
use crate::identity::Identity;
use crate::rpm_ostree::Release;
use crate::rpm_ostree::{Payload, Release};
use anyhow::{Context, Result};
use fn_error_context::context;
use futures::prelude::*;
Expand All @@ -33,7 +33,10 @@ pub static DEADEND_KEY: &str = "org.fedoraproject.coreos.updates.deadend";
pub static DEADEND_REASON_KEY: &str = "org.fedoraproject.coreos.updates.deadend_reason";

/// Metadata value for "checksum" payload scheme.
pub static CHECKSUM_SCHEME: &str = "checksum";
pub const CHECKSUM_SCHEME: &str = "checksum";

/// Metadata value for "oci" payload scheme.
pub const OCI_SCHEME: &str = "oci";

lazy_static::lazy_static! {
static ref GRAPH_NODES: IntGauge = register_int_gauge!(opts!(
Expand Down Expand Up @@ -229,10 +232,16 @@ fn find_update(
.nodes
.iter()
.enumerate()
.find(|(_, node)| is_same_checksum(node, &booted_depl.checksum))
.find(|(_, node)| is_same_checksum(node, &booted_depl))
{
Some(current) => current,
None => return Ok(None),
None => {
log::warn!(
"booted deployment {} not found in the update graph",
&booted_depl.payload
);
return Ok(None);
}
};
drop(booted_depl);
let cur_release = Release::from_cincinnati(cur_node.clone())
Expand Down Expand Up @@ -313,30 +322,42 @@ fn find_denylisted_releases(graph: &client::Graph, depls: BTreeSet<Release>) ->
use std::collections::HashSet;

let mut local_releases = BTreeSet::new();
let checksums: HashSet<String> = depls.into_iter().map(|rel| rel.checksum).collect();
let checksums: HashSet<Payload> = depls.into_iter().map(|rel| rel.payload).collect();

for entry in &graph.nodes {
if !checksums.contains(&entry.payload) {
continue;
}

if let Ok(release) = Release::from_cincinnati(entry.clone()) {
local_releases.insert(release);
if checksums.contains(&release.payload) {
local_releases.insert(release);
}
}
}

local_releases
}

/// Check whether input node matches current checksum.
fn is_same_checksum(node: &Node, checksum: &str) -> bool {
let payload_is_checksum = node
.metadata
.get(SCHEME_KEY)
.map(|v| v == CHECKSUM_SCHEME)
.unwrap_or(false);
fn is_same_checksum(node: &Node, deploy: &Release) -> bool {
let payload_type = node.metadata.get(SCHEME_KEY);

payload_is_checksum && node.payload == checksum
if let Some(scheme) = payload_type {
if scheme.as_str() == OCI_SCHEME {
if let Ok(Some(local_checksum)) = deploy.get_image_reference() {
local_checksum == node.payload
} else {
false
}
} else if scheme.as_str() == CHECKSUM_SCHEME {
if let Payload::Checksum(checksum) = &deploy.payload {
return &node.payload == checksum;
} else {
return false;
}
} else {
return false;
}
} else {
false
}
}

/// Check whether input node is a dead-end; if so, return the reason.
Expand Down Expand Up @@ -373,23 +394,27 @@ mod tests {

#[test]
fn source_node_comparison() {
let current = "current-sha";
let current = Release {
version: String::new(),
payload: Payload::Checksum("current-sha".to_string()),
age_index: None,
};

let mut metadata = HashMap::new();
metadata.insert(SCHEME_KEY.to_string(), CHECKSUM_SCHEME.to_string());
let matching = Node {
version: "v0".to_string(),
payload: current.to_string(),
payload: "current-sha".to_string(),
metadata,
};
assert!(is_same_checksum(&matching, current));
assert!(is_same_checksum(&matching, &current));

let mismatch = Node {
version: "v0".to_string(),
payload: "mismatch".to_string(),
metadata: HashMap::new(),
};
assert!(!is_same_checksum(&mismatch, current));
assert!(!is_same_checksum(&mismatch, &current));
}

#[test]
Expand Down
14 changes: 11 additions & 3 deletions src/identity/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod platform;

use crate::config::inputs;
use crate::rpm_ostree;
use crate::{config::inputs, rpm_ostree::Payload};
use anyhow::{anyhow, ensure, Context, Result};
use fn_error_context::context;
use lazy_static::lazy_static;
Expand Down Expand Up @@ -133,12 +133,20 @@ impl Identity {
pub fn cincinnati_params(&self) -> HashMap<String, String> {
let mut vars = HashMap::new();
vars.insert("basearch".to_string(), self.basearch.clone());
vars.insert("os_checksum".to_string(), self.current_os.checksum.clone());
vars.insert("os_version".to_string(), self.current_os.version.clone());
vars.insert("group".to_string(), self.group.clone());
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());
match &self.current_os.payload {
Payload::Checksum(checksum) => {
vars.insert("os_checksum".to_string(), checksum.clone());
}
Payload::Pullspec(image) => {
vars.insert("os_checksum".to_string(), image.clone());
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 @@ -151,7 +159,7 @@ impl Identity {
basearch: "mock-amd64".to_string(),
current_os: rpm_ostree::Release {
version: "0.0.0-mock".to_string(),
checksum: "sha-mock".to_string(),
payload: Payload::Checksum("sha-mock".to_string()),
age_index: None,
},
group: "mock-workers".to_string(),
Expand Down
53 changes: 50 additions & 3 deletions src/rpm_ostree/actor.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! rpm-ostree client actor.
use super::cli_status::Status;
use super::Release;
use super::{Payload, Release};
use actix::prelude::*;
use anyhow::{Context, Result};
use filetime::FileTime;
use log::trace;
use ostree_ext::container::OstreeImageReference;
use std::collections::BTreeSet;
use std::rc::Rc;

Expand Down Expand Up @@ -52,8 +53,54 @@ impl Handler<StageDeployment> for RpmOstreeClient {
type Result = Result<Release>;

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 booted = super::cli_status::invoke_cli_status(true)?;
let local_deploy = super::cli_status::booted_status(&booted)?;

let release = if let Payload::Pullspec(release_payload) = msg.release.payload {
if let Some(local_imgref) = local_deploy.container_image_reference() {
// forward the custom origin info to the new deployment
// while updating custom-url to match the new image digest
// TODO : when moving to update graph V2 we want to remove this and
// have custom-url pointing to the OCI artifact containing the graph
let custom_origin = if let Some(mut custom_origin) = local_deploy.custom_origin() {
custom_origin.url = release_payload.clone();
Some(custom_origin)
} else {
log::warn!("Missing custom origin information for local OCI deployment.");
None
};

// Cinncinati payload contains the container image pullspec, but we need
// to prepend the OSTree signature source so rpm-ostree will verify the signature of
// the OSTree commit wrapped inside the container.

// let's craft a propper ostree imgref object
let rebase_target = OstreeImageReference {
sigverify: local_imgref.sigverify,
imgref: ostree_ext::container::ImageReference {
transport: ostree_ext::container::Transport::Registry,
name: release_payload,
},
};

// re-craft a release object with the pullspec
let oci_release = Release {
version: msg.release.version.clone(),
payload: Payload::Pullspec(rebase_target.to_string()),
age_index: msg.release.age_index,
};

trace!("request to stage release: {:?}", oci_release);
super::cli_deploy::deploy_locked(oci_release, msg.allow_downgrade, custom_origin)
} else {
// This should never happen as requesting the OCI graph only happens after we detected the local deployement is OCI.
// But let's fail gracefuly just in case.
anyhow::bail!("Zincati does not support OCI updates if the current deployement is not already an OCI image reference.")
}
} else {
trace!("request to stage release: {:?}", msg.release);
super::cli_deploy::deploy_locked(msg.release, msg.allow_downgrade, None)
};
trace!("rpm-ostree CLI returned: {:?}", release);
release
}
Expand Down
52 changes: 39 additions & 13 deletions src/rpm_ostree/cli_deploy.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Interface to `rpm-ostree deploy --lock-finalization` and
//! `rpm-ostree deploy --register-driver`.
use super::Release;
use crate::rpm_ostree::{CustomOrigin, Release};
use anyhow::{bail, Context, Result};
use once_cell::sync::Lazy;
use prometheus::IntCounter;
Expand Down Expand Up @@ -32,10 +32,14 @@ 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,
custom_origin: Option<CustomOrigin>,
) -> Result<Release> {
DEPLOY_ATTEMPTS.inc();

let result = invoke_cli_deploy(release, allow_downgrade);
let result = invoke_cli_deploy(release, allow_downgrade, custom_origin);
if result.is_err() {
DEPLOY_FAILURES.inc();
}
Expand Down Expand Up @@ -94,16 +98,37 @@ 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,
custom_origin: Option<CustomOrigin>,
) -> 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");
match &release.payload {
crate::rpm_ostree::Payload::Pullspec(image) => {
if let Some(origin) = custom_origin {
cmd.arg("rebase")
.arg(image)
.arg("--lock-finalization")
.arg("--custom-origin-url")
.arg(origin.url.clone())
.arg("--custom-origin-description")
.arg(origin.description.clone());
} else {
bail!("Missing custom-origin information.")
}
}
crate::rpm_ostree::Payload::Checksum(checksum) => {
cmd.arg("deploy")
.arg("--lock-finalization")
.arg("--skip-branch-check")
.arg(format!("revision={}", checksum));
}
}
cmd.env("RPMOSTREE_CLIENT_ID", "zincati");
if !allow_downgrade {
cmd.arg("--disallow-downgrade");
}
Expand Down Expand Up @@ -137,6 +162,7 @@ pub fn invoke_cli_cleanup() -> Result<()> {
mod tests {
#[allow(unused_imports)]
use super::*;
use crate::rpm_ostree::Payload;

#[cfg(feature = "failpoints")]
#[test]
Expand All @@ -146,10 +172,10 @@ mod tests {

let release = Release {
version: "foo".to_string(),
checksum: "bar".to_string(),
payload: Payload::Checksum("bar".to_string()),
age_index: None,
};
let result = deploy_locked(release, true);
let result = deploy_locked(release, true, None);
assert!(result.is_err());
assert!(DEPLOY_ATTEMPTS.get() >= 1);
assert!(DEPLOY_FAILURES.get() >= 1);
Expand All @@ -163,10 +189,10 @@ mod tests {

let release = Release {
version: "foo".to_string(),
checksum: "bar".to_string(),
payload: Payload::Checksum("bar".to_string()),
age_index: None,
};
let result = deploy_locked(release.clone(), true).unwrap();
let result = deploy_locked(release.clone(), true, None).unwrap();
assert_eq!(result, release);
assert!(DEPLOY_ATTEMPTS.get() >= 1);
}
Expand Down
Loading

0 comments on commit a99b5bf

Please sign in to comment.