diff --git a/.github/workflows/coherence-matrix.yaml b/.github/workflows/coherence-matrix.yaml index e3409f3b9..32bf9ec8f 100644 --- a/.github/workflows/coherence-matrix.yaml +++ b/.github/workflows/coherence-matrix.yaml @@ -34,33 +34,33 @@ jobs: fail-fast: false matrix: matrixName: -# - "15.1.1-0-SNAPSHOT" -# - "15.1.1-0-SNAPSHOT-Graal" + - "15.1.1-0-SNAPSHOT" + - "15.1.1-0-SNAPSHOT-Graal" - "25.03" - "25.03-Graal" - "14.1.2-0" -# - "14.1.2-0-SNAPSHOT" + - "14.1.2-0-SNAPSHOT" - "22.06" -# - "14.1.1-2206-SNAPSHOT" -# - "14.1.1-0-SNAPSHOT" + - "14.1.1-2206-SNAPSHOT" + - "14.1.1-0-SNAPSHOT" - "14.1.1-0" - "14.1.1.0.0" - "12.2.1.4.0" -# - "12.2.1-4-SNAPSHOT" + - "12.2.1-4-SNAPSHOT" include: -# - matrixName: "15.1.1-0-SNAPSHOT" -# coherenceVersion: "15.1.1-0-0-SNAPSHOT" -# coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:15.1.1-0-0-SNAPSHOT-java17" -# javaVersion: 17 -# coherenceIsJava8: false -# baseImage: "gcr.io/distroless/java17-debian12" + - matrixName: "15.1.1-0-SNAPSHOT" + coherenceVersion: "15.1.1-0-3-SNAPSHOT" + coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:15.1.1-0-3-SNAPSHOT-java17" + javaVersion: 17 + coherenceIsJava8: false + baseImage: "gcr.io/distroless/java17-debian12" -# - matrixName: "15.1.1-0-SNAPSHOT-Graal" -# coherenceVersion: "15.1.1-0-0-SNAPSHOT" -# coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:15.1.1-0-0-SNAPSHOT-graal" -# javaVersion: 17 -# coherenceIsJava8: false -# baseImage: "gcr.io/distroless/java17-debian12" + - matrixName: "15.1.1-0-SNAPSHOT-Graal" + coherenceVersion: "15.1.1-0-3-SNAPSHOT" + coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:15.1.1-0-3-SNAPSHOT-graal" + javaVersion: 17 + coherenceIsJava8: false + baseImage: "gcr.io/distroless/java17-debian12" - matrixName: "25.03" coherenceVersion: "25.03.2" @@ -76,12 +76,12 @@ jobs: coherenceIsJava8: false baseImage: "gcr.io/distroless/java17-debian12" -# - matrixName: "14.1.2-0-SNAPSHOT" -# coherenceVersion: "14.1.2-0-3-SNAPSHOT" -# coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:14.1.2-0-3-SNAPSHOT" -# javaVersion: 17 -# coherenceIsJava8: false -# baseImage: "gcr.io/distroless/java17-debian12" + - matrixName: "14.1.2-0-SNAPSHOT" + coherenceVersion: "14.1.2-0-7-SNAPSHOT" + coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:14.1.2-0-7-SNAPSHOT" + javaVersion: 17 + coherenceIsJava8: false + baseImage: "gcr.io/distroless/java17-debian12" - matrixName: "14.1.2-0" coherenceVersion: "14.1.2-0-2" @@ -97,19 +97,19 @@ jobs: coherenceIsJava8: false baseImage: "gcr.io/distroless/java11-debian11" -# - matrixName: "14.1.1-2206-SNAPSHOT" -# coherenceVersion: "14.1.1-2206-13-SNAPSHOT" -# coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:14.1.1-2206-13-SNAPSHOT" -# javaVersion: 11 -# coherenceIsJava8: false -# baseImage: "gcr.io/distroless/java11-debian11" -# -# - matrixName: "14.1.1-0-SNAPSHOT" -# coherenceVersion: "14.1.1-0-21-SNAPSHOT" -# coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:14.1.1-0-21-SNAPSHOT" -# javaVersion: 11 -# coherenceIsJava8: false -# baseImage: "gcr.io/distroless/java11-debian11" + - matrixName: "14.1.1-2206-SNAPSHOT" + coherenceVersion: "14.1.1-2206-17-SNAPSHOT" + coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:14.1.1-2206-17-SNAPSHOT" + javaVersion: 11 + coherenceIsJava8: false + baseImage: "gcr.io/distroless/java11-debian11" + + - matrixName: "14.1.1-0-SNAPSHOT" + coherenceVersion: "14.1.1-0-26-SNAPSHOT" + coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:14.1.1-0-26-SNAPSHOT" + javaVersion: 11 + coherenceIsJava8: false + baseImage: "gcr.io/distroless/java11-debian11" - matrixName: "14.1.1-0" coherenceVersion: "14.1.1-0-22" @@ -132,12 +132,12 @@ jobs: coherenceIsJava8: true baseImage: "gcr.io/distroless/java11-debian11" -# - matrixName: "12.2.1-4-SNAPSHOT" -# coherenceVersion: "12.2.1-4-26-SNAPSHOT" -# coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:12.2.1-4-26-SNAPSHOT" -# javaVersion: 8 -# coherenceIsJava8: true -# baseImage: "gcr.io/distroless/java11-debian11" + - matrixName: "12.2.1-4-SNAPSHOT" + coherenceVersion: "12.2.1-4-30-SNAPSHOT" + coherenceImage: "iad.ocir.io/odx-stateservice/test/coherence:12.2.1-4-30-SNAPSHOT" + javaVersion: 8 + coherenceIsJava8: true + baseImage: "gcr.io/distroless/java11-debian11" steps: - uses: actions/checkout@v4 @@ -221,7 +221,7 @@ jobs: else echo "Skipping OCR login because this matrix image is not hosted by container-registry.oracle.com" fi - # docker login "${{ secrets.OCI_REGISTRY }}" -u "${{ secrets.OCI_USERNAME }}" -p "${{ secrets.OCI_PASSWORD }}" + docker login "${{ secrets.OCI_REGISTRY }}" -u "${{ secrets.OCI_USERNAME }}" -p "${{ secrets.OCI_PASSWORD }}" docker pull gcr.io/distroless/java docker pull gcr.io/distroless/java11-debian11 docker pull gcr.io/distroless/java17-debian11 @@ -257,6 +257,35 @@ jobs: export TEST_LOGS_DIR=build/_output/test-logs/${{ matrix.matrixName }} make coherence-compatibility-test + - name: Collect failure diagnostics + if: ${{ failure() || cancelled() }} + shell: bash + continue-on-error: true + run: | + DIAG_DIR="build/_output/test-logs/${{ matrix.matrixName }}/diagnostics" + mkdir -p "${DIAG_DIR}" + + collect() { + local name="$1" + shift + echo "Collecting ${name}" + { + echo "$ $*" + "$@" + } > "${DIAG_DIR}/${name}.log" 2>&1 || true + } + + collect nodes kubectl get nodes -o wide + collect pods-all-namespaces kubectl get pods -A -o wide + collect events-all-namespaces kubectl get events -A --sort-by=.lastTimestamp + collect operator-test-all kubectl -n operator-test get all + collect operator-pods kubectl -n operator-test get pod -l control-plane=coherence -o wide + collect operator-pod-describe kubectl -n operator-test describe pod -l control-plane=coherence + collect operator-logs kubectl -n operator-test logs -l control-plane=coherence --all-containers --prefix --tail=-1 + collect operator-logs-previous kubectl -n operator-test logs -l control-plane=coherence --all-containers --previous --prefix --tail=-1 + collect coherence-crds kubectl get crd coherence.coherence.oracle.com coherencejob.coherence.oracle.com + collect coherence-resources kubectl get coherence.coherence.oracle.com,coherencejob.coherence.oracle.com -A + - uses: actions/upload-artifact@v4 if: ${{ failure() || cancelled() }} with: diff --git a/.github/workflows/prometheus-tests.yaml b/.github/workflows/prometheus-tests.yaml index 5d635a9b6..35958de8c 100644 --- a/.github/workflows/prometheus-tests.yaml +++ b/.github/workflows/prometheus-tests.yaml @@ -115,6 +115,40 @@ jobs: df -h make e2e-prometheus-test + - name: Collect failure diagnostics + if: ${{ failure() || cancelled() }} + shell: bash + continue-on-error: true + run: | + DIAG_DIR="build/_output/test-logs/diagnostics" + mkdir -p "${DIAG_DIR}" + + collect() { + local name="$1" + shift + echo "Collecting ${name}" + { + echo "$ $*" + "$@" + } > "${DIAG_DIR}/${name}.log" 2>&1 || true + } + + collect nodes kubectl get nodes -o wide + collect pods-all-namespaces kubectl get pods -A -o wide + collect events-all-namespaces kubectl get events -A --sort-by=.lastTimestamp + collect operator-test-all kubectl -n operator-test get all + collect operator-pods kubectl -n operator-test get pod -l control-plane=coherence -o wide + collect operator-deployment kubectl -n operator-test get deployment coherence-operator-controller-manager -o yaml + collect operator-pod-describe kubectl -n operator-test describe pod -l control-plane=coherence + collect operator-logs kubectl -n operator-test logs -l control-plane=coherence --all-containers --prefix --tail=-1 + collect operator-logs-previous kubectl -n operator-test logs -l control-plane=coherence --all-containers --previous --prefix --tail=-1 + collect monitoring-all kubectl -n monitoring get all + collect monitoring-pods kubectl -n monitoring get pod -o wide + collect monitoring-pod-describe kubectl -n monitoring describe pod + collect prometheus-logs kubectl -n monitoring logs -l app.kubernetes.io/name=prometheus --all-containers --prefix --tail=500 + collect coherence-crds kubectl get crd coherence.coherence.oracle.com coherencejob.coherence.oracle.com + collect coherence-resources kubectl get coherence.coherence.oracle.com,coherencejob.coherence.oracle.com -A + - uses: actions/upload-artifact@v4 if: ${{ failure() || cancelled() }} with: diff --git a/.go-version b/.go-version index c7c3f3333..f8f738140 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.26.2 +1.26.3 diff --git a/api/v1/coherence_types.go b/api/v1/coherence_types.go index a6fcf0d79..b1977eb38 100644 --- a/api/v1/coherence_types.go +++ b/api/v1/coherence_types.go @@ -1062,16 +1062,22 @@ type SSLSpec struct { // This value MUST be provided if SSL is enabled on the Coherence management over REST endpoint. // +optional Secrets *string `json:"secrets,omitempty"` - // Keystore is the name of the Java key store file in the k8s secret to use as the SSL keystore - // when configuring component over REST to use SSL. + // Keystore is the Java key store value used as the SSL keystore when configuring component + // over REST to use SSL. When Secrets is set this is a file name in that k8s secret; + // without Secrets it is an explicit path in the container, which lets users provide + // externally mounted stores without rewriting them as secret keys. // +optional KeyStore *string `json:"keyStore,omitempty"` - // KeyStorePasswordFile is the name of the file in the k8s secret containing the keystore - // password when configuring component over REST to use SSL. + // KeyStorePasswordFile is the file containing the keystore password when configuring + // component over REST to use SSL. When Secrets is set this is a file name in that k8s + // secret; without Secrets it is an explicit path in the container read by the + // operator's FileBasedPasswordProvider. // +optional KeyStorePasswordFile *string `json:"keyStorePasswordFile,omitempty"` - // KeyStorePasswordFile is the name of the file in the k8s secret containing the key - // password when configuring component over REST to use SSL. + // KeyPasswordFile is the file containing the key password when configuring component over + // REST to use SSL. When Secrets is set this is a file name in that k8s secret; without + // Secrets it is an explicit path in the container read by the operator's + // FileBasedPasswordProvider. // +optional KeyPasswordFile *string `json:"keyPasswordFile,omitempty"` // KeyStoreAlgorithm is the name of the keystore algorithm for the keystore in the k8s secret @@ -1086,12 +1092,16 @@ type SSLSpec struct { // when configuring component over REST to use SSL. If not set the default is JKS. // +optional KeyStoreType *string `json:"keyStoreType,omitempty"` - // TrustStore is the name of the Java trust store file in the k8s secret to use as the SSL - // trust store when configuring component over REST to use SSL. + // TrustStore is the Java trust store value used as the SSL trust store when configuring + // component over REST to use SSL. When Secrets is set this is a file name in that k8s + // secret; without Secrets it is an explicit path in the container, which keeps trust-only + // configurations independent from the keystore settings. // +optional TrustStore *string `json:"trustStore,omitempty"` - // TrustStorePasswordFile is the name of the file in the k8s secret containing the trust store - // password when configuring component over REST to use SSL. + // TrustStorePasswordFile is the file containing the trust store password when configuring + // component over REST to use SSL. When Secrets is set this is a file name in that k8s + // secret; without Secrets it is an explicit path in the container read by the + // operator's FileBasedPasswordProvider. // +optional TrustStorePasswordFile *string `json:"trustStorePasswordFile,omitempty"` // TrustStoreAlgorithm is the name of the keystore algorithm for the trust store in the k8s diff --git a/api/v1/coherenceresource_types.go b/api/v1/coherenceresource_types.go index b6c17955c..a49ddf666 100644 --- a/api/v1/coherenceresource_types.go +++ b/api/v1/coherenceresource_types.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -63,12 +63,6 @@ const ( type CoherenceType string -// The package init function that will automatically register the Coherence resource types with -// the default k8s Scheme. -func init() { - SchemeBuilder.Register(&Coherence{}, &CoherenceList{}, &CoherenceJob{}, &CoherenceJobList{}) -} - // ----- Coherence type ------------------------------------------------------------------ var _ CoherenceResource = &Coherence{} diff --git a/api/v1/groupversion_info.go b/api/v1/groupversion_info.go index 79c7d6b05..eb9aa4726 100644 --- a/api/v1/groupversion_info.go +++ b/api/v1/groupversion_info.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -10,17 +10,29 @@ package v1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" ) var ( // GroupVersion is group version used to register these objects GroupVersion = schema.GroupVersion{Group: "coherence.oracle.com", Version: "v1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + // SchemeBuilder stays on apimachinery's registration path so importing the API package + // avoids the deprecated controller-runtime helper while preserving AddToScheme behavior. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme ) + +func addKnownTypes(scheme *runtime.Scheme) error { + // Registering the root API objects here keeps scheme setup explicit for this group/version, + // which replaces the deprecated controller-runtime object-registration helper. + scheme.AddKnownTypes(GroupVersion, &Coherence{}, &CoherenceList{}, &CoherenceJob{}, &CoherenceJobList{}) + // AddToGroupVersion records the API metadata so serialized objects keep the expected + // coherence.oracle.com/v1 identity after the registration path changes. + metav1.AddToGroupVersion(scheme, GroupVersion) + return nil +} diff --git a/api/v1/groupversion_info_test.go b/api/v1/groupversion_info_test.go new file mode 100644 index 000000000..12e9422e8 --- /dev/null +++ b/api/v1/groupversion_info_test.go @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package v1_test + +import ( + "testing" + + . "github.com/onsi/gomega" + coh "github.com/oracle/coherence-operator/api/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestAddToSchemeRegistersCoherenceAPITypes(t *testing.T) { + g := NewGomegaWithT(t) + + // Use an empty scheme so the test proves AddToScheme owns the API type registration + // contract instead of inheriting it from broader test setup. + scheme := runtime.NewScheme() + g.Expect(coh.AddToScheme(scheme)).To(Succeed()) + + tests := []struct { + name string + kind string + expected runtime.Object + }{ + { + name: "coherence", + kind: "Coherence", + expected: &coh.Coherence{}, + }, + { + name: "coherence list", + kind: "CoherenceList", + expected: &coh.CoherenceList{}, + }, + { + name: "coherence job", + kind: "CoherenceJob", + expected: &coh.CoherenceJob{}, + }, + { + name: "coherence job list", + kind: "CoherenceJobList", + expected: &coh.CoherenceJobList{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + obj, err := scheme.New(coh.GroupVersion.WithKind(tt.kind)) + + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(obj).To(BeAssignableToTypeOf(tt.expected)) + }) + } +} diff --git a/go.mod b/go.mod index f73316adb..530cf90c4 100644 --- a/go.mod +++ b/go.mod @@ -8,34 +8,34 @@ module github.com/oracle/coherence-operator // See ./.go-version for the go compiler version used when building binaries // // https://go.dev/doc/modules/gomod-ref#go -go 1.26.2 +go 1.26.3 require ( - github.com/Masterminds/semver/v3 v3.4.0 - github.com/fsnotify/fsnotify v1.9.0 + github.com/Masterminds/semver/v3 v3.5.0 + github.com/fsnotify/fsnotify v1.10.1 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/go-logr/logr v1.4.3 github.com/go-test/deep v1.1.1 - github.com/onsi/gomega v1.39.1 + github.com/onsi/gomega v1.41.0 github.com/pkg/errors v0.9.1 - github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.90.1 - github.com/prometheus-operator/prometheus-operator/pkg/client v0.90.1 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.91.0 + github.com/prometheus-operator/prometheus-operator/pkg/client v0.91.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - golang.org/x/mod v0.35.0 - k8s.io/api v0.35.4 - k8s.io/apiextensions-apiserver v0.35.4 - k8s.io/apimachinery v0.35.4 - k8s.io/client-go v0.35.4 - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 - sigs.k8s.io/controller-runtime v0.23.3 + golang.org/x/mod v0.36.0 + k8s.io/api v0.36.1 + k8s.io/apiextensions-apiserver v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 + k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 + sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/testing_frameworks v0.1.2 ) require ( - cel.dev/expr v0.25.1 // indirect + cel.dev/expr v0.25.2 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -45,7 +45,7 @@ require ( github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.9.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.23.1 // indirect @@ -63,8 +63,7 @@ require ( github.com/go-openapi/swag/typeutils v0.26.0 // indirect github.com/go-openapi/swag/yamlutils v0.26.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.28.0 // indirect + github.com/google/cel-go v0.28.1 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -76,9 +75,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/pelletier/go-toml/v2 v2.3.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect @@ -98,31 +96,32 @@ require ( 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.1 // indirect + go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.15.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect - google.golang.org/grpc v1.80.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.35.4 // indirect - k8s.io/component-base v0.35.4 // indirect + k8s.io/apiserver v0.36.1 // indirect + k8s.io/component-base v0.36.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af // indirect + k8s.io/streaming v0.36.1 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.35.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect diff --git a/go.sum b/go.sum index 1f0c79472..480ec9d9e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -29,10 +29,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= -github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= +github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -85,10 +85,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc= -github.com/google/cel-go v0.28.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/cel-go v0.28.1 h1:YWIwi77J4xIsYUwAF/iIuS6haffzIHS8yWI8glSbLWM= +github.com/google/cel-go v0.28.1/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -96,8 +94,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= @@ -129,25 +127,24 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/onsi/ginkgo v1.4.0 h1:n60/4GZK0Sr9O2iuGKq876Aoa0ER2ydgpMOBwzJ8e2c= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc= -github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= +github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= -github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= +github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.90.1 h1:URbjn501/IBFTzPtGXrYDXHi+ZcbP2W60o6JeTrY3vQ= -github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.90.1/go.mod h1:Gfzi4500QCMnptFIQc8YdDi8YZ4QA0vs22LROWZ3+YU= -github.com/prometheus-operator/prometheus-operator/pkg/client v0.90.1 h1:14emoOxDK98By1T+lWp1TB3S+b4WAgLO1VU7SVnG+wU= -github.com/prometheus-operator/prometheus-operator/pkg/client v0.90.1/go.mod h1:0uAjHkx1IJGD8BqlDmz2rmxfPzan6KtYypSCRGff9lc= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.91.0 h1:m2SZ2z5edgk0nXx7W6VHLfIsKZwgKbr+E5c2RNYyJB8= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.91.0/go.mod h1:Gfzi4500QCMnptFIQc8YdDi8YZ4QA0vs22LROWZ3+YU= +github.com/prometheus-operator/prometheus-operator/pkg/client v0.91.0 h1:WnKN4vTCEkqh4fVQ4q8XWJTXGUS3IPeEgLANVNWy4fk= +github.com/prometheus-operator/prometheus-operator/pkg/client v0.91.0/go.mod h1:3Eg4UduoEYZ+51J2QudVxhi4OAkmhob8/hOFb3L4Q/s= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -206,48 +203,48 @@ 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= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 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-20260420184626-e10c466a9529 h1:zUWMZsvo/IJcD1t6MNCPO/azZTwz0TvwCBqr5aifoVY= -google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529/go.mod h1:a5OGAgyRr4lqco7AG9hQM9Fwh0N2ZV4grR0eXFEsXQg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/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= +google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68 h1:WVVw1Nl19li0fMX++FJ3ye1z9+S1N35QODDy5qpnaXw= +google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:1dCETSCY2YKZNXQE3h4fun3TYwF5p8jejRKZgfWAgAY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -262,28 +259,30 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= -k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= -k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU= -k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c= -k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= -k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= -k8s.io/apiserver v0.35.4 h1:vtuFqNFmF9bPRdHDL2lpK6qCTPWDreZJL4LRPwVM6ho= -k8s.io/apiserver v0.35.4/go.mod h1:JnBcb+J8kFXKpZkgcbcUnPBBHi4qgBii1I7dLxFY/oo= -k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= -k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= -k8s.io/component-base v0.35.4 h1:6n1tNJ87johN0Hif0Fs8K2GMthsaUwMqCebUDLYyv7U= -k8s.io/component-base v0.35.4/go.mod h1:qaDJgz5c1KYKla9occFmlJEfPpkuA55s90G509R+PeY= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks= +k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY= +k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA= +k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f h1:4Qiq0YAoQATdgmHALJWz9rJ4fj20pB3xebpB4CFNhYM= -k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= -sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af h1:zLXA2Irn14q2/06WMkxViyr7YCPUO2lJ0QYE9Juy5vA= +k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= +k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= +k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.35.0 h1:u3Ls3Y5KMAJXGPYiLMblEsT/9pCgwyElgIqKuFumqCA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.35.0/go.mod h1:tJo1aepTXyR+8Xs3sUsGBDk4Ub2AM5dPAPKJx0mpm5c= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/helm-charts/coherence-operator/.helmignore b/helm-charts/coherence-operator/.helmignore new file mode 100644 index 000000000..da17d9ddd --- /dev/null +++ b/helm-charts/coherence-operator/.helmignore @@ -0,0 +1,10 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +.git +.helmignore + +# Chart-adjacent Go sources are repository-side files and should not ship in +# the published Helm chart tarball. +*.go diff --git a/helm-charts/coherence-operator/chart_test.go b/helm-charts/coherence-operator/chart_test.go index 7646afb43..61c8360e1 100644 --- a/helm-charts/coherence-operator/chart_test.go +++ b/helm-charts/coherence-operator/chart_test.go @@ -7,35 +7,43 @@ package coherenceoperator import ( + "fmt" "os" + "regexp" + "strconv" "testing" semver "github.com/Masterminds/semver/v3" "github.com/ghodss/yaml" ) +const ( + // Paths are relative to helm-charts/coherence-operator; update this if the + // config/manifests/bases OLM metadata layout moves. + csvMetadataPath = "../../config/manifests/bases/coherence-operator.clusterserviceversion.yaml" + // Paths are relative to helm-charts/coherence-operator; update this if the + // repository root Makefile moves. + makefilePath = "../../Makefile" +) + type chartMetadata struct { KubeVersion string `json:"kubeVersion"` } -func TestKubeVersionConstraint(t *testing.T) { - data, err := os.ReadFile("Chart.yaml") - if err != nil { - t.Fatalf("failed to read Helm chart metadata: %v", err) - } +type csvMetadata struct { + Spec struct { + MinKubeVersion string `json:"minKubeVersion"` + } `json:"spec"` +} - var chart chartMetadata - if err = yaml.Unmarshal(data, &chart); err != nil { - t.Fatalf("failed to parse Helm chart metadata: %v", err) - } - if chart.KubeVersion == "" { - t.Fatal("expected Helm chart metadata to declare kubeVersion") - } +type kubernetesVersionFloor struct { + Major int + Minor int +} - constraint, err := semver.NewConstraint(chart.KubeVersion) - if err != nil { - t.Fatalf("failed to parse kubeVersion constraint %q: %v", chart.KubeVersion, err) - } +func TestKubeVersionConstraint(t *testing.T) { + chart := readChartMetadata(t) + minimum, constraint := parseChartKubernetesConstraint(t, chart.KubeVersion) tests := []struct { name string @@ -44,17 +52,27 @@ func TestKubeVersionConstraint(t *testing.T) { }{ { name: "accepts supported vendor suffixed Kubernetes versions", - version: "1.32.3-vke.8", + version: minimum.vendorSuffixedPatchString(), + want: true, + }, + { + name: "accepts vendor suffixed Kubernetes versions at the support floor", + version: minimum.patchString() + "-eks.1", want: true, }, { name: "accepts the documented support floor", - version: "1.29.0", + version: minimum.patchString(), want: true, }, { name: "rejects versions below the documented support floor", - version: "1.28.0", + version: minimum.previousMinorPatchString(t), + want: false, + }, + { + name: "rejects vendor suffixed Kubernetes versions below the support floor", + version: minimum.previousMinorPatchString(t) + "-gke.123", want: false, }, } @@ -66,10 +84,150 @@ func TestKubeVersionConstraint(t *testing.T) { t.Fatalf("failed to parse Kubernetes version %q: %v", tt.version, err) } - // Exercise the chart's actual constraint so future edits preserve both the 1.29 floor and vendor suffix support. + // Exercise the chart's actual constraint so future edits preserve both the declared + // floor and vendor suffix handling at the boundary. if got := constraint.Check(version); got != tt.want { t.Fatalf("kubeVersion %q match for %q = %v, want %v", chart.KubeVersion, tt.version, got, tt.want) } }) } } + +func TestKubernetesMinimumVersionsAreAligned(t *testing.T) { + chartFloor, _ := parseChartKubernetesConstraint(t, readChartMetadata(t).KubeVersion) + csvFloor := readCSVKubernetesFloor(t) + makefileFloor := readMakefileKubernetesFloor(t) + + // These files gate different install paths, so compare the normalized floor to catch + // drift before Helm, OLM, and validation builds start enforcing different versions. + if chartFloor != csvFloor || chartFloor != makefileFloor { + t.Fatalf("Kubernetes minimum versions differ: Chart.yaml=%s, CSV=%s, Makefile=%s", + chartFloor.minorString(), csvFloor.minorString(), makefileFloor.minorString()) + } +} + +func readChartMetadata(t *testing.T) chartMetadata { + t.Helper() + + data, err := os.ReadFile("Chart.yaml") + if err != nil { + t.Fatalf("failed to read Helm chart metadata: %v", err) + } + + var chart chartMetadata + if err = yaml.Unmarshal(data, &chart); err != nil { + t.Fatalf("failed to parse Helm chart metadata: %v", err) + } + if chart.KubeVersion == "" { + t.Fatal("expected Helm chart metadata to declare kubeVersion") + } + + return chart +} + +func readCSVKubernetesFloor(t *testing.T) kubernetesVersionFloor { + t.Helper() + + data, err := os.ReadFile(csvMetadataPath) + if err != nil { + t.Fatalf("failed to read OLM CSV metadata: %v", err) + } + + var csv csvMetadata + if err = yaml.Unmarshal(data, &csv); err != nil { + t.Fatalf("failed to parse OLM CSV metadata: %v", err) + } + + return parsePatchKubernetesFloor(t, csv.Spec.MinKubeVersion, "CSV minKubeVersion") +} + +func readMakefileKubernetesFloor(t *testing.T) kubernetesVersionFloor { + t.Helper() + + data, err := os.ReadFile(makefilePath) + if err != nil { + t.Fatalf("failed to read Makefile: %v", err) + } + + re := regexp.MustCompile(`(?m)^[ \t]*(?:export[ \t]+)?KUBERNETES_MIN_VERSION[ \t]*(?::{1,2}=|\?=|=)[ \t]*(\d+)\.(\d+)(?:\.0)?[ \t]*(?:#.*)?\r?$`) + matches := re.FindStringSubmatch(string(data)) + if matches == nil { + t.Fatal("expected Makefile to declare KUBERNETES_MIN_VERSION with =, :=, ?=, or ::= and value .[.0]") + } + + return parseKubernetesFloor(t, matches[1], matches[2], "Makefile KUBERNETES_MIN_VERSION") +} + +func parseChartKubernetesConstraint(t *testing.T, version string) (kubernetesVersionFloor, *semver.Constraints) { + t.Helper() + + constraint, err := semver.NewConstraint(version) + if err != nil { + t.Fatalf("failed to parse Chart.yaml kubeVersion constraint %q: %v", version, err) + } + + re := regexp.MustCompile(`^[ \t]*>=[ \t]*(\d+)\.(\d+)(?:\.0)?-0(?:[ \t]*(?:,|\|\||[ \t]+).*)?[ \t]*$`) + matches := re.FindStringSubmatch(version) + if matches == nil { + t.Fatalf("expected Chart.yaml kubeVersion %q to include a >=.[.0]-0 lower bound", version) + } + + return parseKubernetesFloor(t, matches[1], matches[2], "Chart.yaml kubeVersion"), constraint +} + +func parsePatchKubernetesFloor(t *testing.T, version, field string) kubernetesVersionFloor { + t.Helper() + + re := regexp.MustCompile(`^(\d+)\.(\d+)\.0$`) + matches := re.FindStringSubmatch(version) + if matches == nil { + t.Fatalf("expected %s %q to use ..0", field, version) + } + + return parseKubernetesFloor(t, matches[1], matches[2], field) +} + +func parseKubernetesFloor(t *testing.T, major, minor, field string) kubernetesVersionFloor { + t.Helper() + + majorValue, err := strconv.Atoi(major) + if err != nil { + t.Fatalf("failed to parse %s major version %q: %v", field, major, err) + } + + minorValue, err := strconv.Atoi(minor) + if err != nil { + t.Fatalf("failed to parse %s minor version %q: %v", field, minor, err) + } + + return kubernetesVersionFloor{ + Major: majorValue, + Minor: minorValue, + } +} + +func (v kubernetesVersionFloor) minorString() string { + return fmt.Sprintf("%d.%d", v.Major, v.Minor) +} + +func (v kubernetesVersionFloor) patchString() string { + return fmt.Sprintf("%s.0", v.minorString()) +} + +func (v kubernetesVersionFloor) vendorSuffixedPatchString() string { + return fmt.Sprintf("%d.%d.3-vke.8", v.Major, v.Minor) +} + +func (v kubernetesVersionFloor) previousMinorPatchString(t *testing.T) string { + t.Helper() + + switch { + case v.Minor > 0: + return fmt.Sprintf("%d.%d.0", v.Major, v.Minor-1) + case v.Major > 0: + return fmt.Sprintf("%d.0.0", v.Major-1) + default: + t.Fatal("cannot compute previous minor for Kubernetes 0.0 floor") + return "" + } +} diff --git a/java/pom.xml b/java/pom.xml index 3197455f3..91da97f16 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -69,7 +69,7 @@ 5.7.2 3.10.0 3.25.5 - 3.4.5 + 3.4.12 1.8.4 2.8.9 @@ -86,7 +86,7 @@ 3.0.0 2.0.0-M3 3.1.1 - 3.4.5 + 3.4.6 3.1.1 3.7.0 3.2.0 diff --git a/pkg/runner/cmd_operator.go b/pkg/runner/cmd_operator.go index 31b6cc8db..15c8fae3d 100644 --- a/pkg/runner/cmd_operator.go +++ b/pkg/runner/cmd_operator.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -25,6 +25,7 @@ import ( "github.com/spf13/viper" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apiruntime "k8s.io/apimachinery/pkg/runtime" + utilnet "k8s.io/apimachinery/pkg/util/net" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/version" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -49,6 +50,10 @@ const ( // This value should not be changed, otherwise a rolling upgrade of the Operator // would have two leaders. lockName = "ca804aa8.oracle.com" + + // maxRoundTripperUnwrapDepth bounds how many wrapper layers can be followed. + // Deeper or cyclic custom chains are left unchanged instead of risking an operator startup hang. + maxRoundTripperUnwrapDepth = 32 ) var ( @@ -109,11 +114,13 @@ func execute(v *viper.Viper) error { setupLog.Info("Obtaining kubernetes client config") cfg := ctrl.GetConfigOrDie() - cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { - t := rt.(*http.Transport) - suiteConfig(t.TLSClientConfig) + cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper { + // Use Wrap so any existing client-go transport hook runs before this one; + // HTTPWrappersForConfig then adds auth/user-agent layers around the same wrapper + // shape after this hook mutates the reachable TLS transport. + configureTransportTLS(rt, suiteConfig) return rt - } + }) setupLog.Info("Creating kubernetes client") cs, err := clients.NewForConfig(cfg) @@ -295,6 +302,39 @@ func execute(v *viper.Viper) error { return nil } +func configureTransportTLS(rt http.RoundTripper, configure func(*tls.Config)) { + t, ok := findHTTPTransport(rt) + if !ok { + // Unknown transports are left unchanged so startup remains best-effort, but + // log the skipped mutation because Kubernetes client cipher settings may not apply. + setupLog.Info("Skipping Kubernetes client transport TLS configuration", "TransportType", fmt.Sprintf("%T", rt)) + return + } + if t.TLSClientConfig == nil { + t.TLSClientConfig = &tls.Config{} + } + configure(t.TLSClientConfig) +} + +func findHTTPTransport(rt http.RoundTripper) (*http.Transport, bool) { + for depth := 0; rt != nil; depth++ { + if t, ok := rt.(*http.Transport); ok { + return t, true + } + if depth >= maxRoundTripperUnwrapDepth { + return nil, false + } + wrapper, ok := rt.(utilnet.RoundTripperWrapper) + if !ok { + return nil, false + } + // client-go v0.36 may pass cache-managed wrapper transports here before this + // wrapper runs, so unwrap layers to preserve cipher-suite behavior without panicking. + rt = wrapper.WrappedRoundTripper() + } + return nil, false +} + // GetServerVersion fetches the Kubernetes server version using the provided ClientSet and returns it as a version.Info struct. // It uses the discovery client to send a GET request to the "/version" endpoint and parses the response into version.Info. // This method has a default timeout of 1 minute but can be overridden by setting the environment variable KUBERNETES_CHECK_TIMEOUT. diff --git a/pkg/runner/cmd_operator_test.go b/pkg/runner/cmd_operator_test.go index 4ff3bb261..669b6033a 100644 --- a/pkg/runner/cmd_operator_test.go +++ b/pkg/runner/cmd_operator_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -8,19 +8,245 @@ package runner import ( "crypto/tls" + "net/http" + "strings" + "testing" + . "github.com/onsi/gomega" coh "github.com/oracle/coherence-operator/api/v1" "github.com/oracle/coherence-operator/pkg/operator" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilnet "k8s.io/apimachinery/pkg/util/net" + restclient "k8s.io/client-go/rest" + clientgotransport "k8s.io/client-go/transport" ctrl "sigs.k8s.io/controller-runtime" - "strings" - "testing" ) var ( testLog = ctrl.Log.WithName("test") ) +type testWrappedRoundTripper struct { + wrapped http.RoundTripper +} + +func (t testWrappedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return t.wrapped.RoundTrip(req) +} + +func (t testWrappedRoundTripper) WrappedRoundTripper() http.RoundTripper { + return t.wrapped +} + +type testUnsupportedRoundTripper struct{} + +func (t testUnsupportedRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { + return nil, nil +} + +type testSelfWrappingRoundTripper struct{} + +func (t testSelfWrappingRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { + return nil, nil +} + +func (t testSelfWrappingRoundTripper) WrappedRoundTripper() http.RoundTripper { + return t +} + +type testRecordingRoundTripper struct { + wrapped http.RoundTripper + roundTripCalled *bool +} + +func (t *testRecordingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + // The chaining test needs to prove delegation without opening sockets, so return + // a synthetic response while still exposing the wrapped transport for TLS mutation. + *t.roundTripCalled = true + return &http.Response{ + StatusCode: http.StatusOK, + Body: http.NoBody, + Request: req, + }, nil +} + +func (t *testRecordingRoundTripper) WrappedRoundTripper() http.RoundTripper { + return t.wrapped +} + +func wrapRoundTripper(rt http.RoundTripper, depth int) http.RoundTripper { + for range depth { + rt = testWrappedRoundTripper{wrapped: rt} + } + return rt +} + +func TestConfigureTransportTLSWithHTTPTransport(t *testing.T) { + g := NewGomegaWithT(t) + transport := &http.Transport{} + + configureTransportTLS(transport, func(c *tls.Config) { + // The helper must create missing TLS config so operator TLS options still + // affect plain client-go transports instead of being silently skipped. + c.MinVersion = tls.VersionTLS13 + }) + + g.Expect(transport.TLSClientConfig).NotTo(BeNil()) + g.Expect(transport.TLSClientConfig.MinVersion).To(Equal(uint16(tls.VersionTLS13))) +} + +func TestConfigureTransportTLSWithWrappedHTTPTransport(t *testing.T) { + g := NewGomegaWithT(t) + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + ServerName: "api-server", + }, + } + wrapped := testWrappedRoundTripper{wrapped: transport} + + configureTransportTLS(wrapped, func(c *tls.Config) { + // Wrapped transports are used by newer client-go cache layers, so unwrapping + // preserves the expected cipher and protocol settings without changing the chain. + c.NextProtos = []string{"http/1.1"} + }) + + g.Expect(transport.TLSClientConfig.ServerName).To(Equal("api-server")) + g.Expect(transport.TLSClientConfig.NextProtos).To(Equal([]string{"http/1.1"})) +} + +func TestConfigureTransportTLSWithMaxDepthWrappedHTTPTransport(t *testing.T) { + g := NewGomegaWithT(t) + transport := &http.Transport{} + wrapped := wrapRoundTripper(transport, maxRoundTripperUnwrapDepth) + + configureTransportTLS(wrapped, func(c *tls.Config) { + // The depth constant is intended to allow this many real wrapper layers; + // only deeper or cyclic chains should fall back to best-effort skip behavior. + c.ServerName = "api-server" + }) + + g.Expect(transport.TLSClientConfig).NotTo(BeNil()) + g.Expect(transport.TLSClientConfig.ServerName).To(Equal("api-server")) +} + +func TestConfigureTransportTLSSkipsOverMaxDepthWrappedHTTPTransport(t *testing.T) { + g := NewGomegaWithT(t) + transport := &http.Transport{} + wrapped := wrapRoundTripper(transport, maxRoundTripperUnwrapDepth+1) + called := false + + configureTransportTLS(wrapped, func(*tls.Config) { + called = true + }) + + // Over-limit chains are treated like hidden transports: leave the client alone + // rather than risk hanging startup while chasing a pathological wrapper graph. + g.Expect(called).To(BeFalse()) + g.Expect(transport.TLSClientConfig).To(BeNil()) +} + +func TestConfigureTransportTLSWithUnsupportedRoundTripper(t *testing.T) { + g := NewGomegaWithT(t) + called := false + + g.Expect(func() { + configureTransportTLS(testUnsupportedRoundTripper{}, func(*tls.Config) { + called = true + }) + }).NotTo(Panic()) + // Unknown transport implementations should be left alone so custom clients keep working + // even when their TLS internals are not visible to this package. + g.Expect(called).To(BeFalse()) +} + +func TestConfigureTransportTLSWithSelfWrappingRoundTripper(t *testing.T) { + g := NewGomegaWithT(t) + called := false + + g.Expect(func() { + configureTransportTLS(testSelfWrappingRoundTripper{}, func(*tls.Config) { + called = true + }) + }).NotTo(Panic()) + // The unwrap depth guard keeps malformed wrapper cycles from hanging startup; + // TLS configuration remains best-effort when no concrete transport is reachable. + g.Expect(called).To(BeFalse()) +} + +func TestConfigureTransportTLSWithClientGoCachedTransport(t *testing.T) { + g := NewGomegaWithT(t) + cfg := &clientgotransport.Config{ + TLS: clientgotransport.TLSConfig{ + Insecure: true, + }, + } + cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper { + // client-go v0.36 creates a cache-managed wrapper before invoking WrapTransport; + // this verifies the production helper works against that real wrapper path. + configureTransportTLS(rt, func(c *tls.Config) { + c.ServerName = "api-server" + }) + return rt + }) + + rt, err := clientgotransport.New(cfg) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(rt).NotTo(BeNil()) + + tlsConfig, err := utilnet.TLSClientConfig(rt) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(tlsConfig).NotTo(BeNil()) + g.Expect(tlsConfig.ServerName).To(Equal("api-server")) +} + +func TestConfigureTransportTLSWithConfigWrapPreservesChain(t *testing.T) { + g := NewGomegaWithT(t) + cfg := &restclient.Config{} + base := &http.Transport{} + compositionOrder := make([]string, 0, 2) + roundTripCalled := false + var preExistingLayer *testRecordingRoundTripper + var operatorReceived http.RoundTripper + var operatorReturned http.RoundTripper + + cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper { + compositionOrder = append(compositionOrder, "pre-existing") + preExistingLayer = &testRecordingRoundTripper{ + wrapped: rt, + roundTripCalled: &roundTripCalled, + } + return preExistingLayer + }) + cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper { + compositionOrder = append(compositionOrder, "operator") + operatorReceived = rt + // Match production's callback contract: mutate the reachable TLS transport + // synchronously at WrapTransport composition time, then return the same rt. + configureTransportTLS(rt, func(c *tls.Config) { + c.MinVersion = tls.VersionTLS13 + }) + operatorReturned = rt + return rt + }) + + finalRoundTripper := cfg.WrapTransport(base) + + g.Expect(compositionOrder).To(Equal([]string{"pre-existing", "operator"})) + g.Expect(operatorReceived).To(BeIdenticalTo(preExistingLayer)) + g.Expect(operatorReturned).To(BeIdenticalTo(operatorReceived)) + g.Expect(finalRoundTripper).To(BeIdenticalTo(preExistingLayer)) + g.Expect(base.TLSClientConfig).NotTo(BeNil()) + g.Expect(base.TLSClientConfig.MinVersion).To(Equal(uint16(tls.VersionTLS13))) + + req, err := http.NewRequest(http.MethodGet, "https://example.com", nil) + g.Expect(err).NotTo(HaveOccurred()) + resp, err := finalRoundTripper.RoundTrip(req) + g.Expect(err).NotTo(HaveOccurred()) + defer resp.Body.Close() + g.Expect(resp.StatusCode).To(Equal(http.StatusOK)) + g.Expect(roundTripCalled).To(BeTrue()) +} + func TestBasicOperator(t *testing.T) { g := NewGomegaWithT(t) diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index cc86d2883..b737d29be 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -1056,83 +1056,67 @@ func cohPost2206(details *run_details.RunDetails) { } func addManagementSSL(details *run_details.RunDetails) { - addSSL(v1.EnvVarCohMgmtPrefix, v1.PortNameManagement, details) + addSSL(v1.EnvVarCohMgmtPrefix, v1.PortNameManagement, "ManagementSSLProvider", details) } func addMetricsSSL(details *run_details.RunDetails) { - addSSL(v1.EnvVarCohMetricsPrefix, v1.PortNameMetrics, details) + addSSL(v1.EnvVarCohMetricsPrefix, v1.PortNameMetrics, "MetricsSSLProvider", details) } -func addSSL(prefix, prop string, details *run_details.RunDetails) { - var urlPrefix string - - sslCerts := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLCerts) - if sslCerts != "" { - if !strings.HasSuffix(sslCerts, "/") { - sslCerts += "/" - } - if strings.HasSuffix(sslCerts, "file:") { - urlPrefix = sslCerts - } else { - urlPrefix = "file:" + sslCerts - } - } else { - urlPrefix = "file:" - } +func addSSL(prefix, prop, provider string, details *run_details.RunDetails) { + urlPrefix, pathPrefix := getSSLPrefixes(prefix, details) + securityPrefix := "coherence." + prop + ".security." if details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLEnabled) != "" { - details.AddArg("-Dcoherence." + prop + ".http.provider=ManagementSSLProvider") + details.AddSystemPropertyArg("coherence."+prop+".http.provider", provider) } - ks := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLKeyStore) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.keystore=" + urlPrefix + ks) - } - kspw := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLKeyStoreCredFile) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.keystore.password=" + urlPrefix + kspw) - } - kpw := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLKeyCredFile) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.key.password=" + urlPrefix + kpw) - } - kalg := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLKeyStoreAlgo) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.keystore.algorithm=" + urlPrefix + kalg) - } - kprov := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLKeyStoreProvider) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.keystore.provider=" + urlPrefix + kprov) - } - ktyp := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLKeyStoreType) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.keystore.type=" + urlPrefix + ktyp) - } + addSSLProperty(prefix, v1.EnvVarSuffixSSLKeyStore, securityPrefix+"keystore", urlPrefix, details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLKeyStoreCredFile, securityPrefix+"keystore.password", pathPrefix, details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLKeyCredFile, securityPrefix+"key.password", pathPrefix, details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLKeyStoreAlgo, securityPrefix+"keystore.algorithm", "", details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLKeyStoreProvider, securityPrefix+"keystore.provider", "", details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLKeyStoreType, securityPrefix+"keystore.type", "", details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLTrustStore, securityPrefix+"truststore", urlPrefix, details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLTrustStoreCredFile, securityPrefix+"truststore.password", pathPrefix, details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLTrustStoreAlgo, securityPrefix+"truststore.algorithm", "", details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLTrustStoreProvider, securityPrefix+"truststore.provider", "", details) + addSSLProperty(prefix, v1.EnvVarSuffixSSLTrustStoreType, securityPrefix+"truststore.type", "", details) - ts := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLTrustStore) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.truststore=" + urlPrefix + ts) - } - tspw := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLTrustStoreCredFile) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.truststore.password=" + urlPrefix + tspw) - } - talg := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLTrustStoreAlgo) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.truststore.algorithm=" + urlPrefix + talg) + if details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLRequireClientCert) != "" { + details.AddSystemPropertyArg("coherence."+prop+".http.auth", "cert") } - tprov := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLTrustStoreProvider) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.truststore.provider=" + urlPrefix + tprov) +} + +func getSSLPrefixes(prefix string, details *run_details.RunDetails) (string, string) { + sslCerts := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLCerts) + if sslCerts == "" { + return "file:", "" } - ttyp := details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLTrustStoreType) - if ks != "" { - details.AddArg("-Dcoherence." + prop + ".security.truststore.type=" + urlPrefix + ttyp) + + // _SSL_CERTS is produced from an operator volume mount path. Stripping a + // leading file: keeps future callers from double-prefixing keystore URLs + // and from passing URL strings to FileBasedPasswordProvider, which expects + // plain file paths. + sslCerts = strings.TrimPrefix(sslCerts, "file:") + if !strings.HasSuffix(sslCerts, "/") { + sslCerts += "/" } - if details.GetenvWithPrefix(prefix, v1.EnvVarSuffixSSLRequireClientCert) != "" { - details.AddArg("-Dcoherence." + prop + ".http.auth=cert") + return "file:" + sslCerts, sslCerts +} + +func addSSLProperty(prefix, envSuffix, property, valuePrefix string, details *run_details.RunDetails) { + value := details.GetenvWithPrefix(prefix, envSuffix) + if value == "" { + return } + + // Each SSL field is emitted from its own env var so an empty or unrelated + // keystore setting cannot create file: placeholders or suppress truststore + // overrides. The prefix argument carries the expected value form: URL, + // filesystem path, or literal. + details.AddSystemPropertyArg(property, valuePrefix+value) } func closeFile(f *os.File, log logr.Logger) { diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go index 32d9464f6..d3f874363 100644 --- a/pkg/runner/runner_test.go +++ b/pkg/runner/runner_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * http://oss.oracle.com/licenses/upl. */ @@ -10,12 +10,15 @@ import ( "fmt" . "github.com/onsi/gomega" coh "github.com/oracle/coherence-operator/api/v1" + "github.com/oracle/coherence-operator/pkg/runner/run_details" "github.com/oracle/coherence-operator/test/e2e/helper" + "github.com/spf13/viper" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" "os" "os/exec" + ctrl "sigs.k8s.io/controller-runtime" "strings" "testing" ) @@ -379,3 +382,213 @@ func filterStringArray(ss []string, test func(string) bool) (ret []string) { } return } + +// The SSL tests call addSSL through its management and metrics wrappers because +// the JVM arguments are the behavioral contract; env-var-only tests cannot catch +// invalid file URL prefixes on password files or literal SSL metadata. +func TestAddMetricsSSLWithoutSecretsUsesURLPathAndLiteralValues(t *testing.T) { + g := NewGomegaWithT(t) + details := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLEnabled: "true", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStore: "/keystore/cacerts.p12", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreType: "PKCS12", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreCredFile: "/config/storepass.txt", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyCredFile: "/config/keypass.txt", + }) + + addMetricsSSL(details) + + args := details.GetArguments() + g.Expect(args).To(ContainElements( + "-Dcoherence.metrics.http.provider=MetricsSSLProvider", + "-Dcoherence.metrics.security.keystore=file:/keystore/cacerts.p12", + "-Dcoherence.metrics.security.keystore.type=PKCS12", + "-Dcoherence.metrics.security.keystore.password=/config/storepass.txt", + "-Dcoherence.metrics.security.key.password=/config/keypass.txt", + )) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.http.provider=ManagementSSLProvider")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.keystore.type=file:PKCS12")) + assertNoArgHasPrefix(t, args, "-Dcoherence.metrics.security.keystore.algorithm=") + assertNoArgHasPrefix(t, args, "-Dcoherence.metrics.security.keystore.provider=") + assertNoArgHasPrefix(t, args, "-Dcoherence.metrics.security.truststore=") + assertNoArgHasPrefix(t, args, "-Dcoherence.metrics.security.truststore.type=") + assertNoArgEndsWith(t, args, "=file:") +} + +func TestAddMetricsSSLWithSecretsResolvesFilesAndPreservesLiterals(t *testing.T) { + g := NewGomegaWithT(t) + details := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLCerts: "/coherence/certs/metrics", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStore: "server.jks", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreCredFile: "storepass.txt", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyCredFile: "keypass.txt", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreAlgo: "SunX509", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreProvider: "SunJSSE", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreType: "PKCS12", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStore: "truststore.jks", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStoreCredFile: "trustpass.txt", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStoreAlgo: "PKIX", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStoreProvider: "SunJSSE", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStoreType: "JKS", + }) + + addMetricsSSL(details) + + args := details.GetArguments() + g.Expect(args).To(ContainElements( + "-Dcoherence.metrics.security.keystore=file:/coherence/certs/metrics/server.jks", + "-Dcoherence.metrics.security.keystore.password=/coherence/certs/metrics/storepass.txt", + "-Dcoherence.metrics.security.key.password=/coherence/certs/metrics/keypass.txt", + "-Dcoherence.metrics.security.keystore.algorithm=SunX509", + "-Dcoherence.metrics.security.keystore.provider=SunJSSE", + "-Dcoherence.metrics.security.keystore.type=PKCS12", + "-Dcoherence.metrics.security.truststore=file:/coherence/certs/metrics/truststore.jks", + "-Dcoherence.metrics.security.truststore.password=/coherence/certs/metrics/trustpass.txt", + "-Dcoherence.metrics.security.truststore.algorithm=PKIX", + "-Dcoherence.metrics.security.truststore.provider=SunJSSE", + "-Dcoherence.metrics.security.truststore.type=JKS", + )) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.keystore.algorithm=file:SunX509")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.keystore.provider=file:SunJSSE")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.keystore.type=file:PKCS12")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.truststore.algorithm=file:PKIX")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.truststore.provider=file:SunJSSE")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.truststore.type=file:JKS")) + assertNoArgEndsWith(t, args, "=file:") +} + +func TestAddManagementSSLUsesManagementProperties(t *testing.T) { + g := NewGomegaWithT(t) + details := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMgmtPrefix + coh.EnvVarSuffixSSLEnabled: "true", + coh.EnvVarCohMgmtPrefix + coh.EnvVarSuffixSSLKeyStore: "/management/server.p12", + coh.EnvVarCohMgmtPrefix + coh.EnvVarSuffixSSLKeyStoreCredFile: "/management/storepass.txt", + coh.EnvVarCohMgmtPrefix + coh.EnvVarSuffixSSLKeyStoreType: "PKCS12", + }) + + addManagementSSL(details) + + args := details.GetArguments() + g.Expect(args).To(ContainElements( + "-Dcoherence.management.http.provider=ManagementSSLProvider", + "-Dcoherence.management.security.keystore=file:/management/server.p12", + "-Dcoherence.management.security.keystore.password=/management/storepass.txt", + "-Dcoherence.management.security.keystore.type=PKCS12", + )) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.http.provider=MetricsSSLProvider")) +} + +func TestAddSSLTrustStoreGuardsUseTrustStoreValues(t *testing.T) { + g := NewGomegaWithT(t) + trustOnly := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStore: "/trust/truststore.jks", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStoreCredFile: "/trust/trustpass.txt", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLTrustStoreType: "PKCS12", + }) + + addMetricsSSL(trustOnly) + + trustOnlyArgs := trustOnly.GetArguments() + g.Expect(trustOnlyArgs).To(ContainElements( + "-Dcoherence.metrics.security.truststore=file:/trust/truststore.jks", + "-Dcoherence.metrics.security.truststore.password=/trust/trustpass.txt", + "-Dcoherence.metrics.security.truststore.type=PKCS12", + )) + assertNoArgHasPrefix(t, trustOnlyArgs, "-Dcoherence.metrics.security.keystore=") + + keyOnly := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStore: "/key/keystore.jks", + }) + + addMetricsSSL(keyOnly) + + keyOnlyArgs := keyOnly.GetArguments() + g.Expect(keyOnlyArgs).To(ContainElement("-Dcoherence.metrics.security.keystore=file:/key/keystore.jks")) + assertNoArgHasPrefix(t, keyOnlyArgs, "-Dcoherence.metrics.security.truststore") +} + +func TestAddSSLProviderRequiresSSLEnabled(t *testing.T) { + metrics := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStore: "/metrics/server.jks", + }) + management := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMgmtPrefix + coh.EnvVarSuffixSSLKeyStore: "/management/server.jks", + }) + + addMetricsSSL(metrics) + addManagementSSL(management) + + assertNoArgHasPrefix(t, metrics.GetArguments(), "-Dcoherence.metrics.http.provider=") + assertNoArgHasPrefix(t, management.GetArguments(), "-Dcoherence.management.http.provider=") +} + +func TestAddSSLRequireClientCertControlsHTTPAuth(t *testing.T) { + g := NewGomegaWithT(t) + withClientCert := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLRequireClientCert: "true", + }) + withoutClientCert := newSSLRunDetails(nil) + + addMetricsSSL(withClientCert) + addMetricsSSL(withoutClientCert) + + g.Expect(withClientCert.GetArguments()).To(ContainElement("-Dcoherence.metrics.http.auth=cert")) + g.Expect(withoutClientCert.GetArguments()).NotTo(ContainElement("-Dcoherence.metrics.http.auth=cert")) +} + +func TestAddSSLNormalizesFileQualifiedCertsDirectory(t *testing.T) { + g := NewGomegaWithT(t) + details := newSSLRunDetails(map[string]string{ + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLCerts: "file:/coherence/certs/metrics", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStore: "server.jks", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreCredFile: "storepass.txt", + coh.EnvVarCohMetricsPrefix + coh.EnvVarSuffixSSLKeyStoreType: "PKCS12", + }) + + addMetricsSSL(details) + + args := details.GetArguments() + g.Expect(args).To(ContainElements( + "-Dcoherence.metrics.security.keystore=file:/coherence/certs/metrics/server.jks", + "-Dcoherence.metrics.security.keystore.password=/coherence/certs/metrics/storepass.txt", + "-Dcoherence.metrics.security.keystore.type=PKCS12", + )) + assertNoArgContains(t, args, "file:file:") + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.keystore.password=file:/coherence/certs/metrics/storepass.txt")) + g.Expect(args).NotTo(ContainElement("-Dcoherence.metrics.security.keystore.type=file:PKCS12")) +} + +func newSSLRunDetails(env map[string]string) *run_details.RunDetails { + v := viper.New() + for k, val := range env { + v.Set(k, val) + } + return run_details.NewRunDetails(v, ctrl.Log.WithName("test").WithName("ssl")) +} + +func assertNoArgEndsWith(t *testing.T, args []string, suffix string) { + t.Helper() + for _, arg := range args { + if strings.HasSuffix(arg, suffix) { + t.Fatalf("argument %q ends with %q", arg, suffix) + } + } +} + +func assertNoArgHasPrefix(t *testing.T, args []string, prefix string) { + t.Helper() + for _, arg := range args { + if strings.HasPrefix(arg, prefix) { + t.Fatalf("argument %q starts with %q", arg, prefix) + } + } +} + +func assertNoArgContains(t *testing.T, args []string, value string) { + t.Helper() + for _, arg := range args { + if strings.Contains(arg, value) { + t.Fatalf("argument %q contains %q", arg, value) + } + } +}