mirror of
https://github.com/imjasonh/krust
synced 2026-07-06 22:12:32 +00:00
reproducible
Signed-off-by: Jason Hall <jason@chainguard.dev>
This commit is contained in:
parent
34eb31b29d
commit
72dc9ef103
2 changed files with 124 additions and 1 deletions
|
|
@ -9,6 +9,19 @@ use std::io::Write;
|
|||
use tar::Builder;
|
||||
use tracing::{debug, info};
|
||||
|
||||
/// Get the timestamp to use for reproducible builds.
|
||||
/// Respects SOURCE_DATE_EPOCH environment variable if set.
|
||||
fn get_build_timestamp() -> String {
|
||||
if let Ok(epoch) = std::env::var("SOURCE_DATE_EPOCH") {
|
||||
if let Ok(timestamp) = epoch.parse::<i64>() {
|
||||
if let Some(dt) = chrono::DateTime::from_timestamp(timestamp, 0) {
|
||||
return dt.to_rfc3339();
|
||||
}
|
||||
}
|
||||
}
|
||||
chrono::Utc::now().to_rfc3339()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ImageConfig {
|
||||
pub architecture: String,
|
||||
|
|
@ -220,7 +233,7 @@ impl ImageBuilder {
|
|||
// Combine history (base history + app history)
|
||||
let mut merged_history = base_config.history.clone();
|
||||
merged_history.push(History {
|
||||
created: chrono::Utc::now().to_rfc3339(),
|
||||
created: get_build_timestamp(),
|
||||
created_by: "krust".to_string(),
|
||||
comment: "Built with krust".to_string(),
|
||||
empty_layer: false,
|
||||
|
|
@ -424,4 +437,43 @@ mod tests {
|
|||
assert_eq!(parsed.rootfs.diff_ids.len(), 1);
|
||||
assert_eq!(parsed.history.len(), 0); // Default empty for missing field
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_build_timestamp_respects_source_date_epoch() {
|
||||
// Set SOURCE_DATE_EPOCH
|
||||
std::env::set_var("SOURCE_DATE_EPOCH", "1609459200");
|
||||
|
||||
let timestamp = super::get_build_timestamp();
|
||||
|
||||
// Should be 2021-01-01T00:00:00+00:00
|
||||
assert!(timestamp.starts_with("2021-01-01T00:00:00"));
|
||||
|
||||
// Clean up
|
||||
std::env::remove_var("SOURCE_DATE_EPOCH");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_build_timestamp_without_source_date_epoch() {
|
||||
// Make sure SOURCE_DATE_EPOCH is not set
|
||||
std::env::remove_var("SOURCE_DATE_EPOCH");
|
||||
|
||||
let timestamp = super::get_build_timestamp();
|
||||
|
||||
// Should be a valid RFC3339 timestamp
|
||||
assert!(chrono::DateTime::parse_from_rfc3339(×tamp).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_build_timestamp_invalid_epoch() {
|
||||
// Set invalid SOURCE_DATE_EPOCH
|
||||
std::env::set_var("SOURCE_DATE_EPOCH", "not-a-number");
|
||||
|
||||
let timestamp = super::get_build_timestamp();
|
||||
|
||||
// Should fall back to current time
|
||||
assert!(chrono::DateTime::parse_from_rfc3339(×tamp).is_ok());
|
||||
|
||||
// Clean up
|
||||
std::env::remove_var("SOURCE_DATE_EPOCH");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,3 +123,74 @@ fn test_multi_arch_build_and_run() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reproducible_builds() -> Result<()> {
|
||||
// This test verifies that building the same project twice with SOURCE_DATE_EPOCH
|
||||
// produces identical image digests
|
||||
|
||||
// Get the example project directory
|
||||
let example_dir = env::current_dir()?.join("example").join("hello-krust");
|
||||
|
||||
let native_platform = if cfg!(target_arch = "aarch64") {
|
||||
"linux/arm64"
|
||||
} else {
|
||||
"linux/amd64"
|
||||
};
|
||||
|
||||
// Fixed timestamp for reproducibility
|
||||
let source_date_epoch = "1609459200"; // 2021-01-01 00:00:00 UTC
|
||||
|
||||
// Build the first time (pushing to ttl.sh for temporary storage)
|
||||
let mut cmd1 = Command::cargo_bin("krust")?;
|
||||
let output1 = cmd1
|
||||
.arg("build")
|
||||
.arg("--platform")
|
||||
.arg(native_platform)
|
||||
.arg(".")
|
||||
.env("KRUST_REPO", "ttl.sh/krust-reproducible-test")
|
||||
.env("SOURCE_DATE_EPOCH", source_date_epoch)
|
||||
.current_dir(&example_dir)
|
||||
.output()?;
|
||||
|
||||
if !output1.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output1.stderr);
|
||||
panic!("First build failed: {}", stderr);
|
||||
}
|
||||
|
||||
let digest1 = String::from_utf8_lossy(&output1.stdout).trim().to_string();
|
||||
|
||||
// Build the second time with the same SOURCE_DATE_EPOCH
|
||||
let mut cmd2 = Command::cargo_bin("krust")?;
|
||||
let output2 = cmd2
|
||||
.arg("build")
|
||||
.arg("--platform")
|
||||
.arg(native_platform)
|
||||
.arg(".")
|
||||
.env("KRUST_REPO", "ttl.sh/krust-reproducible-test")
|
||||
.env("SOURCE_DATE_EPOCH", source_date_epoch)
|
||||
.current_dir(&example_dir)
|
||||
.output()?;
|
||||
|
||||
if !output2.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output2.stderr);
|
||||
panic!("Second build failed: {}", stderr);
|
||||
}
|
||||
|
||||
let digest2 = String::from_utf8_lossy(&output2.stdout).trim().to_string();
|
||||
|
||||
// The digests should be identical
|
||||
assert_eq!(
|
||||
digest1, digest2,
|
||||
"Builds with same SOURCE_DATE_EPOCH should produce identical digests.\nFirst: {}\nSecond: {}",
|
||||
digest1, digest2
|
||||
);
|
||||
|
||||
// Both should contain a valid digest
|
||||
assert!(
|
||||
digest1.contains("@sha256:"),
|
||||
"Output should contain a digest"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue