1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 15:45:53 +01:00

Merge pull request #1339 from champtar/repro

ostree-ext: make OCI history reproducible
This commit is contained in:
Colin Walters
2025-05-29 11:14:36 -04:00
committed by GitHub
10 changed files with 63 additions and 30 deletions

1
Cargo.lock generated
View File

@@ -301,6 +301,7 @@ name = "bootc-utils"
version = "0.0.0"
dependencies = [
"anyhow",
"chrono",
"rustix 1.0.3",
"serde",
"serde_json",

View File

@@ -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()),

View File

@@ -102,16 +102,6 @@ pub(crate) struct Deployments {
pub(crate) other: VecDeque<ostree::Deployment>,
}
pub(crate) fn try_deserialize_timestamp(t: &str) -> Option<chrono::DateTime<chrono::Utc>> {
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<String, String>> {

View File

@@ -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<String, String>> {
config.config().as_ref().and_then(|c| c.labels().as_ref())
}
fn try_deserialize_timestamp(t: &str) -> Option<chrono::DateTime<chrono::Utc>> {
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
}
}
}

View File

@@ -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::<HashMap<String, String>>,
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,
);
}

View File

@@ -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,
)
}

View File

@@ -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::<HashMap<String, String>>,
"root",
created,
);
let config = self.src_oci.write_config(config)?;
manifest.set_config(config);

View File

@@ -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 }

View File

@@ -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::*;

13
utils/src/timestamp.rs Normal file
View File

@@ -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<chrono::DateTime<chrono::Utc>> {
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
}
}
}