From d3d07c569a44fc953c3bf88e307afaae72f3c19e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 16 Sep 2023 16:43:18 -0400 Subject: [PATCH] Add version and timestamp to image status fields These are very practical things to want; the switch to a k8s-native API lost these user-facing ergonomics. Signed-off-by: Colin Walters --- lib/Cargo.toml | 5 +++-- lib/src/spec.rs | 5 +++++ lib/src/status.rs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 8ed350d39..06d64f0ca 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -12,6 +12,7 @@ rust-version = "1.64.0" anyhow = "1.0" camino = { version = "1.0.4", features = ["serde1"] } ostree-ext = "0.12" +chrono = { version = "0.4.23", features = ["serde"] } clap = { version= "4.2", features = ["derive"] } clap_mangen = { version = "0.2", optional = true } cap-std-ext = "3" @@ -19,7 +20,7 @@ hex = "^0.4" fn-error-context = "0.2.0" gvariant = "0.4.0" indicatif = "0.17.0" -k8s-openapi = { version = "0.18.0", features = ["v1_25"] } +k8s-openapi = { version = "0.18.0", features = ["v1_25", "schemars"] } kube = { version = "0.83.0", features = ["runtime", "derive"] } libc = "^0.2" liboverdrop = "0.1.0" @@ -29,7 +30,7 @@ openssl = "^0.10" nix = { version = "0.27", features = ["ioctl"] } regex = "1.7.1" rustix = { "version" = "0.38", features = ["thread", "fs", "system", "process"] } -schemars = "0.8.6" +schemars = { version = "0.8.6", features = ["chrono"] } serde = { features = ["derive"], version = "1.0.125" } serde_json = "1.0.64" serde_yaml = "0.9.17" diff --git a/lib/src/spec.rs b/lib/src/spec.rs index e5c40e499..a7dd2af91 100644 --- a/lib/src/spec.rs +++ b/lib/src/spec.rs @@ -1,5 +1,6 @@ //! The definition for host system state. +use k8s_openapi::apimachinery::pkg::apis::meta::v1 as k8smeta; use kube::CustomResource; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -54,6 +55,10 @@ pub struct ImageReference { pub struct ImageStatus { /// The currently booted image pub image: ImageReference, + /// The version string, if any + pub version: Option, + /// The build timestamp, if any + pub timestamp: Option, /// The digest of the fetched image (e.g. sha256:a0...); pub image_digest: String, } diff --git a/lib/src/status.rs b/lib/src/status.rs index 31d59ed9b..ce6fe5a6c 100644 --- a/lib/src/status.rs +++ b/lib/src/status.rs @@ -3,10 +3,12 @@ use std::collections::VecDeque; use crate::spec::{BootEntry, Host, HostSpec, HostStatus, ImageStatus}; use crate::spec::{ImageReference, ImageSignature}; use anyhow::{Context, Result}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1 as k8smeta; use ostree::glib; use ostree_container::OstreeImageReference; use ostree_ext::container as ostree_container; use ostree_ext::keyfileext::KeyFileExt; +use ostree_ext::oci_spec; use ostree_ext::ostree; use ostree_ext::sysroot::SysrootLock; @@ -87,6 +89,22 @@ pub(crate) struct Deployments { pub(crate) other: VecDeque, } +fn try_deserialize_timestamp(t: &str) -> Option { + match chrono::DateTime::parse_from_rfc3339(t).context("Parsing timestamp") { + Ok(t) => Some(k8smeta::Time(t.with_timezone(&chrono::Utc))), + Err(e) => { + tracing::warn!("Invalid timestamp in image: {:#}", e); + None + } + } +} + +pub(crate) fn labels_of_config( + config: &oci_spec::image::ImageConfiguration, +) -> Option<&std::collections::HashMap> { + config.config().as_ref().and_then(|c| c.labels().as_ref()) +} + fn boot_entry_from_deployment( sysroot: &SysrootLock, deployment: &ostree::Deployment, @@ -98,9 +116,23 @@ fn boot_entry_from_deployment( let csum = deployment.csum(); let incompatible = crate::utils::origin_has_rpmostree_stuff(origin); let imgstate = ostree_container::store::query_image_commit(repo, &csum)?; + let config = imgstate.configuration.as_ref(); + let labels = config.and_then(labels_of_config); + let timestamp = labels + .and_then(|l| { + l.get(oci_spec::image::ANNOTATION_CREATED) + .map(|s| s.as_str()) + }) + .and_then(try_deserialize_timestamp); + + let version = config + .and_then(ostree_container::version_for_config) + .map(ToOwned::to_owned); ( Some(ImageStatus { image, + version, + timestamp, image_digest: imgstate.manifest_digest, }), incompatible,