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.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 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