From 9ddd7f45c3adaf8a3d825ac6aca05cb43ef93c87 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 27 Mar 2026 16:16:55 +0100 Subject: [PATCH 1/2] ROX-33792: make operator bundle builds reproducible Use SOURCE_DATE_EPOCH from git commit timestamp instead of current time for createdAt annotation. Sort environment variables to ensure deterministic ordering of related images in the bundle CSV. This allows identical source to produce identical image digests, improving supply chain security and enabling content-addressable storage benefits. User request: "Compare this two bundles and check if we can make our image reporducible. Test it locally on master as that could be our third PR" Co-Authored-By: Claude Sonnet 4.5 --- operator/Makefile | 6 +++++- operator/bundle.Dockerfile | 3 +++ operator/bundle_helpers/pkg/csv/patcher.go | 17 +++++++++++++++-- operator/konflux.bundle.Dockerfile | 5 +++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/operator/Makefile b/operator/Makefile index a7a1d33f2be5a..7938440315cc7 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -501,6 +501,10 @@ bundle: yq manifests kustomize operator-sdk ## Generate bundle manifests and met bundle/manifests/rhacs-operator.clusterserviceversion.yaml $(OPERATOR_SDK) bundle validate ./bundle --select-optional suite=operatorframework +# Use git commit timestamp for reproducible builds +SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct) +export SOURCE_DATE_EPOCH + .PHONY: bundle-post-process bundle-post-process: test-bundle-helpers operator-sdk ## Post-process CSV file to include correct operator versions, etc. # Note: Python venv is always needed because dispatch.sh may use yaml-normalizer.py @@ -532,7 +536,7 @@ bundle-post-process: test-bundle-helpers operator-sdk ## Post-process CSV file t .PHONY: bundle-build bundle-build: bundle.Dockerfile bundle-post-process ## Build the bundle image. - docker build $(if $(DOCKER_BUILD_LOAD),--load) -f $< -t $(BUNDLE_IMG) . + docker build $(if $(DOCKER_BUILD_LOAD),--load) --build-arg SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) -f $< -t $(BUNDLE_IMG) . .PHONY: bundle-test bundle-test: operator-sdk bundle-post-process ## Run scorecard tests against bundle files. diff --git a/operator/bundle.Dockerfile b/operator/bundle.Dockerfile index 4c8f6090339e8..894e8ea82d05e 100644 --- a/operator/bundle.Dockerfile +++ b/operator/bundle.Dockerfile @@ -1,5 +1,8 @@ FROM scratch +# Reproducible builds support +ARG SOURCE_DATE_EPOCH + # Core bundle labels. LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ diff --git a/operator/bundle_helpers/pkg/csv/patcher.go b/operator/bundle_helpers/pkg/csv/patcher.go index bb39135c57ae8..9cf8e2bdb1017 100644 --- a/operator/bundle_helpers/pkg/csv/patcher.go +++ b/operator/bundle_helpers/pkg/csv/patcher.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "os" + "sort" + "strconv" "strings" "time" @@ -40,7 +42,14 @@ func GetRawName(doc chartutil.Values) (string, error) { // PatchCSV modifies the CSV document in-place according to options func PatchCSV(doc chartutil.Values, opts PatchOptions) error { // Update createdAt timestamp - if err := values.CoalesceValue(doc, "metadata.annotations.createdAt", time.Now().UTC().Format(time.RFC3339)); err != nil { + // Use SOURCE_DATE_EPOCH if set (for reproducible builds), otherwise use current time + createdAt := time.Now().UTC() + if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" { + if timestamp, err := strconv.ParseInt(epoch, 10, 64); err == nil { + createdAt = time.Unix(timestamp, 0).UTC() + } + } + if err := values.CoalesceValue(doc, "metadata.annotations.createdAt", createdAt.Format(time.RFC3339)); err != nil { return fmt.Errorf("failed to set createdAt: %w", err) } @@ -172,7 +181,11 @@ func setRelatedImages(spec chartutil.Values, managerImage string) error { relatedImages := make([]map[string]any, 0) // Collect all RELATED_IMAGE_* env vars - for _, envVar := range os.Environ() { + // Sort environment variables for deterministic order + envVars := os.Environ() + sort.Strings(envVars) + + for _, envVar := range envVars { if strings.HasPrefix(envVar, "RELATED_IMAGE_") { parts := strings.SplitN(envVar, "=", 2) if len(parts) != 2 { diff --git a/operator/konflux.bundle.Dockerfile b/operator/konflux.bundle.Dockerfile index 2bee8416585c3..bc3007070244e 100644 --- a/operator/konflux.bundle.Dockerfile +++ b/operator/konflux.bundle.Dockerfile @@ -1,4 +1,9 @@ FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.25@sha256:bd531796aacb86e4f97443797262680fbf36ca048717c00b6f4248465e1a7c0c AS builder + +# Reproducible builds support +ARG SOURCE_DATE_EPOCH +ENV SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH + # This installs PyYAML (with Python) needed by bundle_helpers. RUN dnf -y install --allowerasing python3.12-pyyaml && \ alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 From 9197e6b385159922defc541f8f9637ba7864f093 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 27 Mar 2026 16:43:31 +0100 Subject: [PATCH 2/2] fix(operator): move SOURCE_DATE_EPOCH ARG to bundle.Dockerfile.extra bundle.Dockerfile is regenerated by operator-sdk, so the ARG must be in bundle.Dockerfile.extra which gets appended during the build process. Co-Authored-By: Claude Sonnet 4.5 --- operator/bundle.Dockerfile | 3 --- operator/bundle.Dockerfile.extra | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/operator/bundle.Dockerfile b/operator/bundle.Dockerfile index 894e8ea82d05e..4c8f6090339e8 100644 --- a/operator/bundle.Dockerfile +++ b/operator/bundle.Dockerfile @@ -1,8 +1,5 @@ FROM scratch -# Reproducible builds support -ARG SOURCE_DATE_EPOCH - # Core bundle labels. LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ diff --git a/operator/bundle.Dockerfile.extra b/operator/bundle.Dockerfile.extra index da077abe093fd..fc157b6853b6c 100644 --- a/operator/bundle.Dockerfile.extra +++ b/operator/bundle.Dockerfile.extra @@ -1,3 +1,6 @@ +# Reproducible builds support +ARG SOURCE_DATE_EPOCH + # Labels for operator certification https://redhat-connect.gitbook.io/certified-operator-guide/ocp-deployment/operator-metadata/bundle-directory LABEL com.redhat.delivery.operator.bundle=true