diff --git a/Makefile b/Makefile index 17ddf99a..d00b6f40 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 1.1.0 +VERSION ?= 1.1.1 # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") @@ -175,8 +175,8 @@ build-installer: manifests generate kustomize ## Generate a consolidated YAML wi # Run sample attestation in a kind cluster # pre-requirements: kuttl plugin and kind are installed # Usage: KBS_IMAGE_NAME= CLIENT_IMAGE_NAME= make test-e2e -KBS_IMAGE_NAME ?= ghcr.io/confidential-containers/key-broker-service:built-in-as-v0.17.0 -CLIENT_IMAGE_NAME ?= quay.io/confidential-containers/kbs-client:v0.17.0 +KBS_IMAGE_NAME ?= ghcr.io/confidential-containers/key-broker-service:built-in-as-v0.18.0 +CLIENT_IMAGE_NAME ?= quay.io/confidential-containers/kbs-client:v0.18.0 .PHONY: test-e2e test-e2e: ./tests/scripts/kind-with-registry.sh diff --git a/bundle.Dockerfile b/bundle.Dockerfile index ad57ca73..e4e41e33 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=trustee-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.40.0 +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 diff --git a/bundle/manifests/trustee-operator.clusterserviceversion.yaml b/bundle/manifests/trustee-operator.clusterserviceversion.yaml index ca0fa467..e997be5a 100644 --- a/bundle/manifests/trustee-operator.clusterserviceversion.yaml +++ b/bundle/manifests/trustee-operator.clusterserviceversion.yaml @@ -23,7 +23,7 @@ metadata: features.operators.openshift.io/token-auth-gcp: "false" operators.openshift.io/valid-subscription: '["OpenShift Container Platform", "OpenShift Platform Plus"]' repository: https://github.com/openshift/trustee-operator - name: trustee-operator.v1.1.0 + name: trustee-operator.v1.1.1 namespace: placeholder labels: operatorframework.io/os.linux: supported @@ -183,7 +183,7 @@ spec: - name: KBS_IMAGE_NAME value: "registry.redhat.io/build-of-trustee/trustee-rhel9@sha256:7c132a71a7a374f594e36e5c9f255e6e0cca7ad456b81ddc9f93b0d5c39dec06" - name: KBS_IMAGE_NAME_MICROSERVICES - value: ghcr.io/confidential-containers/key-broker-service:v0.17.0 + value: ghcr.io/confidential-containers/key-broker-service:v0.18.0 - name: AS_IMAGE_NAME value: ghcr.io/confidential-containers/staged-images/coco-as-grpc:latest - name: RVPS_IMAGE_NAME @@ -205,10 +205,10 @@ spec: resources: limits: cpu: 500m - memory: 128Mi + memory: 512Mi requests: cpu: 10m - memory: 64Mi + memory: 256Mi securityContext: allowPrivilegeEscalation: false capabilities: @@ -279,5 +279,5 @@ spec: relatedImages: - image: registry.redhat.io/build-of-trustee/trustee-rhel9@sha256:7c132a71a7a374f594e36e5c9f255e6e0cca7ad456b81ddc9f93b0d5c39dec06 name: trustee - replaces: trustee-operator.v1.0.0 - version: 1.1.0 + replaces: trustee-operator.v1.1.0 + version: 1.1.1 diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 9cd95119..7df26e4f 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: quay.io/confidential-containers/trustee-operator - newTag: v1.1.0 + newTag: v1.1.1 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 524c7fda..0a909d9d 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -74,7 +74,7 @@ spec: args: - --leader-elect - --health-probe-bind-address=:8081 - image: quay.io/confidential-containers/trustee-operator:v1.1.0 + image: quay.io/confidential-containers/trustee-operator:v1.1.1 name: manager # Add the following environment variables to the manager container # POD_NAMESPACE @@ -85,10 +85,10 @@ spec: fieldPath: metadata.namespace - name: KBS_IMAGE_NAME # kbs image for AllInOneDeployment - value: ghcr.io/confidential-containers/key-broker-service:built-in-as-v0.17.0 + value: ghcr.io/confidential-containers/key-broker-service:built-in-as-v0.18.0 # kbs image for MicroserviceDeployment - name: KBS_IMAGE_NAME_MICROSERVICES - value: ghcr.io/confidential-containers/key-broker-service:v0.17.0 + value: ghcr.io/confidential-containers/key-broker-service:v0.18.0 - name: AS_IMAGE_NAME value: ghcr.io/confidential-containers/staged-images/coco-as-grpc:latest - name: RVPS_IMAGE_NAME @@ -115,9 +115,9 @@ spec: resources: limits: cpu: 500m - memory: 128Mi + memory: 512Mi requests: cpu: 10m - memory: 64Mi + memory: 256Mi serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 diff --git a/config/manifests/bases/trustee-operator.clusterserviceversion.yaml b/config/manifests/bases/trustee-operator.clusterserviceversion.yaml index bf005507..0535e6b4 100644 --- a/config/manifests/bases/trustee-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/trustee-operator.clusterserviceversion.yaml @@ -5,7 +5,7 @@ metadata: alm-examples: '[]' capabilities: Basic Install categories: Security - containerImage: quay.io/confidential-containers/trustee-operator:v1.1.0 + containerImage: quay.io/confidential-containers/trustee-operator:v1.1.1 features.operators.openshift.io/disconnected: "true" features.operators.openshift.io/fips-compliant: "false" features.operators.openshift.io/proxy-aware: "true" @@ -15,7 +15,7 @@ metadata: features.operators.openshift.io/token-auth-gcp: "false" operatorframework.io/suggested-namespace: trustee-operator-system support: Confidential Containers Community - name: trustee-operator.v1.1.0 + name: trustee-operator.v1.1.1 namespace: placeholder spec: apiservicedefinitions: {} @@ -63,5 +63,5 @@ spec: provider: name: Confidential Containers Community url: https://github.com/confidential-containers - replaces: trustee-operator.v1.0.0 - version: 1.1.0 + replaces: trustee-operator.v1.1.0 + version: 1.1.1 diff --git a/config/templates/ear_default_attestation_policy_cpu.rego b/config/templates/ear_default_attestation_policy_cpu.rego index 0b865d47..ad503af5 100644 --- a/config/templates/ear_default_attestation_policy_cpu.rego +++ b/config/templates/ear_default_attestation_policy_cpu.rego @@ -176,7 +176,7 @@ hardware := 2 if { # Check TDX Module hash # input.tdx.quote.body.mr_seam in query_reference_value("mr_seam") - + # # Check OVMF code hash input.tdx.quote.body.mr_td in query_reference_value("mr_td") @@ -240,24 +240,24 @@ tdx_uefi_event_tdvfkernelparams_ok if { ##### Azure vTPM SNP executables := 3 if { - input.az_snp_vtpm - - input.az_snp_vtpm.measurement in query_reference_value("measurement") - input.az_snp_vtpm.tpm.pcr03 in query_reference_value("snp_pcr03") - input.az_snp_vtpm.tpm.pcr08 in query_reference_value("snp_pcr08") - input.az_snp_vtpm.tpm.pcr09 in query_reference_value("snp_pcr09") - input.az_snp_vtpm.tpm.pcr11 in query_reference_value("snp_pcr11") - input.az_snp_vtpm.tpm.pcr12 in query_reference_value("snp_pcr12") + input["az-snp-vtpm"] + + input["az-snp-vtpm"].measurement in query_reference_value("measurement") + input["az-snp-vtpm"].tpm.pcr03 in query_reference_value("snp_pcr03") + input["az-snp-vtpm"].tpm.pcr08 in query_reference_value("snp_pcr08") + input["az-snp-vtpm"].tpm.pcr09 in query_reference_value("snp_pcr09") + input["az-snp-vtpm"].tpm.pcr11 in query_reference_value("snp_pcr11") + input["az-snp-vtpm"].tpm.pcr12 in query_reference_value("snp_pcr12") } hardware := 2 if { - input.az_snp_vtpm + input["az-snp-vtpm"] # Check the reported TCB to validate the ASP FW - input.az_snp_vtpm.reported_tcb_bootloader in query_reference_value("tcb_bootloader") - input.az_snp_vtpm.reported_tcb_microcode in query_reference_value("tcb_microcode") - input.az_snp_vtpm.reported_tcb_snp in query_reference_value("tcb_snp") - input.az_snp_vtpm.reported_tcb_tee in query_reference_value("tcb_tee") + input["az-snp-vtpm"].reported_tcb_bootloader in query_reference_value("tcb_bootloader") + input["az-snp-vtpm"].reported_tcb_microcode in query_reference_value("tcb_microcode") + input["az-snp-vtpm"].reported_tcb_snp in query_reference_value("tcb_snp") + input["az-snp-vtpm"].reported_tcb_tee in query_reference_value("tcb_tee") } # For the 'configuration' trust claim 2 stands for @@ -265,50 +265,50 @@ hardware := 2 if { # # For this, we compare all the configuration fields. configuration := 2 if { - input.az_snp_vtpm - - input.az_snp_vtpm.platform_smt_enabled in query_reference_value("smt_enabled") - input.az_snp_vtpm.platform_tsme_enabled in query_reference_value("tsme_enabled") - input.az_snp_vtpm.policy_abi_major in query_reference_value("abi_major") - input.az_snp_vtpm.policy_abi_minor in query_reference_value("abi_minor") - input.az_snp_vtpm.policy_single_socket in query_reference_value("single_socket") - input.az_snp_vtpm.policy_smt_allowed in query_reference_value("smt_allowed") + input["az-snp-vtpm"] + + input["az-snp-vtpm"].platform_smt_enabled in query_reference_value("smt_enabled") + input["az-snp-vtpm"].platform_tsme_enabled in query_reference_value("tsme_enabled") + input["az-snp-vtpm"].policy_abi_major in query_reference_value("abi_major") + input["az-snp-vtpm"].policy_abi_minor in query_reference_value("abi_minor") + input["az-snp-vtpm"].policy_single_socket in query_reference_value("single_socket") + input["az-snp-vtpm"].policy_smt_allowed in query_reference_value("smt_allowed") } ##### Azure vTPM TDX executables := 3 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] - input.az_tdx_vtpm.tpm.pcr03 in query_reference_value("tdx_pcr03") - input.az_tdx_vtpm.tpm.pcr08 in query_reference_value("tdx_pcr08") - input.az_tdx_vtpm.tpm.pcr09 in query_reference_value("tdx_pcr09") - input.az_tdx_vtpm.tpm.pcr11 in query_reference_value("tdx_pcr11") - input.az_tdx_vtpm.tpm.pcr12 in query_reference_value("tdx_pcr12") + input["az-tdx-vtpm"].tpm.pcr03 in query_reference_value("tdx_pcr03") + input["az-tdx-vtpm"].tpm.pcr08 in query_reference_value("tdx_pcr08") + input["az-tdx-vtpm"].tpm.pcr09 in query_reference_value("tdx_pcr09") + input["az-tdx-vtpm"].tpm.pcr11 in query_reference_value("tdx_pcr11") + input["az-tdx-vtpm"].tpm.pcr12 in query_reference_value("tdx_pcr12") } hardware := 2 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] # Check the quote is a TDX quote signed by Intel SGX Quoting Enclave - input.az_tdx_vtpm.quote.header.tee_type == "81000000" - input.az_tdx_vtpm.quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" + input["az-tdx-vtpm"].quote.header.tee_type == "81000000" + input["az-tdx-vtpm"].quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" # Check TDX Module hash # input.tdx.quote.body.mr_seam in query_reference_value("mr_seam") # # Check OVMF code hash -input.az_tdx_vtpm.quote.body.mr_td in query_reference_value("mr_td") +input["az-tdx-vtpm"].quote.body.mr_td in query_reference_value("mr_td") # Check TCB status (covers quote.body.tcb_svn claim check) -input.az_tdx_vtpm.tcb_status == "UpToDate" +input["az-tdx-vtpm"].tcb_status == "UpToDate" # Check minimum TCB date (See TDX section for details.) } configuration := 2 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] - input.az_tdx_vtpm.quote.body.xfam in query_reference_value("xfam") + input["az-tdx-vtpm"].quote.body.xfam in query_reference_value("xfam") } ##### TPM @@ -326,8 +326,23 @@ configuration := 0 if { input.tpm } -##### SE TODO +##### IBM Secure Execution for Linux (SEL) +# Only field existence is checked. No value check is necessary. +# The SE verifier performs cryptographic verification including +# measurements, signatures, and user_data binding. +# If the field exists, it means the verifaction is successful. +# This is a 'trust-the-verifier' approach. +executables := 3 if { + input.se +} + +hardware := 2 if { + input.se +} +configuration := 2 if { + input.se +} ################################# # EXTENSIONS diff --git a/go.mod b/go.mod index 9df871cb..11ba5650 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/confidential-containers/trustee-operator -go 1.24.0 +go 1.25.0 -toolchain go1.24.6 +toolchain go1.25.9 require ( - github.com/go-logr/logr v1.4.2 + github.com/go-logr/logr v1.4.3 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/openshift/api v0.0.0-20251020095937-6a0c921fc0f5 @@ -16,11 +16,11 @@ require ( ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect @@ -41,7 +41,7 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -59,33 +59,33 @@ require ( github.com/spf13/pflag v1.0.9 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.41.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/tools v0.42.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5300501d..7dd3370f 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -29,8 +29,8 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8 github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -64,8 +64,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -115,8 +115,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= @@ -133,32 +133,32 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -180,48 +180,50 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 65b86227..b6e40fb8 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2023. +Copyright Confidential Containers Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -*/ \ No newline at end of file +*/ diff --git a/hack/release/README.md b/hack/release/README.md new file mode 100644 index 00000000..9092756d --- /dev/null +++ b/hack/release/README.md @@ -0,0 +1,435 @@ +# Release Automation Scripts + +This directory contains scripts to automate the release process for trustee-operator. + +## Quick Start + +For a complete release, use the all-in-one script: + +```bash +./hack/release/do-release.sh 0.18.0 \ + --fork git@github.com:yourusername/trustee-operator.git --dry-run # Verify first +./hack/release/do-release.sh 0.18.0 \ + --fork git@github.com:yourusername/trustee-operator.git # Execute release +``` + +## Script Overview + +| Script | Purpose | When to Use | +|--------|---------|-------------| +| `do-release.sh` ⭐ | Complete end-to-end release automation | **Most releases** - handles everything automatically | +| `build-and-push.sh` | Build and push images | Manual testing or custom image builds | +| `bump-version.sh` | Version bump only | Manual workflow or when testing version changes | +| `prepare-community-operators.sh` | Prepare bundle for community-operators | After release, to submit to OperatorHub | + +## Scripts + +### do-release.sh + +**⭐ Recommended** - Complete end-to-end release automation. + +Prepares version, bundle files, and creates pull request. Images are built automatically by GitHub Actions after PR is merged and release is tagged. + +**Usage:** +```bash +./hack/release/do-release.sh --fork FORK [options] +``` + +**Required Options:** +- `--fork FORK` - Your fork URL to push to (e.g., `git@github.com:username/trustee-operator.git`) + +**Optional Options:** +- `--skip-push` - Skip git push and PR creation (for testing) +- `--skip-pr` - Skip creating PR (but still push to fork) +- `--dry-run` - Show what would be done without making changes + +**Examples:** +```bash +# Complete release preparation (recommended) +./hack/release/do-release.sh 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git + +# Dry run first to verify +./hack/release/do-release.sh 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git --dry-run + +# Prepare release without pushing +./hack/release/do-release.sh 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git --skip-push + +# Push to fork but skip PR creation +./hack/release/do-release.sh 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git --skip-pr +``` + +**What it does:** +1. Run tests +2. Bump version in all files +3. Regenerate bundle manifests +4. Commit changes to branch `release-v{VERSION}` +5. Push to your fork +6. Create pull request to main + +**After PR is merged:** +- Create GitHub release with tag `vX.Y.Z` +- GitHub Actions will automatically build and push multi-arch operator image +- Submit bundle files to community-operators using `prepare-community-operators.sh` + +### bump-version.sh + +Bumps the version across all relevant files in the repository. + +**Usage:** +```bash +./hack/release/bump-version.sh +``` + +**Example:** +```bash +./hack/release/bump-version.sh 0.18.0 +``` + +**What it does:** +- Updates VERSION in Makefile +- Updates image tags in all manifests (Makefile, bundle/, config/) +- Updates CSV (ClusterServiceVersion) files and `replaces` field +- Shows summary of changes + +### prepare-community-operators.sh + +Prepares the operator bundle for submission to the community-operators catalog. + +**Usage:** +```bash +./hack/release/prepare-community-operators.sh [version] --fork FORK [options] +``` + +**Required Options:** +- `--fork FORK` - **REQUIRED** Your fork URL to push to (e.g., `git@github.com:username/community-operators.git`) + +**Optional Options:** +- `--skip-push` - Skip pushing to fork and creating PR +- `--skip-pr` - Skip creating PR (but still push to fork) +- `--dry-run` - Show what would be done without making changes + +**Examples:** +```bash +# Complete automated submission with PR (RECOMMENDED) +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:lmilleri/community-operators.git + +# Dry run first to verify +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:myuser/community-operators.git --dry-run + +# Push to fork but skip PR creation +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:myuser/community-operators.git --skip-pr + +# Prepare files only (no push/PR) +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:myuser/community-operators.git --skip-push +``` + +**What it does:** +1. Clones k8s-operatorhub/community-operators to temp directory +2. Adds your fork as 'fork' remote +3. Creates a new branch `trustee-operator-v{VERSION}` +4. Copies bundle files to `operators/trustee-operator/{VERSION}/`: + - `bundle/manifests/` - Operator manifests and CSV + - `bundle/metadata/` - Bundle metadata + - `bundle/tests/` - Scorecard test configuration + - Note: `bundle.Dockerfile` is NOT copied (auto-generated by community-operators) +5. Creates git commit with the changes +6. Pushes to your fork (unless `--skip-push`) +7. Creates pull request to upstream (unless `--skip-pr` or `--skip-push`) + +**After running:** +- If PR was created automatically: Monitor the PR, respond to reviews, wait for CI checks +- If using `--skip-pr`: Create PR manually with `gh pr create --base main` +- If using `--skip-push`: Push manually and then create PR + +### build-and-push.sh + +Builds and pushes multi-platform trustee-operator images using docker buildx. + +**Note:** This is not part of the standard release workflow. GitHub Actions builds images automatically when you create a GitHub release. + +**Usage:** +```bash +./hack/release/build-and-push.sh [version] [options] +``` + +**Options:** +- `--registry REGISTRY` - Container registry (default: `quay.io/confidential-containers`) +- `--skip-docker-push` - Build single-platform image locally without pushing (linux/amd64 only) +- `--dry-run` - Show what would be done without making changes + +**Examples:** +```bash +# Multi-platform build and push (default behavior) +./hack/release/build-and-push.sh 0.18.0 + +# Dry run first +./hack/release/build-and-push.sh 0.18.0 --dry-run + +# Build single-platform locally without pushing (loads to local Docker) +./hack/release/build-and-push.sh 0.18.0 --skip-docker-push + +# Custom registry +./hack/release/build-and-push.sh 0.18.0 --registry myregistry.io/myorg +``` + +**What it does:** +1. Runs tests +2. Generates manifests (make manifests) +3. Generates bundle files (make bundle) +4. Cleans up duplicate kustomization.yaml entries +5. Builds operator image using docker buildx: + - **Default**: Multi-platform build and push (linux/amd64, linux/arm64, linux/s390x) + - **With `--skip-docker-push`**: Single-platform build (linux/amd64) loaded to local Docker + +**Important:** +- Multi-platform builds require push to registry (cannot be loaded locally) +- Use `--skip-docker-push` for local testing with single-platform builds only +- Bundle/catalog images are not built (community-operators uses bundle files only) + +## Release Workflow + +### Recommended: Complete Automated Release + +**For most releases, use the all-in-one `do-release.sh` script:** + +```bash +# 1. Ensure you're on main branch and it's up to date +git checkout main +git pull + +# 2. Run the complete release (dry-run first recommended) +./hack/release/do-release.sh 0.18.0 \ + --fork git@github.com:yourusername/trustee-operator.git --dry-run +./hack/release/do-release.sh 0.18.0 \ + --fork git@github.com:yourusername/trustee-operator.git + +# 3. Merge the PR that was created + +# 4. Create GitHub release +# Go to https://github.com/confidential-containers/trustee-operator/releases/new +# - Tag: v0.18.0 +# - Title: Release v0.18.0 +# - Description: Add changelog and release notes +# GitHub Actions will automatically build and push multi-arch operator image + +# 5. Submit to community-operators (OperatorHub) +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:yourusername/community-operators.git +``` + +This handles version bumping, bundle generation, and PR creation. Images are built by GitHub Actions after the release is tagged. + +**Note:** If you need to manually build images for testing, use `build-and-push.sh`, but this is not part of the standard release workflow. + +### Manual Step-by-Step Workflow + +If you prefer to run each step separately: + +```bash +VERSION=0.18.0 + +# Step 1: Bump version +./hack/release/bump-version.sh ${VERSION} + +# Step 2: Review changes +git diff + +# Step 3: Regenerate bundle +make bundle IMG=quay.io/confidential-containers/trustee-operator:v${VERSION} + +# Step 4: Commit and push to fork +git add -A +git commit -m "Release v${VERSION}" +git push fork release-v${VERSION} + +# Step 5: Create PR manually +gh pr create --base main --title "Release v${VERSION}" + +# Step 6: After PR merge, create GitHub release +# GitHub Actions will build and push the operator image + +# Step 7: Submit to community-operators +./hack/release/prepare-community-operators.sh ${VERSION} \ + --fork git@github.com:yourusername/community-operators.git +``` + +### Manual Image Building (For Testing Only) + +**Note:** This is not part of the standard release workflow. GitHub Actions builds images automatically when you create a GitHub release. + +For local testing or manual builds: + +```bash +VERSION=0.18.0 + +# Build and push multi-platform operator images (default) +./hack/release/build-and-push.sh ${VERSION} + +# Dry run to verify first +./hack/release/build-and-push.sh ${VERSION} --dry-run + +# Single-platform build for local testing (loads to local Docker, no push) +./hack/release/build-and-push.sh ${VERSION} --skip-docker-push +``` + +## Files Updated During Release + +The version bump process updates the following files: + +- `Makefile` - VERSION variable and image tags +- `bundle/manifests/trustee-operator.clusterserviceversion.yaml` - CSV manifest +- `config/manager/manager.yaml` - Manager deployment manifest +- `config/manager/kustomization.yaml` - Image tag in kustomization +- `config/manifests/bases/trustee-operator.clusterserviceversion.yaml` - Base CSV + +## Version Format + +Versions must follow semantic versioning: `MAJOR.MINOR.PATCH` (e.g., `0.18.0`, `1.0.0`) + +## Troubleshooting + +### Go Toolchain Version + +The release scripts rely on Go's automatic toolchain management (available in Go 1.21+). When `go.mod` specifies a toolchain version, Go will automatically download and use it. + +**Recommended approach:** Use the default `GOTOOLCHAIN=auto` setting. This allows Go to automatically download the exact toolchain version specified in `go.mod`, ensuring consistent builds across all contributors. + +**If you encounter toolchain errors:** + +1. **Ensure you have Go 1.21 or later:** + ```bash + go version # Should be 1.21 or higher + ``` + +2. **Verify GOTOOLCHAIN is set to auto (default):** + ```bash + go env GOTOOLCHAIN # Should be "auto" + ``` + +3. **Only if auto-download fails** (e.g., due to network restrictions), you can force use of your local Go installation: + ```bash + export GOTOOLCHAIN=local + make test + make bundle + ``` + **Warning:** This may fail if your local Go version doesn't match the toolchain directive in `go.mod`. Use this only as a last resort for troubleshooting. + +### Tests Failing + +If tests fail during the release process: +```bash +# Run tests manually to see detailed output +make test + +# Fix the failing tests before proceeding with the release +# Tests cannot be skipped - they must pass for a release +``` + +### Docker Build Issues + +If docker build fails: +```bash +# Check Docker is running +docker info + +# Try building manually +make docker-build IMG=quay.io/confidential-containers/trustee-operator:v0.18.0 + +# Check if you're logged into the registry +docker login quay.io +``` + +### Bundle Generation Issues + +If bundle generation fails: +```bash +# Regenerate manifests first +make manifests + +# Then regenerate bundle +make bundle +``` + +## CI/CD Integration + +After creating a GitHub release with a tag (e.g., `v0.18.0`), GitHub Actions will automatically: +1. Build and test the operator +2. Build and push multi-arch operator image (linux/amd64, linux/arm64, linux/s390x) +3. Create release artifacts + +**Note:** Bundle and catalog images are not built by CI/CD. Only bundle files (in the `bundle/` directory) are submitted to community-operators. + +Monitor the pipeline at: https://github.com/confidential-containers/trustee-operator/actions + +## Submitting to Community Operators (OperatorHub) + +After a successful release, submit the operator to OperatorHub: + +### Prerequisites + +1. Fork the community-operators repository: + - https://github.com/k8s-operatorhub/community-operators + +### Automated Submission Workflow (Recommended) + +```bash +# Complete automated submission with PR creation +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:yourusername/community-operators.git + +# The script will: +# 1. Remove existing work directory (if present) +# 2. Clone k8s-operatorhub/community-operators +# 3. Add your fork as 'fork' remote +# 4. Create branch trustee-operator-v0.18.0 +# 5. Copy bundle to operators/trustee-operator/0.18.0/ +# 6. Create and push commit +# 7. Create pull request to upstream + +# Then monitor the PR and respond to reviews +``` + +### Manual Submission Workflow + +If you prefer to review before pushing: + +```bash +# 1. Prepare bundle (skip push/PR) +./hack/release/prepare-community-operators.sh 0.18.0 \ + --fork git@github.com:yourusername/community-operators.git \ + --skip-push + +# 2. Review the changes +cd /tmp/community-operators-0.18.0 +git status +git diff + +# 3. Test the bundle (optional but recommended) +operator-sdk bundle validate operators/trustee-operator/0.18.0 + +# 4. Push to your fork and create PR +git push fork trustee-operator-v0.18.0 +gh pr create --base main +``` + +### PR Guidelines + +Follow the community-operators contribution guidelines: +- https://k8s-operatorhub.github.io/community-operators/contributing-via-pr/ + +Your PR should: +- Include only the new version directory +- Pass all CI checks +- Include testing evidence +- Follow the operator certification requirements + +### Important Notes + +- Each version can only be submitted once to community-operators +- The script clones to a fresh temp directory each time: `${TMPDIR:-/tmp}/community-operators-{VERSION}` +- If a version already exists upstream, the script will detect it and skip submission +- Use `--skip-pr` if you want to review the changes in a browser before creating the PR diff --git a/hack/release/build-and-push.sh b/hack/release/build-and-push.sh new file mode 100755 index 00000000..83750ff2 --- /dev/null +++ b/hack/release/build-and-push.sh @@ -0,0 +1,337 @@ +#!/usr/bin/env bash + +# Copyright Confidential Containers Contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +# Source common functions +source "${SCRIPT_DIR}/common.sh" + +usage() { + cat </dev/null; then + CREATED_BUILDER=true + log_info "Created temporary builder: ${BUILDER_NAME}" + else + # Create failed - verify the builder actually exists before proceeding + if docker buildx inspect "${BUILDER_NAME}" &>/dev/null; then + log_warn "Builder ${BUILDER_NAME} already exists, reusing it" + else + log_error "Failed to create buildx builder: ${BUILDER_NAME}" + log_error "" + log_error "This may indicate:" + log_error " - Docker is not running" + log_error " - docker buildx is not available (update Docker to a recent version)" + log_error " - Insufficient permissions" + log_error "" + log_error "Verify with: docker buildx version" + exit 1 + fi + fi + + # Build with --load instead of --push (single platform only) + docker buildx build --builder "${BUILDER_NAME}" --load --platform="linux/amd64" --tag "${IMG}" . || { + log_error "Docker buildx failed" + # Only remove builder if we created it + if [[ "${CREATED_BUILDER}" == "true" ]]; then + docker buildx rm "${BUILDER_NAME}" 2>/dev/null || true + fi + exit 1 + } + + # Clean up builder only if we created it + if [[ "${CREATED_BUILDER}" == "true" ]]; then + docker buildx rm "${BUILDER_NAME}" 2>/dev/null || true + log_info "Removed temporary builder: ${BUILDER_NAME}" + fi + + log_info "Image loaded into local Docker (not pushed to registry)" + else + # Build and push with buildx (via Makefile) - multi-platform + log_info "Building and pushing multi-platform images with docker buildx..." + make docker-buildx IMG="${IMG}" PLATFORMS="${PLATFORMS}" || { + log_error "Docker buildx failed" + exit 1 + } + fi +else + if [[ "${SKIP_DOCKER_PUSH}" == "true" ]]; then + log_info "[DRY RUN] Would run: docker buildx build --builder --load --platform=linux/amd64 --tag ${IMG} ." + else + log_info "[DRY RUN] Would run: make docker-buildx IMG=${IMG} PLATFORMS=${PLATFORMS}" + fi +fi +echo "" + +# Step 5: Push docker image +if [[ "${SKIP_DOCKER_PUSH}" == "false" ]]; then + log_step "Step 5: Pushing operator image" + if [[ "${DRY_RUN}" == "false" ]]; then + # Buildx with multiple platforms already pushed in step 4 + log_info "Multi-platform images already pushed by docker-buildx in step 4" + else + log_info "[DRY RUN] Images would be pushed by docker-buildx in step 4" + fi + echo "" +else + log_step "Step 5: Push docker image" + log_warn "Skipping docker push (--skip-docker-push)" + if [[ "${DRY_RUN}" == "false" ]]; then + log_info "Single-platform image built with buildx --load (loaded into local Docker only)" + fi + echo "" +fi + +# Summary +log_info "================================================================" +log_info "Build and Push Complete!" +log_info "================================================================" +log_info "" + +if [[ "${DRY_RUN}" == "false" ]]; then + if [[ "${SKIP_DOCKER_PUSH}" == "false" ]]; then + log_info "Multi-platform images built and pushed:" + log_info " ✓ ${IMG}" + log_info " Platforms: ${PLATFORMS}" + else + log_info "Single-platform image built (not pushed):" + log_info " ${IMG}" + log_info " Platform: linux/amd64 (loaded to local Docker)" + fi + echo "" + log_info "Bundle files generated in: bundle/" + log_info " - manifests/" + log_info " - metadata/" + log_info " - tests/" + echo "" + if [[ "${SKIP_DOCKER_PUSH}" == "false" ]]; then + log_info "Next steps:" + log_info " 1. Verify operator image in registry: ${IMG}" + log_info " 2. Test the operator deployment" + log_info " 3. Create GitHub release" + log_info " 4. Submit bundle files to community-operators" + else + log_info "Next steps:" + log_info " 1. Test the operator locally: docker run ${IMG}" + log_info " 2. Push to registry when ready: docker push ${IMG}" + fi +else + log_info "This was a dry run. No changes were made." + log_info "Run without --dry-run to perform the actual build and push." +fi + +echo "" diff --git a/hack/release/bump-version.sh b/hack/release/bump-version.sh new file mode 100755 index 00000000..82c6368d --- /dev/null +++ b/hack/release/bump-version.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +# Copyright Confidential Containers Contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +# Source common functions +source "${SCRIPT_DIR}/common.sh" + +# Disable git pager for better script automation +export GIT_PAGER=cat + +usage() { + cat < + +Bumps the version of trustee-operator from the current version to the specified new version. + +Arguments: + new-version The new version to set (e.g., 0.18.0) + +Examples: + $0 0.18.0 + $0 1.0.0 + +This script will update version references in: + - Makefile + - bundle/manifests/trustee-operator.clusterserviceversion.yaml (including replaces field) + - config/manager/manager.yaml + - config/manager/kustomization.yaml + - config/manifests/bases/trustee-operator.clusterserviceversion.yaml (including replaces field) +EOF +} + +if [[ $# -ne 1 ]]; then + usage + exit 1 +fi + +NEW_VERSION="$1" + +# Validate version format (X.Y.Z) +validate_version "${NEW_VERSION}" || exit 1 + +# Extract current version from Makefile +CURRENT_VERSION=$(get_version_from_makefile) || exit 1 + +log_info "Current version: ${CURRENT_VERSION}" +log_info "New version: ${NEW_VERSION}" + +if [[ "${CURRENT_VERSION}" == "${NEW_VERSION}" ]]; then + log_warn "Current version and new version are the same. Nothing to do." + exit 0 +fi + +# Escape dots in version strings for use in regex patterns +# In sed -E, . matches any character, so we need to escape it to match literal dots +# Only the search pattern (left side of s///) needs escaping; replacement string does not +CURRENT_VERSION_ESCAPED="${CURRENT_VERSION//./\\.}" + +# Confirm with user +echo "" +read -p "Do you want to proceed with version bump from ${CURRENT_VERSION} to ${NEW_VERSION}? [y/N] " -n 1 -r +echo "" +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Aborted by user" + exit 0 +fi + +log_info "Updating version references..." + +# Update version references in files +log_info "Updating files..." + +# Update Makefile - VERSION variable and image tags +sed_inplace "s/^VERSION \?= ${CURRENT_VERSION_ESCAPED}/VERSION \?= ${NEW_VERSION}/" "${ROOT_DIR}/Makefile" +sed_inplace "s/:built-in-as-v${CURRENT_VERSION_ESCAPED}/:built-in-as-v${NEW_VERSION}/g" "${ROOT_DIR}/Makefile" +sed_inplace "s/:v${CURRENT_VERSION_ESCAPED}/:v${NEW_VERSION}/g" "${ROOT_DIR}/Makefile" +log_info " Updated Makefile" + +# Update bundle manifests +if [[ -f "${ROOT_DIR}/bundle/manifests/trustee-operator.clusterserviceversion.yaml" ]]; then + sed_inplace "s/:v${CURRENT_VERSION_ESCAPED}/:v${NEW_VERSION}/g" "${ROOT_DIR}/bundle/manifests/trustee-operator.clusterserviceversion.yaml" + sed_inplace "s/:built-in-as-v${CURRENT_VERSION_ESCAPED}/:built-in-as-v${NEW_VERSION}/g" "${ROOT_DIR}/bundle/manifests/trustee-operator.clusterserviceversion.yaml" + sed_inplace "s/trustee-operator\\.v${CURRENT_VERSION_ESCAPED}/trustee-operator.v${NEW_VERSION}/g" "${ROOT_DIR}/bundle/manifests/trustee-operator.clusterserviceversion.yaml" + sed_inplace "s/version: ${CURRENT_VERSION_ESCAPED}/version: ${NEW_VERSION}/" "${ROOT_DIR}/bundle/manifests/trustee-operator.clusterserviceversion.yaml" + # Update replaces field to point to the current version (the version we're replacing) + sed_inplace "s/replaces: trustee-operator\\.v[0-9]+\\.[0-9]+\\.[0-9]+/replaces: trustee-operator.v${CURRENT_VERSION}/" "${ROOT_DIR}/bundle/manifests/trustee-operator.clusterserviceversion.yaml" + log_info " Updated bundle/manifests/trustee-operator.clusterserviceversion.yaml" +fi + +# Update config/manager/manager.yaml +if [[ -f "${ROOT_DIR}/config/manager/manager.yaml" ]]; then + sed_inplace "s/:v${CURRENT_VERSION_ESCAPED}/:v${NEW_VERSION}/g" "${ROOT_DIR}/config/manager/manager.yaml" + sed_inplace "s/:built-in-as-v${CURRENT_VERSION_ESCAPED}/:built-in-as-v${NEW_VERSION}/g" "${ROOT_DIR}/config/manager/manager.yaml" + log_info " Updated config/manager/manager.yaml" +fi + +# Update config/manager/kustomization.yaml +if [[ -f "${ROOT_DIR}/config/manager/kustomization.yaml" ]]; then + sed_inplace "s/newTag: v${CURRENT_VERSION_ESCAPED}/newTag: v${NEW_VERSION}/" "${ROOT_DIR}/config/manager/kustomization.yaml" + log_info " Updated config/manager/kustomization.yaml" +fi + +# Update config/manifests/bases/trustee-operator.clusterserviceversion.yaml +if [[ -f "${ROOT_DIR}/config/manifests/bases/trustee-operator.clusterserviceversion.yaml" ]]; then + sed_inplace "s/:v${CURRENT_VERSION_ESCAPED}/:v${NEW_VERSION}/g" "${ROOT_DIR}/config/manifests/bases/trustee-operator.clusterserviceversion.yaml" + sed_inplace "s/trustee-operator\\.v${CURRENT_VERSION_ESCAPED}/trustee-operator.v${NEW_VERSION}/g" "${ROOT_DIR}/config/manifests/bases/trustee-operator.clusterserviceversion.yaml" + sed_inplace "s/version: ${CURRENT_VERSION_ESCAPED}/version: ${NEW_VERSION}/" "${ROOT_DIR}/config/manifests/bases/trustee-operator.clusterserviceversion.yaml" + # Update replaces field to point to the current version (the version we're replacing) + sed_inplace "s/replaces: trustee-operator\\.v[0-9]+\\.[0-9]+\\.[0-9]+/replaces: trustee-operator.v${CURRENT_VERSION}/" "${ROOT_DIR}/config/manifests/bases/trustee-operator.clusterserviceversion.yaml" + log_info " Updated config/manifests/bases/trustee-operator.clusterserviceversion.yaml" +fi + +log_info "" +log_info "Version bump complete!" +log_info "" +log_info "Summary of changes:" +git diff --stat 2>/dev/null || log_warn "Git not available or not a git repository" + +log_info "" +log_info "Next steps:" +log_info " 1. Review the changes above" +log_info " 2. Use the automated release workflow:" +log_info " ${SCRIPT_DIR}/do-release.sh ${NEW_VERSION} --fork YOUR_FORK_URL" +log_info "" +log_info " Or manually commit and create PR if needed" diff --git a/hack/release/common.sh b/hack/release/common.sh new file mode 100644 index 00000000..15072d99 --- /dev/null +++ b/hack/release/common.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash + +# Copyright Confidential Containers Contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Common functions and variables for release automation scripts +# Source this file in other scripts: source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +# Ensure this file is sourced, not executed +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + echo "Error: common.sh should be sourced, not executed directly" + exit 1 +fi + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Logging functions +log_info() { + echo -e "${GREEN}[INFO]${NC} $*" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $*" >&2 +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +log_step() { + echo -e "${BLUE}==>${NC} $*" +} + +# Portable in-place sed that works on both GNU and BSD +# Uses extended regex mode (-E) for better portability +sed_inplace() { + local pattern="$1" + local file="$2" + local tmpfile="${file}.tmp.$$" + + # Verify source file exists and is readable + if [[ ! -f "$file" ]] || [[ ! -r "$file" ]]; then + echo "Error: Cannot read file: $file" >&2 + return 1 + fi + + # Run sed and explicitly check exit status + if ! sed -E "$pattern" "$file" > "$tmpfile"; then + # sed failed - clean up temp file and abort + rm -f "$tmpfile" + echo "Error: sed failed on file: $file" >&2 + return 1 + fi + + # Move temp file to replace original + if ! mv "$tmpfile" "$file"; then + # mv failed - clean up temp file and abort + rm -f "$tmpfile" + echo "Error: Failed to replace file: $file" >&2 + return 1 + fi + + return 0 +} + +# Validate version format (X.Y.Z) +validate_version() { + local version="$1" + if ! [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + log_error "Invalid version format: ${version}" + log_error "Version must be in format X.Y.Z (e.g., 0.18.0)" + return 1 + fi + return 0 +} + +# Get version from Makefile +# Safe to use with set -u even if ROOT_DIR is unbound +get_version_from_makefile() { + local root_dir="${1:-${ROOT_DIR:-$COMMON_ROOT_DIR}}" + local version + version=$(awk '/^VERSION \?=/ {print $3}' "${root_dir}/Makefile" || echo "") + if [[ -z "${version}" ]]; then + log_error "Could not determine version from Makefile" + return 1 + fi + echo "${version}" +} + +# Remove duplicate image entries from kustomization.yaml +# Kustomize sometimes adds duplicate image entries; this cleans them up +remove_duplicate_kustomization_images() { + local root_dir="${1:-${ROOT_DIR:-$COMMON_ROOT_DIR}}" + local kustomization_file="${root_dir}/config/manager/kustomization.yaml" + + # Remove all image entries except the first "controller" entry + # Kustomize may add duplicates with name: controller or name: + local total_images + total_images=$(grep -c "^[[:space:]]*- name:" "${kustomization_file}" 2>/dev/null || echo "0") + + if [[ "${total_images}" -gt 1 ]]; then + log_info "Removing duplicate image entries from config/manager/kustomization.yaml" + # Keep only the first image entry (name: controller) + awk ' + /^[[:space:]]*-[[:space:]]+name:/ { + in_image++ + if (in_image > 1) { + skip = 1 + next + } + } + skip && /^[[:space:]]+(newName|newTag):/ { + next + } + skip && /^[[:space:]]*$/ { + skip = 0 + next + } + skip && /^[^[:space:]]/ { + skip = 0 + } + !skip + ' "${kustomization_file}" > "${kustomization_file}.tmp" && \ + mv "${kustomization_file}.tmp" "${kustomization_file}" + fi +} + +# Common directory setup +# These are set relative to common.sh (BASH_SOURCE[0] refers to this file when sourced) +COMMON_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMMON_ROOT_DIR="$(cd "${COMMON_SCRIPT_DIR}/../.." && pwd)" diff --git a/hack/release/do-release.sh b/hack/release/do-release.sh new file mode 100755 index 00000000..98798f2f --- /dev/null +++ b/hack/release/do-release.sh @@ -0,0 +1,631 @@ +#!/usr/bin/env bash + +# Copyright Confidential Containers Contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Complete release automation script +# Handles version bump, bundle generation, git operations, and PR workflow +# Images are built by GitHub Actions after release + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +# Source common functions +source "${SCRIPT_DIR}/common.sh" + +# Change to repository root immediately to ensure all git commands +# operate on the correct repository, regardless of where script is invoked from +cd "${ROOT_DIR}" + +# Verify we're inside a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo "Error: Not inside a git repository" + echo "Expected repository root: ${ROOT_DIR}" + exit 1 +fi + +# Disable git pager for better script automation +export GIT_PAGER=cat + +usage() { + cat < --fork FORK [options] + +⭐ Recommended - Complete end-to-end release automation for trustee-operator. +Prepares version, bundle files, and creates pull request. Images are built by GitHub Actions. + +Arguments: + new-version The new version to release (e.g., 0.18.0) + +Required Options: + --fork FORK Your fork URL to push to (e.g., git@github.com:username/trustee-operator.git) + +Optional Options: + --skip-push Skip pushing to fork and creating PR + --skip-pr Skip creating PR (but still push to fork) + --dry-run Show what would be done without making changes + -h, --help Show this help message + +Examples: + # Complete release preparation (recommended) + $0 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git + + # Dry run to see what would happen + $0 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git --dry-run + + # Prepare release without pushing (testing) + $0 0.18.0 --fork git@github.com:lmilleri/trustee-operator.git --skip-push + +Release steps performed: + 1. Run tests + 2. Bump version to new version + 3. Regenerate bundle manifests + 4. Commit changes to branch release-v{VERSION} + 5. Push to fork (unless --skip-push) + 6. Create pull request to main (unless --skip-push or --skip-pr) + +After PR is merged: + - Create GitHub release (tag v{VERSION}) + - GitHub Actions will automatically build and push multi-arch operator image + - Submit bundle files to community-operators using prepare-community-operators.sh +EOF +} + +# Parse arguments +NEW_VERSION="" +FORK_REPO="" +SKIP_PUSH=false +SKIP_PR=false +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case $1 in + --fork) + if [[ $# -lt 2 ]] || [[ -z "${2:-}" ]]; then + log_error "Missing value for --fork" + usage + exit 1 + fi + FORK_REPO="$2" + shift 2 + ;; + --skip-push) + SKIP_PUSH=true + shift + ;; + --skip-pr) + SKIP_PR=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + -*) + log_error "Unknown option: $1" + usage + exit 1 + ;; + *) + if [[ -z "${NEW_VERSION}" ]]; then + NEW_VERSION="$1" + else + log_error "Unexpected argument: $1" + usage + exit 1 + fi + shift + ;; + esac +done + +if [[ -z "${NEW_VERSION}" ]]; then + log_error "Missing required argument: new-version" + usage + exit 1 +fi + +# Validate required --fork parameter +if [[ -z "${FORK_REPO}" ]]; then + log_error "Missing required parameter: --fork" + log_error "You must specify your fork URL" + log_error "Example: $0 0.18.0 --fork git@github.com:yourusername/trustee-operator.git" + exit 1 +fi + +# Set branch name +BRANCH="release-v${NEW_VERSION}" + +# Validate version format +validate_version "${NEW_VERSION}" || exit 1 + +if [[ "${DRY_RUN}" == "true" ]]; then + log_warn "DRY RUN MODE - No changes will be made" + echo "" +fi + +log_info "Starting release process for version ${NEW_VERSION}" +log_info "Fork: ${FORK_REPO}" +log_info "Branch: ${BRANCH}" +echo "" + +# Check current version to avoid no-op releases +CURRENT_VERSION=$(get_version_from_makefile || echo "") +if [[ -n "${CURRENT_VERSION}" && "${CURRENT_VERSION}" == "${NEW_VERSION}" ]]; then + log_warn "Current version in Makefile is already ${NEW_VERSION}" + log_warn "Nothing to do - version is already set to the target version" + log_warn "If you need to regenerate bundle files, use: make bundle IMG=..." + exit 0 +fi + +# Check if git working directory is clean (including untracked files) +if [[ -n "$(git status --porcelain)" ]]; then + log_error "Git working directory is not clean. Please commit or stash your changes first." + git status --short + exit 1 +fi + +# Ensure we're on main branch to start +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [[ "${CURRENT_BRANCH}" != "main" ]]; then + log_warn "Not on main branch (current: ${CURRENT_BRANCH})" + if [[ "${DRY_RUN}" == "false" ]]; then + read -p "Switch to main branch? [y/N] " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + git checkout main || { + log_error "Failed to switch to main branch" + exit 1 + } + CURRENT_BRANCH="main" + else + log_info "Aborted by user" + exit 0 + fi + fi +fi + +# Ensure local main is up-to-date with upstream +if [[ "${DRY_RUN}" == "false" ]]; then + # Determine which remote to use for upstream sync + # Prefer 'upstream' remote if it exists (fork-based workflow) + # Otherwise use 'origin' (direct contributor workflow) + UPSTREAM_REMOTE="" + + if git remote get-url upstream &>/dev/null; then + UPSTREAM_REMOTE="upstream" + log_info "Found 'upstream' remote, will sync with upstream/main" + elif git remote get-url origin &>/dev/null; then + # Check if origin appears to be the canonical repo + ORIGIN_URL=$(git remote get-url origin) + if [[ "${ORIGIN_URL}" == *"confidential-containers/trustee-operator"* ]] || \ + [[ "${ORIGIN_URL}" == *"github.com/confidential-containers/trustee-operator"* ]]; then + UPSTREAM_REMOTE="origin" + log_info "Using 'origin' remote (appears to be canonical repo)" + else + log_warn "No 'upstream' remote found and 'origin' appears to be a fork" + log_warn "Cannot verify if main is up-to-date with canonical repository" + log_warn "Consider adding upstream remote: git remote add upstream " + # Don't exit - allow user to continue with warning + UPSTREAM_REMOTE="" + fi + fi + + if [[ -n "${UPSTREAM_REMOTE}" ]]; then + log_info "Fetching latest from ${UPSTREAM_REMOTE}..." + git fetch "${UPSTREAM_REMOTE}" || { + log_error "Failed to fetch from ${UPSTREAM_REMOTE}" + exit 1 + } + + # Check if upstream/main (or origin/main) exists + if ! git rev-parse "${UPSTREAM_REMOTE}/main" &>/dev/null; then + log_warn "${UPSTREAM_REMOTE}/main not found, skipping upstream check" + else + # Get commit hashes + LOCAL_MAIN=$(git rev-parse main) + REMOTE_MAIN=$(git rev-parse "${UPSTREAM_REMOTE}/main") + + if [[ "${LOCAL_MAIN}" == "${REMOTE_MAIN}" ]]; then + log_info "Local main is up-to-date with ${UPSTREAM_REMOTE}/main" + else + # Check relationship between local and remote + # If upstream/main is ancestor of main → local is ahead + # If main is ancestor of upstream/main → local is behind + if git merge-base --is-ancestor "${UPSTREAM_REMOTE}/main" main; then + log_warn "Local main is ahead of ${UPSTREAM_REMOTE}/main" + log_warn "You may want to push your changes before creating a release" + elif git merge-base --is-ancestor main "${UPSTREAM_REMOTE}/main"; then + log_warn "Local main is behind ${UPSTREAM_REMOTE}/main" + log_info "Attempting to fast-forward..." + + git pull --ff-only "${UPSTREAM_REMOTE}" main || { + log_error "Failed to fast-forward main branch" + log_error "Your local main has diverged from ${UPSTREAM_REMOTE}/main" + log_error "Please manually update your main branch before releasing" + exit 1 + } + log_info "Successfully updated main to match ${UPSTREAM_REMOTE}/main" + else + log_error "Local main has diverged from ${UPSTREAM_REMOTE}/main" + log_error "Please manually resolve this before releasing" + exit 1 + fi + fi + fi + fi +else + log_info "[DRY RUN] Would fetch and verify main is up-to-date with upstream" +fi + +# Add fork as remote if not already added +if [[ "${DRY_RUN}" == "false" ]]; then + if git remote get-url fork &>/dev/null; then + log_info "Fork remote already exists, updating URL..." + git remote set-url fork "${FORK_REPO}" + else + log_info "Adding fork as remote..." + git remote add fork "${FORK_REPO}" + fi +fi + +# Create and checkout release branch +if [[ "${DRY_RUN}" == "false" ]]; then + log_info "Creating branch: ${BRANCH}" + if git show-ref --verify --quiet refs/heads/"${BRANCH}"; then + log_error "Branch ${BRANCH} already exists locally" + log_error "" + log_error "This could be from a previous failed release attempt with unwanted commits." + log_error "To avoid accidentally including old commits in the release PR:" + log_error "" + log_error " 1. Delete the existing branch:" + log_error " git branch -D ${BRANCH}" + log_error "" + log_error " 2. Re-run this script to create a fresh branch from main" + log_error "" + exit 1 + else + git checkout -b "${BRANCH}" || { + log_error "Failed to create branch ${BRANCH}" + exit 1 + } + fi +else + log_info "[DRY RUN] Would create and checkout branch: ${BRANCH}" +fi + +# Step 1: Run tests +log_step "Step 1: Running tests" +if [[ "${DRY_RUN}" == "true" ]]; then + log_info "[DRY RUN] Would run: make test" +else + log_info "Running tests..." + make test || { + log_error "Tests failed. Aborting release." + exit 1 + } +fi +echo "" + +# Step 2: Bump version +log_step "Step 2: Bumping version to ${NEW_VERSION}" +if [[ "${DRY_RUN}" == "true" ]]; then + log_info "[DRY RUN] Would run: ${SCRIPT_DIR}/bump-version.sh ${NEW_VERSION}" +else + # Run bump-version.sh with auto-yes via stdin redirection + bash "${SCRIPT_DIR}/bump-version.sh" "${NEW_VERSION}" <<< "y" || { + log_error "Version bump failed. Aborting release." + exit 1 + } +fi +echo "" + +# Step 3: Regenerate bundle +log_step "Step 3: Regenerating bundle manifests" + +# Set IMG to the versioned image +REGISTRY="quay.io/confidential-containers" +IMAGE_TAG_BASE="${REGISTRY}/trustee-operator" +IMG="${IMAGE_TAG_BASE}:v${NEW_VERSION}" + +if [[ "${DRY_RUN}" == "true" ]]; then + log_info "[DRY RUN] Would run: make bundle IMG=${IMG}" +else + make bundle IMG="${IMG}" || { + log_error "Bundle generation failed. Aborting release." + exit 1 + } + + # Clean up duplicate image entry in kustomization.yaml that kustomize adds + remove_duplicate_kustomization_images "${ROOT_DIR}" + + log_info "Bundle generated successfully with image: ${IMG}" +fi +echo "" + +# Step 4: Commit changes +log_step "Step 4: Creating git commit" +if [[ "${DRY_RUN}" == "true" ]]; then + log_info "[DRY RUN] Would run: git add -A && git commit -m 'Release v${NEW_VERSION}'" + git diff --stat +else + git add -A + git commit -m "Release v${NEW_VERSION} + +- Bump version to ${NEW_VERSION} +- Regenerate bundle manifests" || { + log_error "Git commit failed. Aborting release." + exit 1 + } + git log -1 --oneline +fi +echo "" + +# Step 5: Push to fork and create PR +if [[ "${SKIP_PUSH}" == "false" ]]; then + if [[ "${SKIP_PR}" == "false" ]]; then + log_step "Step 5: Pushing to fork and creating PR" + else + log_step "Step 5: Pushing to fork" + fi + + # Extract fork owner from fork URL (needed for pushing to fork) + FORK_OWNER=$(echo "${FORK_REPO}" | sed -E 's|.*github\.com[:/]([^/]+)/.*|\1|') + + # Validate FORK_OWNER extraction + # Should be a GitHub username/org (no slashes, no URL artifacts) + # If extraction failed, sed returns the original string, so check for that too + if [[ -z "${FORK_OWNER}" ]] || \ + [[ "${FORK_OWNER}" == "${FORK_REPO}" ]] || \ + [[ "${FORK_OWNER}" == *"/"* ]] || \ + [[ "${FORK_OWNER}" == *":"* ]] || \ + [[ "${FORK_OWNER}" == *"@"* ]] || \ + [[ "${FORK_OWNER}" == *"://"* ]] || \ + [[ "${FORK_OWNER}" == *".git"* ]] || \ + [[ "${FORK_OWNER}" == *"github.com"* ]] || \ + [[ "${FORK_OWNER}" == *"."* ]]; then + log_error "Failed to extract fork owner from: ${FORK_REPO}" + log_error "Expected a GitHub URL like: git@github.com:username/trustee-operator.git" + log_error "Extracted owner: '${FORK_OWNER}' (invalid)" + exit 1 + fi + + # Determine upstream repo for PR target (only needed if creating PR) + if [[ "${SKIP_PR}" == "false" ]]; then + # Use same logic as sync check: prefer 'upstream' remote, fallback to 'origin' if canonical + UPSTREAM_REPO="" + if git remote get-url upstream &>/dev/null; then + UPSTREAM_URL=$(git remote get-url upstream) + UPSTREAM_REPO=$(echo "${UPSTREAM_URL}" | sed -E 's|.*github\.com[:/]([^/]+/[^/]+)(\.git)?$|\1|' | sed 's/\.git$//') + if [[ "${DRY_RUN}" == "false" ]]; then + log_info "Using 'upstream' remote for PR target: ${UPSTREAM_REPO}" + fi + elif git remote get-url origin &>/dev/null; then + ORIGIN_URL=$(git remote get-url origin) + # Check if origin appears to be the canonical repo + if [[ "${ORIGIN_URL}" == *"confidential-containers/trustee-operator"* ]] || \ + [[ "${ORIGIN_URL}" == *"github.com/confidential-containers/trustee-operator"* ]]; then + UPSTREAM_REPO=$(echo "${ORIGIN_URL}" | sed -E 's|.*github\.com[:/]([^/]+/[^/]+)(\.git)?$|\1|' | sed 's/\.git$//') + if [[ "${DRY_RUN}" == "false" ]]; then + log_info "Using 'origin' remote for PR target (canonical repo): ${UPSTREAM_REPO}" + fi + else + log_error "No 'upstream' remote found and 'origin' appears to be a fork" + log_error "Cannot determine canonical repository for PR target" + log_error "Please add upstream remote: git remote add upstream " + exit 1 + fi + else + log_error "No git remotes found" + exit 1 + fi + + # Validate UPSTREAM_REPO extraction + # Should be exactly "owner/repo" format (one slash, no URL artifacts) + if [[ -z "${UPSTREAM_REPO}" ]] || \ + [[ "${UPSTREAM_REPO}" != *"/"* ]] || \ + [[ "${UPSTREAM_REPO}" == *":"* ]] || \ + [[ "${UPSTREAM_REPO}" == *"@"* ]] || \ + [[ "${UPSTREAM_REPO}" == *"://"* ]] || \ + [[ "${UPSTREAM_REPO}" == *".git"* ]] || \ + [[ "${UPSTREAM_REPO}" == *"github.com"* ]] || \ + [[ $(echo "${UPSTREAM_REPO}" | grep -o "/" | wc -l) -ne 1 ]]; then + log_error "Failed to extract upstream repo" + log_error "Expected a GitHub URL like: git@github.com:org/repo.git" + log_error "Extracted repo: '${UPSTREAM_REPO}' (invalid)" + log_error "You can create the PR manually at GitHub" + exit 1 + fi + fi + + # Check GitHub CLI availability and authentication if we're going to create a PR + # Skip these checks in dry-run mode since gh won't actually be invoked + if [[ "${SKIP_PR}" == "false" && "${DRY_RUN}" == "false" ]]; then + # Check if gh command is available + if ! command -v gh &>/dev/null; then + log_error "GitHub CLI (gh) is not installed - cannot create pull request" + log_error "" + log_error "Install instructions:" + log_error " macOS: brew install gh" + log_error " Linux: https://github.com/cli/cli#installation" + log_error " Windows: https://github.com/cli/cli#installation" + log_error "" + log_error "After installation, authenticate with: gh auth login" + log_error "" + log_error "Alternatively, use --skip-pr to skip PR creation and create it manually" + exit 1 + fi + + # Check if gh is authenticated + if ! gh auth status &>/dev/null; then + log_error "GitHub CLI is not authenticated - cannot create pull request" + log_error "" + log_error "Run: gh auth login" + log_error "" + log_error "This will authenticate gh with your GitHub account and allow PR creation" + log_error "" + log_error "Alternatively, use --skip-pr to skip PR creation and create it manually" + exit 1 + fi + + log_info "GitHub CLI authenticated and ready for PR creation" + fi + + # Validate that we're creating PR to canonical repo (only if creating PR) + if [[ "${SKIP_PR}" == "false" ]]; then + # It's OK if fork == upstream (same-repo PR for direct contributors) + # But ensure upstream is the canonical repo + if [[ "${UPSTREAM_REPO}" != "confidential-containers/trustee-operator" ]]; then + FORK_REPO_PATH=$(echo "${FORK_REPO}" | sed -E 's|.*github\.com[:/]([^/]+/[^/]+)(\.git)?$|\1|' | sed 's/\.git$//') + log_warn "PR will be created to: ${UPSTREAM_REPO}" + log_warn "This does not appear to be the canonical repository" + log_warn "Expected: confidential-containers/trustee-operator" + log_warn "Got: ${UPSTREAM_REPO}" + + # If fork and upstream are the same and it's not canonical, that's definitely wrong + if [[ "${UPSTREAM_REPO}" == "${FORK_REPO_PATH}" ]]; then + log_error "Both fork and upstream point to non-canonical repo: ${UPSTREAM_REPO}" + log_error "Please configure 'upstream' remote to point to canonical repository" + log_error " git remote add upstream git@github.com:confidential-containers/trustee-operator.git" + exit 1 + fi + fi + fi + + # Now do the actual push and PR creation (or show dry-run output) + if [[ "${DRY_RUN}" == "true" ]]; then + log_info "[DRY RUN] Would run: git push -u fork ${BRANCH}" + if [[ "${SKIP_PR}" == "false" ]]; then + log_info "[DRY RUN] Would run: gh pr create --repo \"${UPSTREAM_REPO}\" --base main --head \"${FORK_OWNER}:${BRANCH}\" --title \"Release v${NEW_VERSION}\"" + fi + else + log_info "Pushing branch to fork..." + git push -u fork "${BRANCH}" || { + log_error "Git push to fork failed." + exit 1 + } + log_info "Branch ${BRANCH} pushed to fork" + + if [[ "${SKIP_PR}" == "false" ]]; then + log_info "Creating pull request..." + gh pr create \ + --repo "${UPSTREAM_REPO}" \ + --base main \ + --head "${FORK_OWNER}:${BRANCH}" \ + --title "Release v${NEW_VERSION}" \ + --body "$(cat </dev/null; then + # macOS with coreutils installed (brew install coreutils) + REALPATH_CMD="grealpath" +elif command -v realpath &>/dev/null; then + # Linux or macOS with realpath available + REALPATH_CMD="realpath" +else + # realpath not found - check if we're in dry-run mode + if [[ "${DRY_RUN}" == "true" ]]; then + log_warn "realpath command not found - skipping WORK_DIR validation in dry-run mode" + log_warn "On macOS: brew install coreutils (for actual operations)" + log_warn "On Linux: install coreutils package (for actual operations)" + REALPATH_CMD="" + else + log_error "realpath command not found - cannot safely validate WORK_DIR" + log_error "On macOS: brew install coreutils" + log_error "On Linux: install coreutils package" + log_error "This is required to prevent symlink-based path traversal attacks" + log_error "" + log_error "Without realpath, the script cannot guarantee that WORK_DIR is safely under temp/" + log_error "and subsequent 'rm -rf' operations could accidentally delete files outside temp/" + exit 1 + fi +fi + +# Determine the system temp directory +# Use TMPDIR if set (macOS often sets this), otherwise /tmp +SYSTEM_TMPDIR="${TMPDIR:-/tmp}" +SYSTEM_TMPDIR="${SYSTEM_TMPDIR%/}" # Strip trailing slash + +# Path validation (skipped in dry-run mode when realpath is not available) +if [[ -n "${REALPATH_CMD}" ]]; then + # Canonicalize the temp directory itself to handle /tmp -> /private/tmp on macOS + # The temp directory should exist, so we can use realpath without -m + if [[ ! -d "${SYSTEM_TMPDIR}" ]]; then + log_error "System temp directory does not exist: ${SYSTEM_TMPDIR}" + log_error "This is unexpected and may indicate a system configuration issue" + exit 1 + fi + + TMPDIR_CANONICAL=$($REALPATH_CMD "${SYSTEM_TMPDIR}" 2>/dev/null) + if [[ -z "${TMPDIR_CANONICAL}" ]]; then + log_error "Failed to canonicalize system temp directory: ${SYSTEM_TMPDIR}" + log_error "This may indicate a system configuration issue" + exit 1 + fi + + # Also canonicalize /tmp itself (may differ from SYSTEM_TMPDIR on macOS) + # This handles the case where TMPDIR is set but WORK_DIR defaults to /tmp/community-operators-* + TMP_CANONICAL="" + if [[ -d "/tmp" ]]; then + TMP_CANONICAL=$($REALPATH_CMD "/tmp" 2>/dev/null) + fi +else + # Dry-run mode without realpath - use literal paths without validation + TMPDIR_CANONICAL="${SYSTEM_TMPDIR}" + TMP_CANONICAL="/tmp" +fi + +# Early validation before attempting any directory creation +# Reject paths with .. (path traversal) or that don't start with / (relative paths) +if [[ "${WORK_DIR}" == *".."* ]]; then + log_error "WORK_DIR cannot contain '..' (path traversal): ${WORK_DIR}" + log_error "This is a security restriction to prevent directory creation outside temp" + exit 1 +fi + +if [[ "${WORK_DIR}" != /* ]]; then + log_error "WORK_DIR must be an absolute path starting with '/': ${WORK_DIR}" + log_error "Relative paths are not allowed for safety" + exit 1 +fi + +# Basic prefix check before attempting mkdir (prevents creating dirs outside temp) +# This is a preliminary check; the canonical path check below is the authoritative validation +# Check against both literal paths and their canonical forms to allow /private/tmp on macOS +PASSES_PREFIX_CHECK=false + +if [[ "${WORK_DIR}/" == "${SYSTEM_TMPDIR}/"* ]] || \ + [[ "${WORK_DIR}/" == "/tmp/"* ]] || \ + [[ "${WORK_DIR}/" == "${TMPDIR_CANONICAL}/"* ]] || \ + [[ -n "${TMP_CANONICAL}" && "${WORK_DIR}/" == "${TMP_CANONICAL}/"* ]]; then + PASSES_PREFIX_CHECK=true +fi + +if [[ "${PASSES_PREFIX_CHECK}" == "false" ]]; then + log_error "WORK_DIR must start with system temp directory" + log_error " Computed: ${WORK_DIR}" + log_error " Allowed prefixes:" + log_error " - ${SYSTEM_TMPDIR}/" + if [[ "${TMPDIR_CANONICAL}" != "${SYSTEM_TMPDIR}" ]]; then + log_error " - ${TMPDIR_CANONICAL}/ (canonical)" + fi + log_error " - /tmp/" + if [[ -n "${TMP_CANONICAL}" && "${TMP_CANONICAL}" != "/tmp" ]]; then + log_error " - ${TMP_CANONICAL}/ (canonical)" + fi + log_error "" + log_error "This check prevents creating directories outside temp before canonicalization" + exit 1 +fi + +# Canonicalize WORK_DIR to resolve symlinks and path traversal +# Skip validation in dry-run mode when realpath is not available +if [[ -n "${REALPATH_CMD}" ]]; then + # Try to use realpath -m to canonicalize even if path doesn't exist + WORK_DIR_CANONICAL="" + if WORK_DIR_CANONICAL=$($REALPATH_CMD -m "${WORK_DIR}" 2>/dev/null); then + # GNU realpath with -m support + : + else + # BSD realpath or -m not supported - create the directory temporarily + # Safe to do now because we've validated the path doesn't contain .. and has correct prefix + WORK_DIR_CREATED=false + if [[ ! -e "${WORK_DIR}" ]]; then + mkdir -p "${WORK_DIR}" || { + log_error "Failed to create temporary directory for validation: ${WORK_DIR}" + exit 1 + } + WORK_DIR_CREATED=true + fi + + # Now canonicalize the existing path + WORK_DIR_CANONICAL=$($REALPATH_CMD "${WORK_DIR}" 2>/dev/null) + + # Remove the directory if we created it temporarily + if [[ "${WORK_DIR_CREATED}" == "true" ]]; then + rmdir "${WORK_DIR}" 2>/dev/null || true + fi + fi + + if [[ -z "${WORK_DIR_CANONICAL}" ]]; then + log_error "Failed to canonicalize path: ${WORK_DIR}" + log_error "This may indicate an invalid path or system issue" + exit 1 + fi + + # CRITICAL SAFETY CHECK: Reject WORK_DIR if it equals a temp root directory + # This prevents "rm -rf /tmp" or "rm -rf $TMPDIR" which would wipe the entire temp + # WORK_DIR must be a subdirectory under temp, not the temp directory itself + if [[ "${WORK_DIR_CANONICAL}" == "${TMPDIR_CANONICAL}" ]] || \ + [[ -n "${TMP_CANONICAL}" && "${WORK_DIR_CANONICAL}" == "${TMP_CANONICAL}" ]] || \ + [[ "${WORK_DIR_CANONICAL}" == "/tmp" ]]; then + log_error "WORK_DIR cannot be the temp root directory itself" + log_error " Computed: ${WORK_DIR}" + log_error " Resolved to: ${WORK_DIR_CANONICAL}" + log_error "" + log_error "This would cause 'rm -rf ${WORK_DIR_CANONICAL}' which would wipe the entire temp directory!" + log_error "" + log_error "WORK_DIR must be a subdirectory under temp, for example:" + log_error " ${TMPDIR_CANONICAL}/community-operators-${VERSION}" + log_error "" + log_error "This error suggests an invalid TMPDIR environment variable." + log_error "Expected: ${TMPDIR:-/tmp}/community-operators-${VERSION}" + exit 1 + fi + + # Ensure the canonical WORK_DIR is under an allowed temp directory + # Accept either TMPDIR_CANONICAL (system temp) or TMP_CANONICAL (/tmp canonical) + # This handles both Linux (/tmp) and macOS where /tmp→/private/tmp and TMPDIR→/var/folders/ + # Use a slash suffix to ensure we match directory prefix, not just string prefix + UNDER_TMPDIR=false + UNDER_TMP=false + + if [[ "${WORK_DIR_CANONICAL}/" == "${TMPDIR_CANONICAL}/"* ]]; then + UNDER_TMPDIR=true + fi + + if [[ -n "${TMP_CANONICAL}" ]] && [[ "${WORK_DIR_CANONICAL}/" == "${TMP_CANONICAL}/"* ]]; then + UNDER_TMP=true + fi + + if [[ "${UNDER_TMPDIR}" == "false" ]] && [[ "${UNDER_TMP}" == "false" ]]; then + log_error "WORK_DIR must be under system temp directory for safety" + log_error " Computed: ${WORK_DIR}" + log_error " Resolved: ${WORK_DIR_CANONICAL}" + log_error " System temp: ${SYSTEM_TMPDIR} → ${TMPDIR_CANONICAL}" + if [[ -n "${TMP_CANONICAL}" ]] && [[ "${TMP_CANONICAL}" != "${TMPDIR_CANONICAL}" ]]; then + log_error " /tmp resolved: ${TMP_CANONICAL}" + fi + log_error "" + log_error "Path traversal (../) or symlinks pointing outside temp are not allowed" + log_error "This prevents accidental deletion of files outside the temp directory" + exit 1 + fi + + # Use canonical path for all operations + WORK_DIR="${WORK_DIR_CANONICAL}" +else + # Dry-run mode without realpath - skip validation, use literal path + log_warn "Skipping WORK_DIR path validation (dry-run mode without realpath)" +fi + +# Check GitHub CLI availability early (before cloning/copying) if we're going to create a PR +# Skip these checks in dry-run mode since gh won't actually be invoked +if [[ "${SKIP_PUSH}" == "false" && "${SKIP_PR}" == "false" && "${DRY_RUN}" == "false" ]]; then + # Check if gh command is available + if ! command -v gh &>/dev/null; then + log_error "GitHub CLI (gh) is not installed - cannot create pull request" + log_error "" + log_error "Install instructions:" + log_error " macOS: brew install gh" + log_error " Linux: https://github.com/cli/cli#installation" + log_error " Windows: https://github.com/cli/cli#installation" + log_error "" + log_error "After installation, authenticate with: gh auth login" + log_error "" + log_error "Alternatively, use --skip-pr to skip PR creation and create it manually" + exit 1 + fi + + # Check if gh is authenticated + if ! gh auth status &>/dev/null; then + log_error "GitHub CLI is not authenticated - cannot create pull request" + log_error "" + log_error "Run: gh auth login" + log_error "" + log_error "This will authenticate gh with your GitHub account and allow PR creation" + log_error "" + log_error "Alternatively, use --skip-pr to skip PR creation and create it manually" + exit 1 + fi +fi + +# Set catalog path based on type +if [[ "${CATALOG}" == "community" ]]; then + CATALOG_PATH="operators" +else + CATALOG_PATH="operators" # Same for now, but can be different for upstream +fi + +TARGET_RELEASE="${VERSION}" +DEST_DIR="${WORK_DIR}/${CATALOG_PATH}/trustee-operator/${TARGET_RELEASE}" + +if [[ "${DRY_RUN}" == "true" ]]; then + log_warn "DRY RUN MODE - No changes will be made" + echo "" +fi + +log_info "================================================================" +log_info "Community Operators Bundle Preparation" +log_info "================================================================" +log_info "Version: ${VERSION}" +log_info "Upstream: ${UPSTREAM_REPO}" +log_info "Fork: ${FORK_REPO}" +log_info "Branch: ${BRANCH}" +log_info "Catalog: ${CATALOG}" +log_info "Work Directory: ${WORK_DIR}" +log_info "Target Path: ${CATALOG_PATH}/trustee-operator/${TARGET_RELEASE}" +log_info "================================================================" +echo "" + +# Check if bundle directory exists +if [[ ! -d "${ROOT_DIR}/bundle" ]]; then + log_error "Bundle directory not found: ${ROOT_DIR}/bundle" + log_error "Please run 'make bundle' first" + exit 1 +fi + +# Check if bundle has files +if [[ -z "$(find "${ROOT_DIR}/bundle/manifests" -mindepth 1 -print -quit 2>/dev/null)" ]]; then + log_error "Bundle manifests directory is empty: ${ROOT_DIR}/bundle/manifests" + log_error "Please run 'make bundle' first" + exit 1 +fi + +# Step 1: Clone repository +log_step "Step 1: Cloning community-operators repository" + +# Remove existing work directory to ensure fresh clone +if [[ -d "${WORK_DIR}" ]]; then + if [[ "${DRY_RUN}" == "false" ]]; then + log_info "Removing existing work directory: ${WORK_DIR}" + rm -rf "${WORK_DIR}" + else + log_info "[DRY RUN] Would remove: ${WORK_DIR}" + fi +fi + +if [[ "${DRY_RUN}" == "false" ]]; then + log_info "Cloning upstream repository (shallow clone): ${UPSTREAM_REPO}..." + # Use shallow clone (--depth 1 --single-branch) to speed up cloning + # We only need current state to add files and create PR, not full history + git clone --depth 1 --single-branch "${UPSTREAM_REPO}" "${WORK_DIR}" || { + log_error "Failed to clone repository" + exit 1 + } + + cd "${WORK_DIR}" + + # Add fork as remote + if git remote get-url fork &>/dev/null; then + log_info "Fork remote already exists, updating URL..." + git remote set-url fork "${FORK_REPO}" + else + log_info "Adding fork as remote: ${FORK_REPO}" + git remote add fork "${FORK_REPO}" + fi + + # Create new branch from main/master + log_info "Creating branch: ${BRANCH}" + if git show-ref --verify --quiet "refs/heads/${BRANCH}"; then + log_warn "Branch already exists, checking it out" + git checkout "${BRANCH}" + else + git checkout -b "${BRANCH}" + fi +else + log_info "[DRY RUN] Would clone (shallow): ${UPSTREAM_REPO} to ${WORK_DIR}" + log_info "[DRY RUN] (using --depth 1 --single-branch for faster cloning)" + log_info "[DRY RUN] Would add fork remote: ${FORK_REPO}" + log_info "[DRY RUN] Would create branch: ${BRANCH}" +fi +echo "" + +# Step 2: Copy bundle +log_step "Step 2: Copying bundle to ${CATALOG_PATH}/trustee-operator/${TARGET_RELEASE}" + +if [[ "${DRY_RUN}" == "false" ]]; then + # Remove existing directory if it exists + if [[ -d "${DEST_DIR}" ]]; then + log_info "Removing existing directory: ${DEST_DIR}" + rm -rf "${DEST_DIR}" + fi + + # Create target directory + log_info "Creating directory: ${DEST_DIR}" + mkdir -p "${DEST_DIR}" + + # Copy bundle contents + log_info "Copying bundle files..." + cp -r "${ROOT_DIR}/bundle/manifests" "${DEST_DIR}/" + cp -r "${ROOT_DIR}/bundle/metadata" "${DEST_DIR}/" + + # Copy tests directory if it exists (scorecard tests) + if [[ -d "${ROOT_DIR}/bundle/tests" ]]; then + cp -r "${ROOT_DIR}/bundle/tests" "${DEST_DIR}/" + log_info "Copied tests directory" + else + log_warn "No tests directory found in bundle/" + fi + + # Note: bundle.Dockerfile is typically NOT included in community-operators submissions + # as it's auto-generated by the community-operators CI/CD pipeline. + # Uncomment the following lines if your submission requires it: + # + # if [[ -f "${ROOT_DIR}/bundle.Dockerfile" ]]; then + # cp "${ROOT_DIR}/bundle.Dockerfile" "${DEST_DIR}/" + # log_info "Copied bundle.Dockerfile" + # fi + + log_info "Bundle copied successfully" + + # Show what was copied + log_info "Copied files:" + # Use parameter expansion to strip WORK_DIR prefix (safer than sed with regex metacharacters) + find "${DEST_DIR}" -type f | while read -r file; do echo "${file#${WORK_DIR}/}"; done | sort +else + log_info "[DRY RUN] Would remove: ${DEST_DIR}" + log_info "[DRY RUN] Would create: ${DEST_DIR}" + log_info "[DRY RUN] Would copy:" + log_info "[DRY RUN] - ${ROOT_DIR}/bundle/manifests → ${DEST_DIR}/manifests" + log_info "[DRY RUN] - ${ROOT_DIR}/bundle/metadata → ${DEST_DIR}/metadata" + if [[ -d "${ROOT_DIR}/bundle/tests" ]]; then + log_info "[DRY RUN] - ${ROOT_DIR}/bundle/tests → ${DEST_DIR}/tests" + fi + log_info "[DRY RUN] NOTE: bundle.Dockerfile is not copied (auto-generated by community-operators)" +fi + +echo "" + +# Step 3: Create git commit +HAS_CHANGES=true + +log_step "Step 3: Creating git commit" + +if [[ "${DRY_RUN}" == "false" ]]; then + cd "${WORK_DIR}" + + # Stage the changes first + log_info "Staging changes..." + git add "${CATALOG_PATH}/trustee-operator/${TARGET_RELEASE}" + + # Check if there are any staged changes + if git diff --cached --quiet; then + log_warn "No changes to commit" + log_warn "Bundle for version ${TARGET_RELEASE} already exists in upstream repository" + HAS_CHANGES=false + else + log_info "Creating commit..." + COMMIT_MSG="operator trustee-operator (${TARGET_RELEASE}) + +Add trustee-operator version ${TARGET_RELEASE}" + + git commit -m "${COMMIT_MSG}" || { + log_error "Failed to create commit" + exit 1 + } + + log_info "Commit created successfully" + git log -1 --oneline + fi +else + log_info "[DRY RUN] Would create commit with bundle changes" +fi +echo "" + +# Step 4: Push to fork and create PR +if [[ "${SKIP_PUSH}" == "false" && "${HAS_CHANGES}" == "true" ]]; then + if [[ "${SKIP_PR}" == "false" ]]; then + log_step "Step 4: Pushing to fork and creating PR" + else + log_step "Step 4: Pushing to fork" + fi + + # Extract and validate fork owner and upstream repo for both dry-run and actual execution + # This ensures dry-run output matches actual command + if [[ "${SKIP_PR}" == "false" ]]; then + # Extract fork owner from fork URL + FORK_OWNER=$(echo "${FORK_REPO}" | sed -E 's|.*github\.com[:/]([^/]+)/.*|\1|') + + # Get upstream repo (should be k8s-operatorhub/community-operators) + UPSTREAM_REPO_NORMALIZED=$(echo "${UPSTREAM_REPO}" | sed -E 's|.*github\.com[:/]([^/]+/[^/]+)(\.git)?$|\1|' | sed 's/\.git$//') + + # Validate FORK_OWNER extraction + # Should be a GitHub username/org (no slashes, no URL artifacts) + # If extraction failed, sed returns the original string, so check for that too + if [[ -z "${FORK_OWNER}" ]] || \ + [[ "${FORK_OWNER}" == "${FORK_REPO}" ]] || \ + [[ "${FORK_OWNER}" == *"/"* ]] || \ + [[ "${FORK_OWNER}" == *":"* ]] || \ + [[ "${FORK_OWNER}" == *"@"* ]] || \ + [[ "${FORK_OWNER}" == *"://"* ]] || \ + [[ "${FORK_OWNER}" == *".git"* ]] || \ + [[ "${FORK_OWNER}" == *"github.com"* ]] || \ + [[ "${FORK_OWNER}" == *"."* ]]; then + log_error "Failed to extract fork owner from: ${FORK_REPO}" + log_error "Expected a GitHub URL like: git@github.com:username/community-operators.git" + log_error "Extracted owner: '${FORK_OWNER}' (invalid)" + log_error "You can create the PR manually at GitHub" + exit 1 + fi + + # Validate UPSTREAM_REPO_NORMALIZED extraction + # Should be exactly "owner/repo" format (one slash, no URL artifacts) + if [[ -z "${UPSTREAM_REPO_NORMALIZED}" ]] || \ + [[ "${UPSTREAM_REPO_NORMALIZED}" != *"/"* ]] || \ + [[ "${UPSTREAM_REPO_NORMALIZED}" == *":"* ]] || \ + [[ "${UPSTREAM_REPO_NORMALIZED}" == *"@"* ]] || \ + [[ "${UPSTREAM_REPO_NORMALIZED}" == *"://"* ]] || \ + [[ "${UPSTREAM_REPO_NORMALIZED}" == *".git"* ]] || \ + [[ "${UPSTREAM_REPO_NORMALIZED}" == *"github.com"* ]] || \ + [[ $(echo "${UPSTREAM_REPO_NORMALIZED}" | grep -o "/" | wc -l) -ne 1 ]]; then + log_error "Failed to extract upstream repo from: ${UPSTREAM_REPO}" + log_error "Expected a GitHub URL like: git@github.com:k8s-operatorhub/community-operators.git" + log_error "Extracted repo: '${UPSTREAM_REPO_NORMALIZED}' (invalid)" + log_error "You can create the PR manually at GitHub" + exit 1 + fi + fi + + if [[ "${DRY_RUN}" == "true" ]]; then + log_info "[DRY RUN] Would run: cd ${WORK_DIR} && git push -u fork ${BRANCH}" + if [[ "${SKIP_PR}" == "false" ]]; then + log_info "[DRY RUN] Would run: gh pr create --repo \"${UPSTREAM_REPO_NORMALIZED}\" --base main --head \"${FORK_OWNER}:${BRANCH}\" --title \"operator trustee-operator (${TARGET_RELEASE})\" --body '...'" + log_info "[DRY RUN] (PR body includes operator information, testing details, and checklist)" + fi + else + cd "${WORK_DIR}" + + log_info "Pushing branch to fork..." + git push -u fork "${BRANCH}" || { + log_error "Git push to fork failed." + exit 1 + } + log_info "Branch ${BRANCH} pushed to fork" + + if [[ "${SKIP_PR}" == "false" ]]; then + log_info "Creating pull request..." + gh pr create \ + --repo "${UPSTREAM_REPO_NORMALIZED}" \ + --base main \ + --head "${FORK_OWNER}:${BRANCH}" \ + --title "operator trustee-operator (${TARGET_RELEASE})" \ + --body "$(cat <= min_tcb_date_ns + # Check collateral expiration status input.tdx.collateral_expiration_status == "0" # Check against allowed advisory ids @@ -231,20 +247,20 @@ data: ##### Azure vTPM SNP executables := 3 if { - input.az_snp_vtpm + input["az-snp-vtpm"] - input.az_snp_vtpm.measurement in query_reference_value("measurement") - input.az_snp_vtpm.tpm.pcr11 in query_reference_value("snp_pcr11") + input["az-snp-vtpm"].measurement in query_reference_value("measurement") + input["az-snp-vtpm"].tpm.pcr11 in query_reference_value("snp_pcr11") } hardware := 2 if { - input.az_snp_vtpm + input["az-snp-vtpm"] # Check the reported TCB to validate the ASP FW - input.az_snp_vtpm.reported_tcb_bootloader in query_reference_value("tcb_bootloader") - input.az_snp_vtpm.reported_tcb_microcode in query_reference_value("tcb_microcode") - input.az_snp_vtpm.reported_tcb_snp in query_reference_value("tcb_snp") - input.az_snp_vtpm.reported_tcb_tee in query_reference_value("tcb_tee") + input["az-snp-vtpm"].reported_tcb_bootloader in query_reference_value("tcb_bootloader") + input["az-snp-vtpm"].reported_tcb_microcode in query_reference_value("tcb_microcode") + input["az-snp-vtpm"].reported_tcb_snp in query_reference_value("tcb_snp") + input["az-snp-vtpm"].reported_tcb_tee in query_reference_value("tcb_tee") } # For the 'configuration' trust claim 2 stands for @@ -252,40 +268,46 @@ data: # # For this, we compare all the configuration fields. configuration := 2 if { - input.az_snp_vtpm - - input.az_snp_vtpm.platform_smt_enabled in query_reference_value("smt_enabled") - input.az_snp_vtpm.platform_tsme_enabled in query_reference_value("tsme_enabled") - input.az_snp_vtpm.policy_abi_major in query_reference_value("abi_major") - input.az_snp_vtpm.policy_abi_minor in query_reference_value("abi_minor") - input.az_snp_vtpm.policy_single_socket in query_reference_value("single_socket") - input.az_snp_vtpm.policy_smt_allowed in query_reference_value("smt_allowed") + input["az-snp-vtpm"] + + input["az-snp-vtpm"].platform_smt_enabled in query_reference_value("smt_enabled") + input["az-snp-vtpm"].platform_tsme_enabled in query_reference_value("tsme_enabled") + input["az-snp-vtpm"].policy_abi_major in query_reference_value("abi_major") + input["az-snp-vtpm"].policy_abi_minor in query_reference_value("abi_minor") + input["az-snp-vtpm"].policy_single_socket in query_reference_value("single_socket") + input["az-snp-vtpm"].policy_smt_allowed in query_reference_value("smt_allowed") } ##### Azure vTPM TDX executables := 3 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] - input.az_tdx_vtpm.tpm.pcr11 in query_reference_value("tdx_pcr11") + input["az-tdx-vtpm"].tpm.pcr11 in query_reference_value("tdx_pcr11") } hardware := 2 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] # Check the quote is a TDX quote signed by Intel SGX Quoting Enclave - input.az_tdx_vtpm.quote.header.tee_type == "81000000" - input.az_tdx_vtpm.quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" + input["az-tdx-vtpm"].quote.header.tee_type == "81000000" + input["az-tdx-vtpm"].quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" - # Check TDX Module version and its hash. Also check OVMF code hash. - input.az_tdx_vtpm.quote.body.mr_seam in query_reference_value("mr_seam") - input.az_tdx_vtpm.quote.body.tcb_svn in query_reference_value("tcb_svn") - input.az_tdx_vtpm.quote.body.mr_td in query_reference_value("mr_td") + # Check TDX Module hash + # input.tdx.quote.body.mr_seam in query_reference_value("mr_seam") + # + # Check OVMF code hash + input["az-tdx-vtpm"].quote.body.mr_td in query_reference_value("mr_td") + + # Check TCB status (covers quote.body.tcb_svn claim check) + input["az-tdx-vtpm"].tcb_status == "UpToDate" + + # Check minimum TCB date (See TDX section for details.) } configuration := 2 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] - input.az_tdx_vtpm.quote.body.xfam in query_reference_value("xfam") + input["az-tdx-vtpm"].quote.body.xfam in query_reference_value("xfam") } ##### TPM @@ -303,8 +325,23 @@ data: input.tpm } - ##### SE TODO + ##### IBM Secure Execution for Linux (SEL) + # Only field existence is checked. No value check is necessary. + # The SE verifier performs cryptographic verification including + # measurements, signatures, and user_data binding. + # If the field exists, it means the verifaction is successful. + # This is a 'trust-the-verifier' approach. + executables := 3 if { + input.se + } + + hardware := 2 if { + input.se + } + configuration := 2 if { + input.se + } ################################# # EXTENSIONS @@ -355,4 +392,4 @@ data: container_uids_id := {"container_uids": container_uids} if { count(container_uids) > 0 - } else := {} + } else := {} \ No newline at end of file diff --git a/tests/e2e/sample-attester/06-ear-default-policy-cpu.yaml b/tests/e2e/sample-attester/06-ear-default-policy-cpu.yaml index ae05218b..ddc2bc46 100644 --- a/tests/e2e/sample-attester/06-ear-default-policy-cpu.yaml +++ b/tests/e2e/sample-attester/06-ear-default-policy-cpu.yaml @@ -181,14 +181,30 @@ data: input.tdx.quote.header.tee_type == "81000000" input.tdx.quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" - # Check TDX Module version and its hash. Also check OVMF code hash. - input.tdx.quote.body.mr_seam in query_reference_value("mr_seam") - input.tdx.quote.body.tcb_svn in query_reference_value("tcb_svn") + # Check TDX Module hash + # input.tdx.quote.body.mr_seam in query_reference_value("mr_seam") + # + # Check OVMF code hash input.tdx.quote.body.mr_td in query_reference_value("mr_td") - # Check TCB status + # Check TCB status (covers quote.body.tcb_svn claim check) input.tdx.tcb_status == "UpToDate" + # Check minimum TCB date + # An alternative check to tcb_status is to define a minimum acceptable + # TCB date. TCB dates are associated with TCB Recovery events to which + # the platforms are certified. + # + # Available TCB dates can be checked using: + # curl -s https://api.trustedservices.intel.com/tdx/certification/v4/tcbevaluationdatanumbers | jq + # + # Example: in some cases, "OutOfDate" tcb_status can be accepted as long as + # the tcb_date is not older than a given date from a past TCB Recovery event: + # min_tcb_date := "2025-08-13T00:00:00Z" + # attester_tcb_date_ns := time.parse_rfc3339_ns(input.tdx.tcb_date) + # min_tcb_date_ns := time.parse_rfc3339_ns(min_tcb_date) + # attester_tcb_date_ns >= min_tcb_date_ns + # Check collateral expiration status input.tdx.collateral_expiration_status == "0" # Check against allowed advisory ids @@ -231,20 +247,20 @@ data: ##### Azure vTPM SNP executables := 3 if { - input.az_snp_vtpm + input["az-snp-vtpm"] - input.az_snp_vtpm.measurement in query_reference_value("measurement") - input.az_snp_vtpm.tpm.pcr11 in query_reference_value("snp_pcr11") + input["az-snp-vtpm"].measurement in query_reference_value("measurement") + input["az-snp-vtpm"].tpm.pcr11 in query_reference_value("snp_pcr11") } hardware := 2 if { - input.az_snp_vtpm + input["az-snp-vtpm"] # Check the reported TCB to validate the ASP FW - input.az_snp_vtpm.reported_tcb_bootloader in query_reference_value("tcb_bootloader") - input.az_snp_vtpm.reported_tcb_microcode in query_reference_value("tcb_microcode") - input.az_snp_vtpm.reported_tcb_snp in query_reference_value("tcb_snp") - input.az_snp_vtpm.reported_tcb_tee in query_reference_value("tcb_tee") + input["az-snp-vtpm"].reported_tcb_bootloader in query_reference_value("tcb_bootloader") + input["az-snp-vtpm"].reported_tcb_microcode in query_reference_value("tcb_microcode") + input["az-snp-vtpm"].reported_tcb_snp in query_reference_value("tcb_snp") + input["az-snp-vtpm"].reported_tcb_tee in query_reference_value("tcb_tee") } # For the 'configuration' trust claim 2 stands for @@ -252,40 +268,46 @@ data: # # For this, we compare all the configuration fields. configuration := 2 if { - input.az_snp_vtpm - - input.az_snp_vtpm.platform_smt_enabled in query_reference_value("smt_enabled") - input.az_snp_vtpm.platform_tsme_enabled in query_reference_value("tsme_enabled") - input.az_snp_vtpm.policy_abi_major in query_reference_value("abi_major") - input.az_snp_vtpm.policy_abi_minor in query_reference_value("abi_minor") - input.az_snp_vtpm.policy_single_socket in query_reference_value("single_socket") - input.az_snp_vtpm.policy_smt_allowed in query_reference_value("smt_allowed") + input["az-snp-vtpm"] + + input["az-snp-vtpm"].platform_smt_enabled in query_reference_value("smt_enabled") + input["az-snp-vtpm"].platform_tsme_enabled in query_reference_value("tsme_enabled") + input["az-snp-vtpm"].policy_abi_major in query_reference_value("abi_major") + input["az-snp-vtpm"].policy_abi_minor in query_reference_value("abi_minor") + input["az-snp-vtpm"].policy_single_socket in query_reference_value("single_socket") + input["az-snp-vtpm"].policy_smt_allowed in query_reference_value("smt_allowed") } ##### Azure vTPM TDX executables := 3 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] - input.az_tdx_vtpm.tpm.pcr11 in query_reference_value("tdx_pcr11") + input["az-tdx-vtpm"].tpm.pcr11 in query_reference_value("tdx_pcr11") } hardware := 2 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] # Check the quote is a TDX quote signed by Intel SGX Quoting Enclave - input.az_tdx_vtpm.quote.header.tee_type == "81000000" - input.az_tdx_vtpm.quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" + input["az-tdx-vtpm"].quote.header.tee_type == "81000000" + input["az-tdx-vtpm"].quote.header.vendor_id == "939a7233f79c4ca9940a0db3957f0607" - # Check TDX Module version and its hash. Also check OVMF code hash. - input.az_tdx_vtpm.quote.body.mr_seam in query_reference_value("mr_seam") - input.az_tdx_vtpm.quote.body.tcb_svn in query_reference_value("tcb_svn") - input.az_tdx_vtpm.quote.body.mr_td in query_reference_value("mr_td") + # Check TDX Module hash + # input.tdx.quote.body.mr_seam in query_reference_value("mr_seam") + # + # Check OVMF code hash + input["az-tdx-vtpm"].quote.body.mr_td in query_reference_value("mr_td") + + # Check TCB status (covers quote.body.tcb_svn claim check) + input["az-tdx-vtpm"].tcb_status == "UpToDate" + + # Check minimum TCB date (See TDX section for details.) } configuration := 2 if { - input.az_tdx_vtpm + input["az-tdx-vtpm"] - input.az_tdx_vtpm.quote.body.xfam in query_reference_value("xfam") + input["az-tdx-vtpm"].quote.body.xfam in query_reference_value("xfam") } ##### TPM @@ -303,8 +325,23 @@ data: input.tpm } - ##### SE TODO + ##### IBM Secure Execution for Linux (SEL) + # Only field existence is checked. No value check is necessary. + # The SE verifier performs cryptographic verification including + # measurements, signatures, and user_data binding. + # If the field exists, it means the verifaction is successful. + # This is a 'trust-the-verifier' approach. + executables := 3 if { + input.se + } + + hardware := 2 if { + input.se + } + configuration := 2 if { + input.se + } ################################# # EXTENSIONS