diff --git a/Cargo.lock b/Cargo.lock index 94f061927..3abfabd34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,7 @@ name = "bootc-utils" version = "0.0.0" dependencies = [ "anyhow", + "chrono", "rustix 1.0.3", "serde", "serde_json", diff --git a/lib/src/install.rs b/lib/src/install.rs index 38ae7a352..04d01aecb 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -535,7 +535,7 @@ impl InstallAleph { l.get(oci_spec::image::ANNOTATION_CREATED) .map(|s| s.as_str()) }) - .and_then(crate::status::try_deserialize_timestamp); + .and_then(bootc_utils::try_deserialize_timestamp); let r = InstallAleph { image: src_imageref.imgref.name.clone(), version: imgstate.version().as_ref().map(|s| s.to_string()), diff --git a/lib/src/status.rs b/lib/src/status.rs index 759e3ba9f..04bb0443b 100644 --- a/lib/src/status.rs +++ b/lib/src/status.rs @@ -102,16 +102,6 @@ pub(crate) struct Deployments { pub(crate) other: VecDeque, } -pub(crate) fn try_deserialize_timestamp(t: &str) -> Option> { - match chrono::DateTime::parse_from_rfc3339(t).context("Parsing timestamp") { - Ok(t) => Some(t.into()), - 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> { diff --git a/lib/src/store/ostree_container.rs b/lib/src/store/ostree_container.rs index c26bd3e40..b2f127fdb 100644 --- a/lib/src/store/ostree_container.rs +++ b/lib/src/store/ostree_container.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use ostree_ext::container as ostree_container; use ostree_ext::oci_spec; @@ -52,7 +52,7 @@ fn create_imagestatus( .map(|s| s.as_str()) }) .or_else(|| config.created().as_deref()) - .and_then(try_deserialize_timestamp); + .and_then(bootc_utils::try_deserialize_timestamp); let version = ostree_container::version_for_config(config).map(ToOwned::to_owned); let architecture = config.architecture().to_string(); @@ -70,13 +70,3 @@ fn labels_of_config( ) -> Option<&std::collections::HashMap> { config.config().as_ref().and_then(|c| c.labels().as_ref()) } - -fn try_deserialize_timestamp(t: &str) -> Option> { - match chrono::DateTime::parse_from_rfc3339(t).context("Parsing timestamp") { - Ok(t) => Some(t.into()), - Err(e) => { - tracing::warn!("Invalid timestamp in image: {:#}", e); - None - } - } -} diff --git a/ostree-ext/src/container/encapsulate.rs b/ostree-ext/src/container/encapsulate.rs index 9dcf48675..21e814248 100644 --- a/ostree-ext/src/container/encapsulate.rs +++ b/ostree-ext/src/container/encapsulate.rs @@ -133,8 +133,20 @@ pub(crate) fn export_chunked( .uncompressed_sha256 .clone(); + let created = imgcfg + .created() + .as_deref() + .and_then(bootc_utils::try_deserialize_timestamp) + .unwrap_or_default(); // Add the ostree layer - ociw.push_layer(manifest, imgcfg, ostree_layer, description, None); + ociw.push_layer_full( + manifest, + imgcfg, + ostree_layer, + None::>, + description, + created, + ); // Add the component/content layers let mut buf = [0; 8]; let sep = COMPONENT_SEPARATOR.encode_utf8(&mut buf); @@ -142,12 +154,13 @@ pub(crate) fn export_chunked( let mut annotation_component_layer = HashMap::new(); packages.sort(); annotation_component_layer.insert(CONTENT_ANNOTATION.to_string(), packages.join(sep)); - ociw.push_layer( + ociw.push_layer_full( manifest, imgcfg, layer, - name.as_str(), Some(annotation_component_layer), + name.as_str(), + created, ); } diff --git a/ostree-ext/src/container/store.rs b/ostree-ext/src/container/store.rs index ed0a5d61a..c94424401 100644 --- a/ostree-ext/src/container/store.rs +++ b/ostree-ext/src/container/store.rs @@ -1490,12 +1490,22 @@ pub(crate) fn export_to_oci( .get(i) .and_then(|h| h.comment().as_deref()) .unwrap_or_default(); - dest_oci.push_layer( + + let previous_created = srcinfo + .configuration + .history() + .get(i) + .and_then(|h| h.created().as_deref()) + .and_then(bootc_utils::try_deserialize_timestamp) + .unwrap_or_default(); + + dest_oci.push_layer_full( &mut new_manifest, &mut new_config, layer, - previous_description, previous_annotations, + previous_description, + previous_created, ) } diff --git a/ostree-ext/src/fixture.rs b/ostree-ext/src/fixture.rs index c05fea922..b4e1e1408 100644 --- a/ostree-ext/src/fixture.rs +++ b/ostree-ext/src/fixture.rs @@ -26,6 +26,7 @@ use ocidir::cap_std::fs::{DirBuilder, DirBuilderExt as _}; use ocidir::oci_spec::image::ImageConfigurationBuilder; use regex::Regex; use std::borrow::Cow; +use std::collections::HashMap; use std::ffi::CString; use std::fmt::Write as _; use std::io::{self, Write}; @@ -1014,8 +1015,20 @@ impl NonOstreeFixture { let bw = bw.into_inner()?; let new_layer = bw.complete()?; - self.src_oci - .push_layer(&mut manifest, &mut config, new_layer, "root", None); + let created = config + .created() + .as_deref() + .and_then(bootc_utils::try_deserialize_timestamp) + .unwrap_or_default(); + + self.src_oci.push_layer_full( + &mut manifest, + &mut config, + new_layer, + None::>, + "root", + created, + ); let config = self.src_oci.write_config(config)?; manifest.set_config(config); diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 56949fedc..2c84bcccd 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/bootc-dev/bootc" [dependencies] anyhow = { workspace = true } +chrono = { workspace = true, features = ["std"] } rustix = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 0ff3929f9..82434f103 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -8,5 +8,7 @@ mod path; pub use path::*; mod iterators; pub use iterators::*; +mod timestamp; +pub use timestamp::*; mod tracing_util; pub use tracing_util::*; diff --git a/utils/src/timestamp.rs b/utils/src/timestamp.rs new file mode 100644 index 000000000..82057084c --- /dev/null +++ b/utils/src/timestamp.rs @@ -0,0 +1,13 @@ +use anyhow::Context; +use chrono; + +/// Try to parse an RFC 3339, warn on error. +pub fn try_deserialize_timestamp(t: &str) -> Option> { + match chrono::DateTime::parse_from_rfc3339(t).context("Parsing timestamp") { + Ok(t) => Some(t.into()), + Err(e) => { + tracing::warn!("Invalid timestamp in image: {:#}", e); + None + } + } +}