diff --git a/.github/workflows/lint-config-checks.yaml b/.github/workflows/lint-config-checks.yaml new file mode 100644 index 000000000..94534e266 --- /dev/null +++ b/.github/workflows/lint-config-checks.yaml @@ -0,0 +1,27 @@ +name: Lint Config Checks + +on: + push: + paths: + - '.golangci.yml' + - '.github/workflows/lint-config-checks.yaml' + branches: [ main ] + pull_request: + paths: + - '.golangci.yml' + - '.github/workflows/lint-config-checks.yaml' + branches: [ main ] + +jobs: + lint-config-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + + - name: Setup Go + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6 + with: + go-version-file: 'go.mod' + + - name: Verify linter configuration + run: make lint-config diff --git a/.golangci.yml b/.golangci.yml index 8826e9246..4830366b6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,14 +1,62 @@ -# Configuration for the golangci-lint tool - ensuring that dot imports do not trigger an ST1001 warning. - version: "2" +run: + allow-parallel-runners: true linters: + default: none enable: + - errcheck + - govet + - ineffassign + - misspell + - nakedret - staticcheck + - unconvert + - unused + # TODO(asoro): Enable these linters from the operator-sdk scaffold once the + # existing violations are fixed. Tracked as tech debt. + #- copyloopvar + #- dupl + #- ginkgolinter + #- goconst + #- gocyclo + #- lll + #- prealloc + #- revive + #- unparam + # TODO(asoro): Re-add revive settings when the linter is enabled + #settings: + # revive: + # rules: + # - name: comment-spacings + # - name: import-shadowing exclusions: + generated: lax rules: - path: '^integration_tests/|^tests/|[^/]+_test\.go' linters: - staticcheck # exclude dot-imports checks on test-related files - text: "ST1001:" \ No newline at end of file + text: "ST1001:" + # TODO(asoro): Re-add these exclusions when lll and dupl are enabled + #- linters: + # - lll + # path: api/* + #- linters: + # - dupl + # - lll + # path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index fae5219f7..44bcbeaad 100644 --- a/Makefile +++ b/Makefile @@ -92,14 +92,13 @@ endif # Set the Operator SDK version to use. By default, what is installed on the system is used. # This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. -OPERATOR_SDK_VERSION ?= v1.37.0 -OPM_VERSION ?= v1.23.0 +OPERATOR_SDK_VERSION ?= v1.42.2 +OPM_VERSION ?= v1.72.0 # Image URL to use all building/pushing image targets IMG ?= $(IMAGE_TAG_BASE):$(IMAGE_TAG_VERSION) # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION ?= $(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') - # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin @@ -127,8 +126,8 @@ endif SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec -# Source packages outside of tests -PKGS := $(shell go list ./... | grep -vE 'github.com/redhat-developer/rhdh-operator/(tests/|api/v1alpha([1-2]))') +# Source packages outside of tests and unserved API versions (v1alpha1-3) +PKGS := $(shell go list ./... | grep -vE 'github.com/redhat-developer/rhdh-operator/(tests/|api/v1alpha[1-3]$$)') .PHONY: all all: build @@ -165,7 +164,7 @@ fmt: goimports ## Format the code using goimports find . -not -path '*/\.*' -name '*.go' -exec $(GOIMPORTS) -w {} \; .PHONY: test -test: manifests generate fmt vet envtest $(LOCALBIN) ## Run tests. We need LOCALBIN=$(LOCALBIN) to get correct default-config path +test: manifests generate fmt vet setup-envtest $(LOCALBIN) ## Run tests. We need LOCALBIN=$(LOCALBIN) to get correct default-config path mkdir -p $(LOCALBIN)/default-config && rm -fr $(LOCALBIN)/default-config/* && cp -r config/profile/$(PROFILE)/default-config/* $(LOCALBIN)/default-config mkdir -p $(LOCALBIN)/plugin-deps && rm -fr $(LOCALBIN)/plugin-deps/* && cp -r config/profile/$(PROFILE)/plugin-deps/* $(LOCALBIN)/plugin-deps 2>/dev/null || : LOCALBIN=$(LOCALBIN) KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(PKGS) -coverprofile cover.out @@ -201,7 +200,7 @@ test-e2e-upgrade: ginkgo ## Run end-to-end tests dedicated to the operator upgra .PHONY: gosec gosec: addgosec ## run the gosec scanner for non-test files in this repo # we let the report content trigger a failure using the GitHub Security features. - $(GOSEC) -no-fail -fmt $(GOSEC_FMT) -out $(GOSEC_OUTPUT_FILE) ./... + $(GOSEC) -no-fail -exclude-generated -fmt $(GOSEC_FMT) -out $(GOSEC_OUTPUT_FILE) ./... .PHONY: lint lint: golangci-lint ## Run golangci-lint linter & yamllint @@ -211,6 +210,10 @@ lint: golangci-lint ## Run golangci-lint linter & yamllint lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes $(GOLANGCI_LINT) run --fix --timeout 15m +.PHONY: lint-config +lint-config: golangci-lint ## Verify golangci-lint linter configuration + $(GOLANGCI_LINT) config verify + .PHONY: vet vet: ## Run go vet against code. go vet ./... @@ -464,22 +467,22 @@ $(LOCALBIN): ## Tool Binaries KUBECTL ?= kubectl -KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION) -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION) -ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) -GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) -GOIMPORTS ?= $(LOCALBIN)/goimports-$(GOIMPORTS_VERSION) -GOSEC ?= $(LOCALBIN)/gosec-$(GOSEC_VERSION) -GINKGO ?= $(LOCALBIN)/ginkgo-$(GINKGO_VERSION) +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +GOIMPORTS ?= $(LOCALBIN)/goimports +GOSEC ?= $(LOCALBIN)/gosec +GINKGO ?= $(LOCALBIN)/ginkgo ## Tool Versions -KUSTOMIZE_VERSION ?= v5.4.2 -CONTROLLER_TOOLS_VERSION ?= v0.20.0 +KUSTOMIZE_VERSION ?= v5.8.1 +CONTROLLER_TOOLS_VERSION ?= v0.21.0 ENVTEST_VERSION ?= $(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') -GOLANGCI_LINT_VERSION ?= v2.6.2 -GOIMPORTS_VERSION ?= v0.16.1 -GOSEC_VERSION ?= v2.22.8 -GINKGO_VERSION ?= v2.22.2 +GOLANGCI_LINT_VERSION ?= v2.12.2 +GOIMPORTS_VERSION ?= v0.46.0 +GOSEC_VERSION ?= v2.27.1 +GINKGO_VERSION ?= v2.28.1 ## Gosec options - default format is sarif so we can integrate with Github code scanning GOSEC_FMT ?= sarif # for other options, see https://github.com/securego/gosec#output-formats @@ -495,6 +498,14 @@ controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessar $(CONTROLLER_GEN): $(LOCALBIN) $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) +.PHONY: setup-envtest +setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory. + @echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..." + @$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \ + echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \ + exit 1; \ + } + .PHONY: envtest envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. $(ENVTEST): $(LOCALBIN) @@ -503,62 +514,68 @@ $(ENVTEST): $(LOCALBIN) .PHONY: golangci-lint golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) .PHONY: goimports goimports: $(GOIMPORTS) ## Download goimports locally if necessary. $(GOIMPORTS): $(LOCALBIN) - $(call go-install-tool,$(GOIMPORTS),golang.org/x/tools/cmd/goimports,${GOIMPORTS_VERSION}) + $(call go-install-tool,$(GOIMPORTS),golang.org/x/tools/cmd/goimports,$(GOIMPORTS_VERSION)) .PHONY: addgosec addgosec: $(GOSEC) ## Download gosec locally if necessary. $(GOSEC): $(LOCALBIN) - $(call go-install-tool,$(GOSEC),github.com/securego/gosec/v2/cmd/gosec,${GOSEC_VERSION}) + $(call go-install-tool,$(GOSEC),github.com/securego/gosec/v2/cmd/gosec,$(GOSEC_VERSION)) .PHONY: ginkgo ginkgo: $(GINKGO) ## Download Ginkgo locally if necessary. $(GINKGO): $(LOCALBIN) - $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo,${GINKGO_VERSION}) + $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo,$(GINKGO_VERSION)) # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary (ideally with version) # $2 - package url which can be installed # $3 - specific version of package define go-install-tool -@[ -f $(1) ] || { \ +@[ -f "$(1)-$(3)" ] || { \ set -e; \ package=$(2)@$(3) ;\ echo "Downloading $${package}" ;\ +rm -f $(1) || true ;\ GOBIN=$(LOCALBIN) go install $${package} ;\ -mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ -} +mv $(1) $(1)-$(3) ;\ +} ;\ +ln -sf $(1)-$(3) $(1) endef .PHONY: operator-sdk -OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk-$(OPERATOR_SDK_VERSION) +OPERATOR_SDK_WITH_VER ?= $(LOCALBIN)/operator-sdk-$(OPERATOR_SDK_VERSION) +OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk operator-sdk: ## Download operator-sdk locally if necessary. -ifeq (,$(wildcard $(OPERATOR_SDK))) +ifeq (,$(wildcard $(OPERATOR_SDK_WITH_VER))) @{ \ set -e ;\ - mkdir -p $(dir $(OPERATOR_SDK)) ;\ + mkdir -p $(dir $(OPERATOR_SDK_WITH_VER)) ;\ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ - chmod +x $(OPERATOR_SDK) ;\ + curl -sSLo $(OPERATOR_SDK_WITH_VER) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ + chmod +x $(OPERATOR_SDK_WITH_VER) ;\ } endif + ln -sf $(OPERATOR_SDK_WITH_VER) $(OPERATOR_SDK) .PHONY: opm -OPM ?= $(LOCALBIN)/opm-$(OPM_VERSION) +OPM_WITH_VER ?= $(LOCALBIN)/opm-$(OPM_VERSION) +OPM ?= $(LOCALBIN)/opm opm: ## Download opm locally if necessary. -ifeq (,$(wildcard $(OPM))) +ifeq (,$(wildcard $(OPM_WITH_VER))) @{ \ set -e ;\ - mkdir -p $(dir $(OPM)) ;\ + mkdir -p $(dir $(OPM_WITH_VER)) ;\ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$${OS}-$${ARCH}-opm ;\ - chmod +x $(OPM) ;\ + curl -sSLo $(OPM_WITH_VER) https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM_WITH_VER) ;\ } endif + ln -sf $(OPM_WITH_VER) $(OPM) ##@ Misc. diff --git a/api/v1alpha1/backstage_types.go b/api/v1alpha1/backstage_types.go index f6c831fb9..e895e41f3 100644 --- a/api/v1alpha1/backstage_types.go +++ b/api/v1alpha1/backstage_types.go @@ -39,7 +39,7 @@ type RuntimeConfig struct { type Database struct { // Control the creation of a local PostgreSQL DB. Set to false if using for example an external Database for Backstage. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true EnableLocalDb *bool `json:"enableLocalDb,omitempty"` // Name of the secret for database authentication. Optional. @@ -85,7 +85,7 @@ type Application struct { // Number of desired replicas to set in the Backstage Deployment. // Defaults to 1. // +optional - //+kubebuilder:default=1 + // +kubebuilder:default=1 Replicas *int32 `json:"replicas,omitempty"` // Custom image to use in all containers (including Init Containers). @@ -156,7 +156,7 @@ type ExtraEnvs struct { type ObjectKeyRef struct { // Name of the object // We support only ConfigMaps and Secrets. - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -166,11 +166,11 @@ type ObjectKeyRef struct { type Env struct { // Name of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Value of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Value string `json:"value"` } @@ -181,10 +181,10 @@ type BackstageStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -//+kubebuilder:unservedversion -//+kubebuilder:deprecatedversion:warning="v1alpha1 is not served" -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:unservedversion +// +kubebuilder:deprecatedversion:warning="v1alpha1 is not served" +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +operator-sdk:csv:customresourcedefinitions:displayName="Red Hat Developer Hub" // Backstage is the Schema for the Red Hat Developer Hub backstages API. @@ -199,7 +199,7 @@ type Backstage struct { Status BackstageStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // BackstageList contains a list of Backstage type BackstageList struct { @@ -213,7 +213,7 @@ type BackstageList struct { type Route struct { // Control the creation of a Route on OpenShift. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true Enabled *bool `json:"enabled,omitempty"` // Host is an alias/DNS that points to the service. Optional. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ca4d7a346..9b7acdd38 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha2/backstage_types.go b/api/v1alpha2/backstage_types.go index 20412c3ef..1ac1b5869 100644 --- a/api/v1alpha2/backstage_types.go +++ b/api/v1alpha2/backstage_types.go @@ -52,7 +52,7 @@ type RuntimeConfig struct { type Database struct { // Control the creation of a local PostgreSQL DB. Set to false if using for example an external Database for Backstage. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true EnableLocalDb *bool `json:"enableLocalDb,omitempty"` // Name of the secret for database authentication. Optional. @@ -98,7 +98,7 @@ type Application struct { // Number of desired replicas to set in the Backstage Deployment. // Defaults to 1. // +optional - //+kubebuilder:default=1 + // +kubebuilder:default=1 Replicas *int32 `json:"replicas,omitempty"` // Custom image to use in all containers (including Init Containers). @@ -169,7 +169,7 @@ type ExtraEnvs struct { type ObjectKeyRef struct { // Name of the object // We support only ConfigMaps and Secrets. - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -179,11 +179,11 @@ type ObjectKeyRef struct { type Env struct { // Name of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Value of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Value string `json:"value"` } @@ -194,10 +194,10 @@ type BackstageStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -//+kubebuilder:unservedversion -//+kubebuilder:deprecatedversion:warning="v1alpha2 is not served" -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:unservedversion +// +kubebuilder:deprecatedversion:warning="v1alpha2 is not served" +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +operator-sdk:csv:customresourcedefinitions:displayName="Red Hat Developer Hub" // Backstage is the Schema for the Red Hat Developer Hub backstages API. @@ -212,7 +212,7 @@ type Backstage struct { Status BackstageStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // BackstageList contains a list of Backstage type BackstageList struct { @@ -226,7 +226,7 @@ type BackstageList struct { type Route struct { // Control the creation of a Route on OpenShift. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true Enabled *bool `json:"enabled,omitempty"` // Host is an alias/DNS that points to the service. Optional. diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 1357c08bd..c3ecd6b25 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha2 import ( - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha3/backstage_types.go b/api/v1alpha3/backstage_types.go index 454e88943..52fdcc30a 100644 --- a/api/v1alpha3/backstage_types.go +++ b/api/v1alpha3/backstage_types.go @@ -53,7 +53,7 @@ type RuntimeConfig struct { type Database struct { // Control the creation of a local PostgreSQL DB. Set to false if using for example an external Database for Backstage. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true EnableLocalDb *bool `json:"enableLocalDb,omitempty"` // Name of the secret for database authentication. Optional. @@ -99,7 +99,7 @@ type Application struct { // Number of desired replicas to set in the Backstage Deployment. // Defaults to 1. // +optional - //+kubebuilder:default=1 + // +kubebuilder:default=1 Replicas *int32 `json:"replicas,omitempty"` // Custom image to use in all containers (including Init Containers). @@ -175,7 +175,7 @@ type ExtraEnvs struct { type EnvObjectRef struct { // Name of the object // We support only ConfigMaps and Secrets. - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -186,7 +186,7 @@ type EnvObjectRef struct { type FileObjectRef struct { // Name of the object // Supported ConfigMaps and Secrets - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -200,7 +200,7 @@ type FileObjectRef struct { type PvcRef struct { // Name of the object - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Path to mount PVC. If not specified default-path/Name will be used @@ -210,11 +210,11 @@ type PvcRef struct { type Env struct { // Name of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Value of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Value string `json:"value"` } @@ -225,10 +225,10 @@ type BackstageStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -//+kubebuilder:unservedversion -//+kubebuilder:deprecatedversion:warning="v1alpha3 is not served" -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:unservedversion +// +kubebuilder:deprecatedversion:warning="v1alpha3 is not served" +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +operator-sdk:csv:customresourcedefinitions:displayName="Red Hat Developer Hub" // Backstage is the Schema for the Red Hat Developer Hub backstages API. @@ -243,7 +243,7 @@ type Backstage struct { Status BackstageStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // BackstageList contains a list of Backstage type BackstageList struct { @@ -257,7 +257,7 @@ type BackstageList struct { type Route struct { // Control the creation of a Route on OpenShift. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true Enabled *bool `json:"enabled,omitempty"` // Host is an alias/DNS that points to the service. Optional. diff --git a/api/v1alpha3/zz_generated.deepcopy.go b/api/v1alpha3/zz_generated.deepcopy.go index 6613a8896..b315b789e 100644 --- a/api/v1alpha3/zz_generated.deepcopy.go +++ b/api/v1alpha3/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha3 import ( - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha4/backstage_types.go b/api/v1alpha4/backstage_types.go index aae700453..4ee0aff55 100644 --- a/api/v1alpha4/backstage_types.go +++ b/api/v1alpha4/backstage_types.go @@ -58,7 +58,7 @@ type RuntimeConfig struct { type Database struct { // Control the creation of a local PostgreSQL DB. Set to false if using for example an external Database for Backstage. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true EnableLocalDb *bool `json:"enableLocalDb,omitempty"` // Name of the secret for database authentication. Optional. @@ -111,7 +111,7 @@ type Application struct { // Number of desired replicas to set in the Backstage Deployment. // Defaults to 1. // +optional - //+kubebuilder:default=1 + // +kubebuilder:default=1 Replicas *int32 `json:"replicas,omitempty"` // Custom image to use in all containers (including Init Containers). @@ -187,7 +187,7 @@ type ExtraEnvs struct { type EnvObjectRef struct { // Name of the object // We support only ConfigMaps and Secrets. - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -205,7 +205,7 @@ type EnvObjectRef struct { type FileObjectRef struct { // Name of the object // Supported ConfigMaps and Secrets - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -226,7 +226,7 @@ type FileObjectRef struct { type PvcRef struct { // Name of the object - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Path to mount PVC. If not specified default-path/Name will be used @@ -243,11 +243,11 @@ type PvcRef struct { type Env struct { // Name of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Value of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Value string `json:"value"` // If set, the env variable will be injected only in the specified containers, otherwise in backstage container only. @@ -265,8 +265,8 @@ type BackstageStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +operator-sdk:csv:customresourcedefinitions:displayName="Red Hat Developer Hub" // Backstage is the Schema for the Red Hat Developer Hub backstages API. @@ -281,7 +281,7 @@ type Backstage struct { Status BackstageStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // BackstageList contains a list of Backstage type BackstageList struct { @@ -295,7 +295,7 @@ type BackstageList struct { type Route struct { // Control the creation of a Route on OpenShift. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true Enabled *bool `json:"enabled,omitempty"` // Host is an alias/DNS that points to the service. Optional. diff --git a/api/v1alpha4/zz_generated.deepcopy.go b/api/v1alpha4/zz_generated.deepcopy.go index 9c86c46ee..c9c126a0f 100644 --- a/api/v1alpha4/zz_generated.deepcopy.go +++ b/api/v1alpha4/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha4 import ( - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha5/backstage_types.go b/api/v1alpha5/backstage_types.go index 9287b4fe8..d66ed7977 100644 --- a/api/v1alpha5/backstage_types.go +++ b/api/v1alpha5/backstage_types.go @@ -76,7 +76,7 @@ type RuntimeConfig struct { type Database struct { // Control the creation of a local PostgreSQL DB. Set to false if using for example an external Database for Backstage. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true EnableLocalDb *bool `json:"enableLocalDb,omitempty"` // Name of the secret for database authentication. Optional. @@ -190,7 +190,7 @@ type ExtraEnvs struct { type EnvObjectRef struct { // Name of the object // We support only ConfigMaps and Secrets. - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -208,7 +208,7 @@ type EnvObjectRef struct { type FileObjectRef struct { // Name of the object // Supported ConfigMaps and Secrets - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Key in the object @@ -229,7 +229,7 @@ type FileObjectRef struct { type PvcRef struct { // Name of the object - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Path to mount PVC. If not specified default-path/Name will be used @@ -246,11 +246,11 @@ type PvcRef struct { type Env struct { // Name of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Name string `json:"name"` // Value of the environment variable - //+kubebuilder:validation:Required + // +kubebuilder:validation:Required Value string `json:"value"` // If set, the env variable will be injected only in the specified containers, otherwise in backstage container only. @@ -268,9 +268,9 @@ type BackstageStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status -//+kubebuilder:storageversion +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:storageversion // +operator-sdk:csv:customresourcedefinitions:displayName="Red Hat Developer Hub" // Backstage is the Schema for the Red Hat Developer Hub backstages API. @@ -285,7 +285,7 @@ type Backstage struct { Status BackstageStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // BackstageList contains a list of Backstage type BackstageList struct { @@ -299,7 +299,7 @@ type BackstageList struct { type Route struct { // Control the creation of a Route on OpenShift. // +optional - //+kubebuilder:default=true + // +kubebuilder:default=true Enabled *bool `json:"enabled,omitempty"` // Host is an alias/DNS that points to the service. Optional. diff --git a/api/v1alpha5/zz_generated.deepcopy.go b/api/v1alpha5/zz_generated.deepcopy.go index 1ee510162..0e0086d44 100644 --- a/api/v1alpha5/zz_generated.deepcopy.go +++ b/api/v1alpha5/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha5 import ( - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/bundle/backstage.io/bundle.Dockerfile b/bundle/backstage.io/bundle.Dockerfile index 7910aa207..897cb0274 100644 --- a/bundle/backstage.io/bundle.Dockerfile +++ b/bundle/backstage.io/bundle.Dockerfile @@ -7,7 +7,7 @@ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=backstage-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha LABEL operators.operatorframework.io.bundle.channel.default.v1=alpha -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.37.0 +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.2 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 diff --git a/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml index 5a709d967..a069bc880 100644 --- a/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml @@ -25,9 +25,9 @@ metadata: } } ] - createdAt: "2026-05-27T12:26:15Z" + createdAt: "2026-06-18T10:56:32Z" description: Backstage Operator - operators.operatorframework.io/builder: operator-sdk-v1.37.0 + operators.operatorframework.io/builder: operator-sdk-v1.42.2 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 labels: operatorframework.io/arch.amd64: supported @@ -211,9 +211,8 @@ spec: containers: - args: - --health-probe-bind-address=:8081 - - --metrics-bind-address=:8443 - - --metrics-secure=true - --leader-elect + - --metrics-bind-address=:8443 command: - /manager image: quay.io/rhdh-community/operator:0.11.0 diff --git a/bundle/backstage.io/manifests/rhdh.redhat.com_backstages.yaml b/bundle/backstage.io/manifests/rhdh.redhat.com_backstages.yaml index 271d6e40b..372b4ca38 100644 --- a/bundle/backstage.io/manifests/rhdh.redhat.com_backstages.yaml +++ b/bundle/backstage.io/manifests/rhdh.redhat.com_backstages.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.21.0 creationTimestamp: null name: backstages.rhdh.redhat.com spec: diff --git a/bundle/backstage.io/metadata/annotations.yaml b/bundle/backstage.io/metadata/annotations.yaml index afbeaa545..4fb3e43b5 100644 --- a/bundle/backstage.io/metadata/annotations.yaml +++ b/bundle/backstage.io/metadata/annotations.yaml @@ -6,7 +6,7 @@ annotations: operators.operatorframework.io.bundle.package.v1: backstage-operator operators.operatorframework.io.bundle.channels.v1: alpha operators.operatorframework.io.bundle.channel.default.v1: alpha - operators.operatorframework.io.metrics.builder: operator-sdk-v1.37.0 + operators.operatorframework.io.metrics.builder: operator-sdk-v1.42.2 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 diff --git a/bundle/rhdh/bundle.Dockerfile b/bundle/rhdh/bundle.Dockerfile index e695be461..2ce4cde43 100644 --- a/bundle/rhdh/bundle.Dockerfile +++ b/bundle/rhdh/bundle.Dockerfile @@ -7,7 +7,7 @@ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=rhdh LABEL operators.operatorframework.io.bundle.channels.v1=fast,fast-${CI_X_VERSION}.${CI_Y_VERSION} LABEL operators.operatorframework.io.bundle.channel.default.v1=fast -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.37.0 +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.2 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 diff --git a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml index c0e6c684f..1165d3338 100644 --- a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml @@ -29,7 +29,7 @@ metadata: categories: Developer Tools certified: "true" containerImage: registry.redhat.io/rhdh/rhdh-rhel9-operator:1.11 - createdAt: "2026-06-12T18:42:24Z" + createdAt: "2026-06-18T10:56:33Z" description: Red Hat Developer Hub is a Red Hat supported version of Backstage. It comes with pre-built plug-ins and configuration settings, supports use of an external database, and can help streamline the process of setting up a self-managed @@ -46,7 +46,7 @@ metadata: features.operators.openshift.io/token-auth-gcp: "false" operatorframework.io/suggested-namespace: rhdh-operator operators.openshift.io/valid-subscription: '["Red Hat Developer Hub"]' - operators.operatorframework.io/builder: operator-sdk-v1.37.0 + operators.operatorframework.io/builder: operator-sdk-v1.42.2 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 repository: https://gitlab.cee.redhat.com/rhidp/rhdh/ skipRange: '>=1.0.0 <1.11.0' @@ -303,9 +303,8 @@ spec: containers: - args: - --health-probe-bind-address=:8081 - - --metrics-bind-address=:8443 - - --metrics-secure=true - --leader-elect + - --metrics-bind-address=:8443 command: - /manager env: diff --git a/bundle/rhdh/manifests/rhdh.redhat.com_backstages.yaml b/bundle/rhdh/manifests/rhdh.redhat.com_backstages.yaml index 271d6e40b..372b4ca38 100644 --- a/bundle/rhdh/manifests/rhdh.redhat.com_backstages.yaml +++ b/bundle/rhdh/manifests/rhdh.redhat.com_backstages.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.21.0 creationTimestamp: null name: backstages.rhdh.redhat.com spec: diff --git a/bundle/rhdh/metadata/annotations.yaml b/bundle/rhdh/metadata/annotations.yaml index d8ebbe608..10e554c0f 100644 --- a/bundle/rhdh/metadata/annotations.yaml +++ b/bundle/rhdh/metadata/annotations.yaml @@ -6,7 +6,7 @@ annotations: operators.operatorframework.io.bundle.package.v1: rhdh operators.operatorframework.io.bundle.channels.v1: fast,fast-${CI_X_VERSION}.${CI_Y_VERSION} operators.operatorframework.io.bundle.channel.default.v1: fast - operators.operatorframework.io.metrics.builder: operator-sdk-v1.37.0 + operators.operatorframework.io.metrics.builder: operator-sdk-v1.42.2 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 diff --git a/cmd/main.go b/cmd/main.go index 0ff204f89..20db65561 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,10 +4,12 @@ import ( "crypto/tls" "flag" "os" + "path/filepath" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" corev1 "k8s.io/api/core/v1" @@ -24,12 +26,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + bsv1 "github.com/redhat-developer/rhdh-operator/api/v1alpha5" "github.com/redhat-developer/rhdh-operator/internal/controller" openshift "github.com/openshift/api/route/v1" - //+kubebuilder:scaffold:imports + // +kubebuilder:scaffold:imports ) var ( @@ -45,7 +48,7 @@ func init() { utilruntime.Must(openshift.Install(scheme)) utilruntime.Must(monitoringv1.AddToScheme(scheme)) - //+kubebuilder:scaffold:scheme + // +kubebuilder:scaffold:scheme } func main() { @@ -54,17 +57,29 @@ func main() { var probeAddr string var secureMetrics bool var enableHTTP2 bool + var metricsCertPath, metricsCertName, metricsCertKey string + var webhookCertPath, webhookCertName, webhookCertKey string var enableCacheLabelFilter bool - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", - "The address the metrics endpoint binds to. Use 0 to disable the metrics service.") + + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&secureMetrics, "metrics-secure", false, - "If set, the metrics endpoint is served securely over HTTPS and requires authentication and authorization.") + flag.BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS and requires authentication and authorization. "+ + "Use --metrics-secure=false to use HTTP instead.") flag.BoolVar(&enableHTTP2, "enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers") + flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") + flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") + flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") + flag.StringVar(&metricsCertPath, "metrics-cert-path", "", + "The directory that contains the metrics server certificate.") + flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", + "The name of the metrics server certificate file.") + flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") flag.BoolVar(&enableCacheLabelFilter, "enable-cache-label-filter", os.Getenv("ENABLE_CACHE_LABEL_FILTER") == "true", "If set, the cache will only store Secrets and ConfigMaps with the label 'rhdh.redhat.com/external-config=true'. This reduces memory consumption. Can also be set via ENABLE_CACHE_LABEL_FILTER env var.") @@ -96,7 +111,36 @@ func main() { tlsOpts = append(tlsOpts, disableHTTP2) } - metricsServerOpts := metricsserver.Options{ + // Create watchers for metrics and webhooks certificates + var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher + + // Initial webhook TLS options + webhookTLSOpts := tlsOpts + + if len(webhookCertPath) > 0 { + setupLog.Info("Initializing webhook certificate watcher using provided certificates", + "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) + + var err error + webhookCertWatcher, err = certwatcher.New( + filepath.Join(webhookCertPath, webhookCertName), + filepath.Join(webhookCertPath, webhookCertKey), + ) + if err != nil { + setupLog.Error(err, "Failed to initialize webhook certificate watcher") + os.Exit(1) + } + + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { + config.GetCertificate = webhookCertWatcher.GetCertificate + }) + } + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ BindAddress: metricsAddr, SecureServing: secureMetrics, TLSOpts: tlsOpts, @@ -105,17 +149,44 @@ func main() { // FilterProvider is used to protect the metrics endpoint with authn/authz. // These configurations ensure that only authorized users and service accounts // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/metrics/filters#WithAuthenticationAndAuthorization - metricsServerOpts.FilterProvider = filters.WithAuthenticationAndAuthorization + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization } webhookServer := webhook.NewServer(webhook.Options{ - TLSOpts: tlsOpts, + TLSOpts: webhookTLSOpts, }) + // If the certificate is not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + // + // TODO(user): If you enable certManager, uncomment the following lines: + // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates + // managed by cert-manager for the metrics server. + // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. + if len(metricsCertPath) > 0 { + setupLog.Info("Initializing metrics certificate watcher using provided certificates", + "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) + + var err error + metricsCertWatcher, err = certwatcher.New( + filepath.Join(metricsCertPath, metricsCertName), + filepath.Join(metricsCertPath, metricsCertKey), + ) + if err != nil { + setupLog.Error(err, "Failed to initialize metrics certificate watcher") + os.Exit(1) + } + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { + config.GetCertificate = metricsCertWatcher.GetCertificate + }) + } + mgrOpts := ctrl.Options{ Scheme: scheme, - Metrics: metricsServerOpts, + Metrics: metricsServerOptions, WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, @@ -178,7 +249,23 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Backstage") os.Exit(1) } - //+kubebuilder:scaffold:builder + // +kubebuilder:scaffold:builder + + if metricsCertWatcher != nil { + setupLog.Info("Adding metrics certificate watcher to manager") + if err := mgr.Add(metricsCertWatcher); err != nil { + setupLog.Error(err, "unable to add metrics certificate watcher to manager") + os.Exit(1) + } + } + + if webhookCertWatcher != nil { + setupLog.Info("Adding webhook certificate watcher to manager") + if err := mgr.Add(webhookCertWatcher); err != nil { + setupLog.Error(err, "unable to add webhook certificate watcher to manager") + os.Exit(1) + } + } if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") diff --git a/config/crd/bases/rhdh.redhat.com_backstages.yaml b/config/crd/bases/rhdh.redhat.com_backstages.yaml index 109073c27..1d30468b9 100644 --- a/config/crd/bases/rhdh.redhat.com_backstages.yaml +++ b/config/crd/bases/rhdh.redhat.com_backstages.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: backstages.rhdh.redhat.com spec: group: rhdh.redhat.com diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 104e55f62..4d391c457 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -10,13 +10,7 @@ patches: # patches here are for enabling the conversion webhook for each CRD #+kubebuilder:scaffold:crdkustomizewebhookpatch -# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. -# patches here are for enabling the CA injection for each CRD -#- path: patches/cainjection_in_immiches.yaml -#+kubebuilder:scaffold:crdkustomizecainjectionpatch - # [WEBHOOK] To enable webhook, uncomment the following section # the following config is for teaching kustomize how to do kustomization for CRDs. - #configurations: #- kustomizeconfig.yaml diff --git a/config/manager/cert_metrics_manager_patch.yaml b/config/manager/cert_metrics_manager_patch.yaml new file mode 100644 index 000000000..d97501553 --- /dev/null +++ b/config/manager/cert_metrics_manager_patch.yaml @@ -0,0 +1,30 @@ +# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs. + +# Add the volumeMount for the metrics-server certs +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + mountPath: /tmp/k8s-metrics-server/metrics-certs + name: metrics-certs + readOnly: true + +# Add the --metrics-cert-path argument for the metrics server +- op: add + path: /spec/template/spec/containers/0/args/- + value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs + +# Add the metrics-server certs volume configuration +- op: add + path: /spec/template/spec/volumes/- + value: + name: metrics-certs + secret: + secretName: metrics-server-cert + optional: false + items: + - key: ca.crt + path: ca.crt + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key diff --git a/config/manager/deployment.yaml b/config/manager/deployment.yaml index 85ec0ec4f..1ca60434c 100644 --- a/config/manager/deployment.yaml +++ b/config/manager/deployment.yaml @@ -52,8 +52,6 @@ spec: - /manager args: - --health-probe-bind-address=:8081 - - --metrics-bind-address=:8443 - - --metrics-secure=true - --leader-elect image: controller:latest name: manager diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 0c00d6fb7..768d9e2df 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -3,3 +3,211 @@ kind: Kustomization resources: - deployment.yaml + # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in + # crd/kustomization.yaml + #- ../webhook + # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. + #- ../certmanager + # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. + #- ../prometheus + # [METRICS] Expose the controller manager metrics service. +- metrics_service.yaml +# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. +# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics. +# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will +# be able to communicate with the Webhook Server. +# TODO(asoro): NetworkPolicies for Operator disabled for now due to https://issues.redhat.com/browse/RHIDP-8734. +# Support should be added as part of https://issues.redhat.com/browse/RHDHPLAN-351 +#- ../network-policy + +patches: +# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. +# More info: https://book.kubebuilder.io/reference/metrics +- path: manager_metrics_patch.yaml + target: + kind: Deployment + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- path: manager_webhook_patch.yaml +# target: +# kind: Deployment + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +# Uncomment the following replacements to add the cert-manager CA injection annotations +#replacements: +# - source: # Uncomment the following block to enable certificates for metrics +# kind: Service +# version: v1 +# name: controller-manager-metrics-service +# fieldPath: metadata.name +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: metrics-certs +# fieldPaths: +# - spec.dnsNames.0 +# - spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor +# kind: ServiceMonitor +# group: monitoring.coreos.com +# version: v1 +# name: controller-manager-metrics-monitor +# fieldPaths: +# - spec.endpoints.0.tlsConfig.serverName +# options: +# delimiter: '.' +# index: 0 +# create: true +# +# - source: +# kind: Service +# version: v1 +# name: controller-manager-metrics-service +# fieldPath: metadata.namespace +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: metrics-certs +# fieldPaths: +# - spec.dnsNames.0 +# - spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true +# - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor +# kind: ServiceMonitor +# group: monitoring.coreos.com +# version: v1 +# name: controller-manager-metrics-monitor +# fieldPaths: +# - spec.endpoints.0.tlsConfig.serverName +# options: +# delimiter: '.' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have any webhook +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.name # Name of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - source: +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.namespace # Namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # This name should match the one in certificate.yaml +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. +# +kubebuilder:scaffold:crdkustomizecainjectionns +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. +# +kubebuilder:scaffold:crdkustomizecainjectionname diff --git a/config/manager/manager_metrics_patch.yaml b/config/manager/manager_metrics_patch.yaml new file mode 100644 index 000000000..8e0cedb9e --- /dev/null +++ b/config/manager/manager_metrics_patch.yaml @@ -0,0 +1,4 @@ +# This patch adds the args to allow exposing the metrics endpoint using HTTPS +- op: add + path: /spec/template/spec/containers/0/args/- + value: --metrics-bind-address=:8443 diff --git a/config/rbac/metrics_service.yaml b/config/manager/metrics_service.yaml similarity index 100% rename from config/rbac/metrics_service.yaml rename to config/manager/metrics_service.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml index b4feb71f6..1b59aac31 100644 --- a/config/prometheus/monitor.yaml +++ b/config/prometheus/monitor.yaml @@ -11,10 +11,19 @@ metadata: spec: endpoints: - path: /metrics - port: https + port: metrics scheme: https bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token tlsConfig: + # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables + # certificate verification. This poses a significant security risk by making the system vulnerable to + # man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between + # Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data, + # compromising the integrity and confidentiality of the information. + # Please use the following options for secure configurations: + # caFile: /etc/metrics-certs/ca.crt + # certFile: /etc/metrics-certs/tls.crt + # keyFile: /etc/metrics-certs/tls.key insecureSkipVerify: true selector: matchLabels: diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index d449df95b..d9ddf1e3d 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -9,8 +9,6 @@ resources: - role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml -# Metrics -- metrics_service.yaml # The following RBAC configurations are used to protect # the metrics endpoint with authn/authz. These configurations # ensure that only authorized users and service accounts diff --git a/dist/backstage.io/install.yaml b/dist/backstage.io/install.yaml index 37ecabc32..801b2148e 100644 --- a/dist/backstage.io/install.yaml +++ b/dist/backstage.io/install.yaml @@ -15,7 +15,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: backstages.rhdh.redhat.com spec: group: rhdh.redhat.com @@ -2792,9 +2792,8 @@ spec: containers: - args: - --health-probe-bind-address=:8081 - - --metrics-bind-address=:8443 - - --metrics-secure=true - --leader-elect + - --metrics-bind-address=:8443 command: - /manager image: quay.io/rhdh-community/operator:0.11.0 diff --git a/dist/rhdh/install.yaml b/dist/rhdh/install.yaml index 6316f189d..dd67cbea5 100644 --- a/dist/rhdh/install.yaml +++ b/dist/rhdh/install.yaml @@ -15,7 +15,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: backstages.rhdh.redhat.com spec: group: rhdh.redhat.com @@ -4019,9 +4019,8 @@ spec: containers: - args: - --health-probe-bind-address=:8081 - - --metrics-bind-address=:8443 - - --metrics-secure=true - --leader-elect + - --metrics-bind-address=:8443 command: - /manager env: diff --git a/go.mod b/go.mod index f9cc596a0..446ae5959 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.26 toolchain go1.26.4 require ( - github.com/onsi/ginkgo/v2 v2.22.2 - github.com/onsi/gomega v1.36.2 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.71.2 github.com/stretchr/testify v1.11.1 @@ -19,22 +19,22 @@ require ( k8s.io/client-go v0.35.4 k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20260617174310-a95e086a2553 - sigs.k8s.io/controller-runtime v0.19.4 + sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/kustomize/kyaml v0.18.1 sigs.k8s.io/yaml v1.6.0 ) require ( cel.dev/expr v0.25.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // 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/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 - github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -58,17 +58,15 @@ require ( github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/moby/spdystream v0.5.1 // indirect @@ -98,9 +96,10 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect + go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.37.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.21.0 // indirect @@ -114,6 +113,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // 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 k8s.io/apiserver v0.35.0 // indirect @@ -121,28 +121,26 @@ require ( k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // 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/v4 v4.7.0 // indirect ) -// Replace directives to pin k8s.io dependencies to v0.31.3 +// Replace directives to pin k8s.io dependencies to v0.33.0 (matching controller-runtime v0.21.0). // This is necessary because: -// 1. k8s.io/client-go v0.31.3 depends on k8s.io/api v0.31.x -// 2. github.com/openshift/api (our direct dependency) requires k8s.io/api v0.34.x -// 3. In k8s.io/api v0.34.x, the v1alpha1 packages for coordination and networking were removed -// 4. k8s.io/client-go/kubernetes/scheme still imports these v1alpha1 packages, causing build failures -// 5. Newer k8s.io/kube-openapi versions use structured-merge-diff v6 (incompatible with v0.31.3) -// and go.yaml.in/yaml paths (causing duplicate yaml.v3 module conflicts) -// 6. Newer github.com/google/gnostic-models and sigs.k8s.io/yaml versions use go.yaml.in/yaml paths +// 1. github.com/openshift/api (our direct dependency) transitively requires k8s.io/api v0.34.x+ +// 2. In k8s.io/api v0.34.x, the v1alpha1 packages for coordination and networking were removed +// 3. k8s.io/client-go v0.33.0's kubernetes/scheme still imports these v1alpha1 packages, causing build failures +// 4. Newer github.com/google/gnostic-models and sigs.k8s.io/yaml versions use go.yaml.in/yaml paths // Without these replace directives, go mod tidy fails with: // "module k8s.io/api@latest found (v0.34.1), but does not contain package k8s.io/api/coordination/v1alpha1" replace ( github.com/google/gnostic-models => github.com/google/gnostic-models v0.6.8 - k8s.io/api => k8s.io/api v0.31.3 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.3 - k8s.io/apimachinery => k8s.io/apimachinery v0.31.3 - k8s.io/apiserver => k8s.io/apiserver v0.31.3 - k8s.io/client-go => k8s.io/client-go v0.31.3 - k8s.io/component-base => k8s.io/component-base v0.31.3 - k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 + k8s.io/api => k8s.io/api v0.33.0 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.0 + k8s.io/apimachinery => k8s.io/apimachinery v0.33.0 + k8s.io/apiserver => k8s.io/apiserver v0.33.0 + k8s.io/client-go => k8s.io/client-go v0.33.0 + k8s.io/component-base => k8s.io/component-base v0.33.0 + k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff sigs.k8s.io/yaml => sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index c229c27e4..2c0aa8bdf 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ 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= 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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 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= @@ -23,14 +23,20 @@ github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 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.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -76,12 +82,14 @@ github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6 github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -92,18 +100,18 @@ 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-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +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/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.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -116,6 +124,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -130,10 +142,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq 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/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9 h1:yb8ul1HPFYhO04yp0D8T/qSySZnKv210f4nE//i/Bdg= github.com/openshift/api v0.0.0-20260511191110-9b69e5fa27e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -164,6 +176,8 @@ github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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= @@ -177,6 +191,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= 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/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= @@ -207,8 +223,8 @@ 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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -220,6 +236,8 @@ golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZC golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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= @@ -275,34 +293,33 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= -k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= -k8s.io/apiextensions-apiserver v0.31.3 h1:+GFGj2qFiU7rGCsA5o+p/rul1OQIq6oYpQw4+u+nciE= -k8s.io/apiextensions-apiserver v0.31.3/go.mod h1:2DSpFhUZZJmn/cr/RweH1cEVVbzFw9YBu4T+U3mf1e4= -k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= -k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.3 h1:+1oHTtCB+OheqFEz375D0IlzHZ5VeQKX1KGXnx+TTuY= -k8s.io/apiserver v0.31.3/go.mod h1:PrxVbebxrxQPFhJk4powDISIROkNMKHibTg9lTRQ0Qg= -k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= -k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= -k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= -k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU= +k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= +k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= +k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= +k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= +k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= +k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= +k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= +k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= +k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= +k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20260617174310-a95e086a2553 h1:hmGqDecjc8d7HVzWzRFl0QD9bYuYKbBEG7t8xwnVxfI= k8s.io/utils v0.0.0-20260617174310-a95e086a2553/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= -sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= 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/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= diff --git a/integration_tests/matchers.go b/integration_tests/matchers.go index cfe56c50a..50febc90a 100644 --- a/integration_tests/matchers.go +++ b/integration_tests/matchers.go @@ -210,6 +210,7 @@ func HaveAppConfigBaseUrl(expected any) types.GomegaMatcher { } return true, nil }).WithTemplate( - "Expected the default app-config ConfigMap:\n{{.FormattedActual}}\n{{.To}} have the default baseUrls and CORS origin meet the expectations:\n{{format .Data 1}}", + "Expected the default app-config ConfigMap:\n{{.FormattedActual}}\n{{.To}} "+ + "have the default baseUrls and CORS origin meet the expectations:\n{{format .Data 1}}", expected) } diff --git a/integration_tests/plugin-deps_test.go b/integration_tests/plugin-deps_test.go index 1e6cfe32a..c3b5d56b0 100644 --- a/integration_tests/plugin-deps_test.go +++ b/integration_tests/plugin-deps_test.go @@ -46,7 +46,7 @@ var _ = When("test plugin deps", func() { dynapluginCm := map[string]string{"dynamic-plugins.yaml": readTestYamlFile("raw-dynaplugins-with-deps.yaml")} - bsRaw := generateConfigMap(ctx, k8sClient, "dynaplugins", ns, dynapluginCm, nil, nil) + bsRaw := generateConfigMap(ctx, k8sClient, "dynaplugins", ns, dynapluginCm, map[string]string{}, map[string]string{}) createAndReconcileBackstage(ctx, ns, api.BackstageSpec{ RawRuntimeConfig: &api.RuntimeConfig{ diff --git a/integration_tests/suite_test.go b/integration_tests/suite_test.go index 1bf08a7cd..b44621aee 100644 --- a/integration_tests/suite_test.go +++ b/integration_tests/suite_test.go @@ -45,7 +45,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" - //+kubebuilder:scaffold:imports + // +kubebuilder:scaffold:imports ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -64,7 +64,7 @@ type TestBackstageReconciler struct { func init() { rand.Seed(time.Now().UnixNano()) - //testOnExistingCluster, _ = strconv.ParseBool(os.Getenv("TEST_ON_EXISTING_CLUSTER")) + // testOnExistingCluster, _ = strconv.ParseBool(os.Getenv("TEST_ON_EXISTING_CLUSTER")) } func TestAPIs(t *testing.T) { @@ -74,16 +74,26 @@ func TestAPIs(t *testing.T) { } var _ = BeforeSuite(func() { - //logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + // logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) logf.SetLogger(zap.New(zap.UseDevMode(true))) By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases"), filepath.Join("..", "config", "crd", "external")}, + CRDDirectoryPaths: []string{ + filepath.Join("..", "config", "crd", "bases"), + filepath.Join("..", "config", "crd", "external"), + }, ErrorIfCRDPathMissing: true, } + // Optionally set the path to the binary assets directory for envtest. + // This is useful when running tests directly (e.g., via an IDE) without + // using Makefile targets that set the KUBEBUILDER_ASSETS environment variable. + if dir := getFirstFoundEnvTestBinaryDir(); dir != "" { + testEnv.BinaryAssetsDirectory = dir + } + testEnv.UseExistingCluster = ptr.To(false) if val, ok := os.LookupEnv("USE_EXISTING_CLUSTER"); ok { boolValue, err := strconv.ParseBool(val) @@ -121,7 +131,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) utilruntime.Must(openshift.Install(scheme.Scheme)) - //+kubebuilder:scaffold:scheme + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).NotTo(HaveOccurred()) @@ -135,6 +145,24 @@ var _ = AfterSuite(func() { Expect(err).NotTo(HaveOccurred()) }) +// getFirstFoundEnvTestBinaryDir locates the first subdirectory under +// ../bin/k8s, streamlining the process of finding envtest binaries similar +// to setting the KUBEBUILDER_ASSETS environment variable. +func getFirstFoundEnvTestBinaryDir() string { + basePath := filepath.Join("..", "bin", "k8s") + entries, err := os.ReadDir(basePath) + if err != nil { + logf.Log.Info("Could not read envtest binary directory", "path", basePath, "error", err) + return "" + } + for _, entry := range entries { + if entry.IsDir() { + return filepath.Join(basePath, entry.Name()) + } + } + return "" +} + var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz0123456789") func randString(n int) string { diff --git a/integration_tests/utils.go b/integration_tests/utils.go index e06955c04..dc05f0e91 100644 --- a/integration_tests/utils.go +++ b/integration_tests/utils.go @@ -7,10 +7,11 @@ import ( "os" "path/filepath" + "k8s.io/apimachinery/pkg/util/yaml" + "github.com/redhat-developer/rhdh-operator/internal/controller" "github.com/redhat-developer/rhdh-operator/pkg/model" "github.com/redhat-developer/rhdh-operator/pkg/utils" - "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -24,7 +25,10 @@ import ( . "github.com/onsi/gomega" ) -func generateConfigMap(ctx context.Context, k8sClient client.Client, name string, namespace string, data, labels map[string]string, annotations map[string]string) string { +func generateConfigMap( + ctx context.Context, k8sClient client.Client, name, namespace string, + data, labels, annotations map[string]string, +) string { Expect(k8sClient.Create(ctx, &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -38,7 +42,10 @@ func generateConfigMap(ctx context.Context, k8sClient client.Client, name string return name } -func generateSecret(ctx context.Context, k8sClient client.Client, name, namespace string, data, labels, annotations map[string]string) string { +func generateSecret( + ctx context.Context, k8sClient client.Client, name, namespace string, + data, labels, annotations map[string]string, +) string { Expect(k8sClient.Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -59,7 +66,9 @@ func readTestYamlFile(name string) string { return string(b) } -func executeRemoteCommand(ctx context.Context, podNamespace, podName, container, command string) (string, string, error) { +func executeRemoteCommand( + ctx context.Context, podNamespace, podName, container, command string, +) (string, string, error) { kubeCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}, @@ -132,7 +141,8 @@ func backstageContainer(pod corev1.PodSpec) corev1.Container { func getBackstagePod(ctx context.Context, k8sClient client.Client, ns, backstageName string) (*corev1.Pod, error) { podList := &corev1.PodList{} - err := k8sClient.List(ctx, podList, client.InNamespace(ns), client.MatchingLabels{model.BackstageAppLabel: utils.BackstageAppLabelValue(backstageName)}) + labels := client.MatchingLabels{model.BackstageAppLabel: utils.BackstageAppLabelValue(backstageName)} + err := k8sClient.List(ctx, podList, client.InNamespace(ns), labels) if err != nil { return nil, err } @@ -143,6 +153,8 @@ func getBackstagePod(ctx context.Context, k8sClient client.Client, ns, backstage return &podList.Items[0], nil } -func backstageDeployment(ctx context.Context, k8sClient client.Client, namespace, backstageName string) (model.Deployable, error) { +func backstageDeployment( + ctx context.Context, k8sClient client.Client, namespace, backstageName string, +) (model.Deployable, error) { return controller.FindDeployment(ctx, k8sClient, namespace, backstageName) } diff --git a/internal/controller/backstage_controller.go b/internal/controller/backstage_controller.go index 1293e0139..4ae61b061 100644 --- a/internal/controller/backstage_controller.go +++ b/internal/controller/backstage_controller.go @@ -43,16 +43,16 @@ type BackstageReconciler struct { Platform platform.Platform } -//+kubebuilder:rbac:groups=rhdh.redhat.com,resources=backstages,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=rhdh.redhat.com,resources=backstages/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=rhdh.redhat.com,resources=backstages/finalizers,verbs=update -//+kubebuilder:rbac:groups="",resources=configmaps;secrets;services;persistentvolumeclaims,verbs=get;watch;create;update;list;delete;patch -//+kubebuilder:rbac:groups="",resources=persistentvolumes,verbs=get;list;watch -//+kubebuilder:rbac:groups="apps",resources=deployments;statefulsets,verbs=get;watch;create;update;list;delete;patch -//+kubebuilder:rbac:groups="route.openshift.io",resources=routes;routes/custom-host,verbs=get;watch;create;update;list;delete;patch -//+kubebuilder:rbac:groups="config.openshift.io",resources=ingresses,verbs=get -//+kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=create;update;patch;delete +// +kubebuilder:rbac:groups=rhdh.redhat.com,resources=backstages,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rhdh.redhat.com,resources=backstages/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=rhdh.redhat.com,resources=backstages/finalizers,verbs=update +// +kubebuilder:rbac:groups="",resources=configmaps;secrets;services;persistentvolumeclaims,verbs=get;watch;create;update;list;delete;patch +// +kubebuilder:rbac:groups="",resources=persistentvolumes,verbs=get;list;watch +// +kubebuilder:rbac:groups="apps",resources=deployments;statefulsets,verbs=get;watch;create;update;list;delete;patch +// +kubebuilder:rbac:groups="route.openshift.io",resources=routes;routes/custom-host,verbs=get;watch;create;update;list;delete;patch +// +kubebuilder:rbac:groups="config.openshift.io",resources=ingresses,verbs=get +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -180,7 +180,8 @@ func (r *BackstageReconciler) applyPayload(ctx context.Context, obj client.Objec func (r *BackstageReconciler) SetupWithManager(mgr ctrl.Manager) error { b := ctrl.NewControllerManagedBy(mgr). - For(&api.Backstage{}) + For(&api.Backstage{}). + Named("backstage") if err := r.addWatchers(b); err != nil { return err diff --git a/internal/controller/deployable_finder.go b/internal/controller/deployable_finder.go index 04cfc08b4..02fe970a7 100644 --- a/internal/controller/deployable_finder.go +++ b/internal/controller/deployable_finder.go @@ -13,14 +13,14 @@ import ( func FindDeployment(ctx context.Context, k8sClient client.Client, namespace, backstageName string) (model.Deployable, error) { nn := client.ObjectKey{Namespace: namespace, Name: model.DeploymentName(backstageName)} deploy := &appsv1.Deployment{} - //lg := log.FromContext(ctx) - //lg.V(1).Info("Looking for Deployment", "namespace", namespace, "name", nn.Name) + // lg := log.FromContext(ctx) + // lg.V(1).Info("Looking for Deployment", "namespace", namespace, "name", nn.Name) err := k8sClient.Get(ctx, nn, deploy) if err == nil { return model.CreateDeployable(deploy) } else if errors.IsNotFound(err) { ss := &appsv1.StatefulSet{} - //lg.V(1).Info("Deployment not found, looking for StatefulSet", "namespace", namespace, "name", nn.Name) + // lg.V(1).Info("Deployment not found, looking for StatefulSet", "namespace", namespace, "name", nn.Name) err = k8sClient.Get(ctx, nn, ss) if err == nil { return model.CreateDeployable(ss) diff --git a/internal/controller/mock_client.go b/internal/controller/mock_client.go index f4acc45be..8f7f43b85 100644 --- a/internal/controller/mock_client.go +++ b/internal/controller/mock_client.go @@ -36,7 +36,7 @@ type NameKind struct { func kind(obj runtime.Object) string { str := reflect.TypeOf(obj).String() return str[strings.LastIndex(str, ".")+1:] - //return reflect.TypeOf(obj).String() + // return reflect.TypeOf(obj).String() } func (m MockClient) Get(_ context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error { diff --git a/internal/controller/watchers.go b/internal/controller/watchers.go index 13a7b9cab..d5f4b6be1 100644 --- a/internal/controller/watchers.go +++ b/internal/controller/watchers.go @@ -59,7 +59,7 @@ func (r *BackstageReconciler) addWatchers(b *builder.Builder) error { builder.WithPredicates(pred, predicate.Funcs{ DeleteFunc: func(e event.DeleteEvent) bool { return true }, UpdateFunc: func(e event.UpdateEvent) bool { return true }, - //CreateFunc: func(e event.CreateEvent) bool { return true }, + // CreateFunc: func(e event.CreateEvent) bool { return true }, }), ). WatchesMetadata( @@ -70,7 +70,7 @@ func (r *BackstageReconciler) addWatchers(b *builder.Builder) error { builder.WithPredicates(pred, predicate.Funcs{ DeleteFunc: func(e event.DeleteEvent) bool { return true }, UpdateFunc: func(e event.UpdateEvent) bool { return true }, - //CreateFunc: func(e event.CreateEvent) bool { return true }, + // CreateFunc: func(e event.CreateEvent) bool { return true }, })) } @@ -133,7 +133,7 @@ func (r *BackstageReconciler) requestByExtConfigLabel(ctx context.Context, objec backstageName := object.GetAnnotations()[model.BackstageNameAnnotation] if backstageName == "" { - //lg.V(1).Info(fmt.Sprintf("warning: %s annotation is not defined for %s, Backstage instances will not be reconciled in this loop", model.BackstageNameAnnotation, object.GetName())) + // lg.V(1).Info(fmt.Sprintf("warning: %s annotation is not defined for %s, Backstage instances will not be reconciled in this loop", model.BackstageNameAnnotation, object.GetName())) return []reconcile.Request{} } @@ -150,11 +150,11 @@ func (r *BackstageReconciler) requestByExtConfigLabel(ctx context.Context, objec return []reconcile.Request{} } - //ec, err := r.preprocessSpec(ctx, backstage) - //if err != nil { - // lg.Error(err, "request by label failed, preprocess Backstage ") - // return []reconcile.Request{} - //} + // ec, err := r.preprocessSpec(ctx, backstage) + // if err != nil { + // lg.Error(err, "request by label failed, preprocess Backstage ") + // return []reconcile.Request{} + // } deploy, err := FindDeployment(ctx, r.Client, object.GetNamespace(), backstage.Name) if err != nil { diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 185a46ff1..d6256c92d 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -80,7 +80,7 @@ spec: requests: storage: 2Gi` - objects, err := ReadYamls([]byte(y), nil /*&corev1.PersistentVolumeClaim{},*/, *util_test_scheme) + objects, err := ReadYamls([]byte(y), nil /* &corev1.PersistentVolumeClaim{}, */, *util_test_scheme) assert.NoError(t, err) @@ -110,18 +110,20 @@ metadata: name: pvc2 data:` - _, err := ReadYamls([]byte(y), nil /*&corev1.PersistentVolumeClaim{},*/, *util_test_scheme) + _, err := ReadYamls([]byte(y), nil /* &corev1.PersistentVolumeClaim{}, */, *util_test_scheme) // Kind not match for the second item, PersistentVolumeClaim expected - // TODO with unstructured change we do not have this error anymore as we do not have expected kind. Should we test it in another way/place? - //assert.EqualError(t, err, "GroupVersionKind not match, found: /v1, Kind=ConfigMap, expected: [/v1, Kind=PersistentVolumeClaim]") + // TODO with unstructured change we do not have this error anymore as we do not have expected kind. + // Should we test it in another way/place? + // assert.EqualError(t, err, "GroupVersionKind not match, found: /v1, Kind=ConfigMap, + // expected: [/v1, Kind=PersistentVolumeClaim]") assert.NoError(t, err) } func TestPlatformPatchMerge(t *testing.T) { // ocp (no patch, so default) - //t.Setenv(PlatformEnvVar, "ocp") + // t.Setenv(PlatformEnvVar, "ocp") obj, err := ReadYamlFiles("testdata/deployment.yaml", *util_test_scheme, "ocp") assert.NoError(t, err) @@ -131,7 +133,7 @@ func TestPlatformPatchMerge(t *testing.T) { assert.Nil(t, depl.Spec.Template.Spec.SecurityContext) // k8s (patched) - //t.Setenv(PlatformEnvVar, "k8s") + // t.Setenv(PlatformEnvVar, "k8s") obj, err = ReadYamlFiles("testdata/deployment.yaml", *util_test_scheme, "k8s") assert.NoError(t, err) diff --git a/tests/e2e/e2e_suite_test.go b/tests/e2e/e2e_suite_test.go index a3e6a05bb..421e2080c 100644 --- a/tests/e2e/e2e_suite_test.go +++ b/tests/e2e/e2e_suite_test.go @@ -53,7 +53,9 @@ func installRhdhOperatorManifest(operatorManifest string) { func installRhdhOperator(flavor string) (podLabel string) { Expect(helper.IsOpenShift()).Should(BeTrue(), "install RHDH script works only on OpenShift clusters!") - cmd := exec.Command(filepath.Join(".rhdh", "scripts", "install-rhdh-catalog-source.sh"), "--"+flavor, "--install-operator", "rhdh") + cmd := exec.Command( + filepath.Join(".rhdh", "scripts", "install-rhdh-catalog-source.sh"), + "--"+flavor, "--install-operator", "rhdh") _, err := helper.Run(cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred()) podLabel = "app=rhdh-operator" @@ -64,7 +66,7 @@ func installRhdhOperatorAirgapped() (podLabel string) { Expect(helper.IsOpenShift()).Should(BeTrue(), "airgap preparation script for RHDH works only on OpenShift clusters!") indexImg, ok := os.LookupEnv("BACKSTAGE_OPERATOR_TESTS_AIRGAP_INDEX_IMAGE") if !ok { - //TODO(rm3l): find a way to pass the right OCP version and arch + // TODO(rm3l): find a way to pass the right OCP version and arch indexImg = "quay.io/rhdh/iib:latest-v4.14-x86_64" } operatorVersion, ok := os.LookupEnv("BACKSTAGE_OPERATOR_TESTS_AIRGAP_OPERATOR_VERSION") @@ -117,20 +119,24 @@ spec: return podLabel } +func isEnvEnabled(env string) bool { + return os.Getenv(env) == "true" +} + func installOperatorWithMakeDeploy(withOlm bool) { img, err := helper.Run(exec.Command("make", "--no-print-directory", "show-img")) ExpectWithOffset(1, err).NotTo(HaveOccurred()) operatorImage := strings.TrimSpace(string(img)) imgArg := fmt.Sprintf("IMG=%s", operatorImage) - if os.Getenv("BACKSTAGE_OPERATOR_TESTS_BUILD_IMAGES") == "true" { + if isEnvEnabled("BACKSTAGE_OPERATOR_TESTS_BUILD_IMAGES") { By("building the manager(Operator) image") cmd := exec.Command("make", "image-build", imgArg) _, err = helper.Run(cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred()) } - if os.Getenv("BACKSTAGE_OPERATOR_TESTS_PUSH_IMAGES") == "true" { + if isEnvEnabled("BACKSTAGE_OPERATOR_TESTS_PUSH_IMAGES") { By("building the manager(Operator) image") cmd := exec.Command("make", "image-push", imgArg) _, err = helper.Run(cmd) @@ -170,7 +176,7 @@ func installOperatorWithMakeDeploy(withOlm bool) { } var _ = SynchronizedBeforeSuite(func() []byte { - //runs *only* on process #1 + // runs *only* on process #1 _start = time.Now() GinkgoWriter.Println("isOpenshift:", helper.IsOpenShift()) @@ -192,15 +198,16 @@ var _ = SynchronizedBeforeSuite(func() []byte { } By("validating that the controller-manager pod is running as expected") - EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, time.Second).WithArguments(managerPodLabel).Should(Succeed()) + EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, time.Second). + WithArguments(managerPodLabel).Should(Succeed()) return nil }, func(_ []byte) { - //runs on *all* processes + // runs on *all* processes }) var _ = SynchronizedAfterSuite(func() { - //runs on *all* processes + // runs on *all* processes }, // the function below *only* on process #1 func() { @@ -318,7 +325,8 @@ func uninstallOperator() { } func uninstallRhdhOperator(withAirgap bool) { - cmd := exec.Command(helper.GetPlatformTool(), "delete", "subscription", "rhdh", "-n", _namespace, "--ignore-not-found=true") + cmd := exec.Command( + helper.GetPlatformTool(), "delete", "subscription", "rhdh", "-n", _namespace, "--ignore-not-found=true") _, err := helper.Run(cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred()) @@ -326,7 +334,9 @@ func uninstallRhdhOperator(withAirgap bool) { if withAirgap { cs = "rhdh-disconnected-install" } - cmd = exec.Command(helper.GetPlatformTool(), "delete", "catalogsource", cs, "-n", "openshift-marketplace", "--ignore-not-found=true") + cmd = exec.Command( + helper.GetPlatformTool(), "delete", "catalogsource", cs, + "-n", "openshift-marketplace", "--ignore-not-found=true") _, err = helper.Run(cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred()) diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 67f12420c..7ed5e5872 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -88,7 +88,8 @@ var _ = Describe("Backstage Operator E2E", func() { return false, fmt.Errorf("invalid json: %q", respBody) } return gjson.Get(respBody, "#").Int() > 0, nil - }).WithMessage("be a valid and non-empty JSON array. This is the response from the 'GET /api/extensions/loaded-plugins' endpoint, using the guest user."), + }).WithMessage("be a valid and non-empty JSON array. " + + "This is the response from the 'GET /api/extensions/loaded-plugins' endpoint, using the guest user."), }, }, }, @@ -108,7 +109,6 @@ var _ = Describe("Backstage Operator E2E", func() { crName: "bs-raw-runtime-config", }, } { - tt := tt When(fmt.Sprintf("applying %s (%s)", tt.name, tt.crFilePath), func() { var crPath string var crLabel string @@ -125,7 +125,7 @@ var _ = Describe("Backstage Operator E2E", func() { It("should handle CR as expected", func() { By("validating that the status of the custom resource created is updated or not", func() { - // [{"lastTransitionTime":"2025-04-09T09:02:06Z","message":"","reason":"Deployed","status":"True","type":"Deployed"}] + // [{"lastTransitionTime":"2025-04-09T09:02:06Z",...,"type":"Deployed"}] Eventually(helper.VerifyBackstageCRStatus, time.Minute, 10*time.Second). WithArguments(ns, tt.crName, ContainSubstring(`"type":"Deployed"`)). Should(Succeed(), func() string { @@ -213,13 +213,14 @@ spec: _, err = helper.Run(cmd) Expect(err).ShouldNot(HaveOccurred()) }) - appUrlProvider = func(g Gomega) (context.CancelFunc, string) { - return nil, "http://" + appUrl + appUrlProvider = func(_ Gomega) (context.CancelFunc, string) { + return func() {}, "http://" + appUrl } } else { // Expose the service and reach localhost:7007 appUrlProvider = func(g Gomega) (context.CancelFunc, string) { - localPort, cancelFunc, err := helper.StartPortForward(context.Background(), fmt.Sprintf("backstage-%s", tt.crName), ns, 80) + localPort, cancelFunc, err := helper.StartPortForward( + context.Background(), fmt.Sprintf("backstage-%s", tt.crName), ns, 80) g.Expect(err).ShouldNot(HaveOccurred()) g.Expect(localPort).ShouldNot(BeZero()) return cancelFunc, fmt.Sprintf("http://127.0.0.1:%d", localPort) @@ -292,7 +293,10 @@ spec: }) }) -func ensureRouteIsReachable(reachabilityTimeoutMinutes time.Duration, ns string, crName string, crLabel string, additionalApiEndpointTests []helper.ApiEndpointTest) { +func ensureRouteIsReachable( + reachabilityTimeoutMinutes time.Duration, ns, crName, crLabel string, + additionalApiEndpointTests []helper.ApiEndpointTest, +) { Eventually(helper.VerifyBackstageRoute, reachabilityTimeoutMinutes, 10*time.Second). WithArguments(ns, crName, additionalApiEndpointTests). Should(Succeed(), func() string { diff --git a/tests/e2e/e2e_upgrade_test.go b/tests/e2e/e2e_upgrade_test.go index b367a956d..3751dc8ce 100644 --- a/tests/e2e/e2e_upgrade_test.go +++ b/tests/e2e/e2e_upgrade_test.go @@ -47,7 +47,8 @@ var _ = Describe("Operator upgrade with existing instances", func() { cmd := exec.Command(helper.GetPlatformTool(), "apply", "-f", fromDeploymentManifest) _, err := helper.Run(cmd) Expect(err).ShouldNot(HaveOccurred()) - EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, time.Second).WithArguments("app=rhdh-operator").Should(Succeed()) + EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, time.Second). + WithArguments("app=rhdh-operator").Should(Succeed()) apiVersion, err := helper.GetBackstageCRDStorageVersion() ExpectWithOffset(1, err).NotTo(HaveOccurred(), "could not determine Backstage CRD storage version") @@ -104,15 +105,17 @@ metadata: } else { installOperatorWithMakeDeploy(false) } - EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, 10*time.Second).WithArguments(managerPodLabel).Should(Succeed()) + EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, 10*time.Second). + WithArguments(managerPodLabel).Should(Succeed()) }) crLabel := fmt.Sprintf("rhdh.redhat.com/app=backstage-%s", crName) - // TODO(rm3l): this might never work because the Deployment may not necessarily change after an upgrade of the Operator - // It might not result in a different replicas if the newer operator did not change anything. - //By("ensuring the current operator eventually reconciled through the creation of a new ReplicaSet of the application") - //Eventually(func(g Gomega) { + // TODO(rm3l): this might never work because the Deployment may not necessarily + // change after an upgrade of the Operator. It might not result in a different replicas + // if the newer operator did not change anything. + // By("ensuring the current operator eventually reconciled through the new ReplicaSet") + // Eventually(func(g Gomega) { // cmd := exec.Command(helper.GetPlatformTool(), "get", // "replicasets", "-l", crLabel, // "-o", "go-template={{ range .items }}{{ if not .metadata.deletionTimestamp }}{{ .metadata.name }}"+ @@ -124,7 +127,7 @@ metadata: // rsNames := helper.GetNonEmptyLines(string(rsOutput)) // g.Expect(len(rsNames)).Should(BeNumerically(">=", 2), // fmt.Sprintf("expected at least 2 Backstage operand ReplicaSets, but got %d", len(rsNames))) - //}, 3*time.Minute, 3*time.Second).Should(Succeed(), fetchOperatorLogs) + // }, 3*time.Minute, 3*time.Second).Should(Succeed(), fetchOperatorLogs) By("checking the status of the existing CR") // [{"lastTransitionTime":"2025-04-09T09:02:06Z","message":"","reason":"Deployed","status":"True","type":"Deployed"}] @@ -149,7 +152,8 @@ metadata: podOutput, err := helper.Run(cmd) g.Expect(err).ShouldNot(HaveOccurred()) podNames := helper.GetNonEmptyLines(string(podOutput)) - g.Expect(podNames).Should(HaveLen(1), fmt.Sprintf("expected 1 Backstage operand pod(s) running, but got %d", len(podNames))) + g.Expect(podNames).Should( + HaveLen(1), fmt.Sprintf("expected 1 Backstage operand pod(s) running, but got %d", len(podNames))) }, 15*time.Minute, 10*time.Second).Should(Succeed(), func() string { return fmt.Sprintf("%s\n---\n%s", fetchOperatorAndOperandLogs(managerPodLabel, ns, crLabel), diff --git a/tests/helper/helper_backstage.go b/tests/helper/helper_backstage.go index 55a581cdd..8afb047ef 100644 --- a/tests/helper/helper_backstage.go +++ b/tests/helper/helper_backstage.go @@ -80,7 +80,8 @@ func GuestAuth(baseUrl string) (string, error) { return "", fmt.Errorf("error while trying to read response body from 'GET %q': %w", url, err) } if resp.StatusCode != 200 { - return "", fmt.Errorf("expected status code 200, but got %d in response to 'GET %q', body: %s", resp.StatusCode, url, string(body)) + return "", fmt.Errorf("expected status code 200, but got %d in response to 'GET %q', body: %s", + resp.StatusCode, url, string(body)) } var authResponse BackstageAuthRefreshResponse err = json.Unmarshal(body, &authResponse) @@ -119,7 +120,8 @@ func VerifyBackstagePodStatus(g Gomega, ns string, crName string, expectedStatus } func VerifyBackstageCRStatus(g Gomega, ns string, crName string, statusMatcher types.GomegaMatcher) { - cmd := exec.Command(GetPlatformTool(), "get", "backstage", crName, "-o", "jsonpath={.status.conditions}", "-n", ns) // #nosec G204 + cmd := exec.Command( + GetPlatformTool(), "get", "backstage", crName, "-o", "jsonpath={.status.conditions}", "-n", ns) // #nosec G204 status, err := Run(cmd) GinkgoWriter.Println(string(status)) g.Expect(err).ShouldNot(HaveOccurred()) @@ -131,7 +133,8 @@ func PatchBackstageCR(ns string, crName string, jsonPatch string, patchType stri if p == "" { p = "strategic" } - _, err := Run(exec.Command(GetPlatformTool(), "-n", ns, "patch", "backstage", crName, "--patch", jsonPatch, "--type="+p)) // #nosec G204 + _, err := Run(exec.Command( + GetPlatformTool(), "-n", ns, "patch", "backstage", crName, "--patch", jsonPatch, "--type="+p)) // #nosec G204 return err } @@ -151,7 +154,8 @@ func GetBackstageRouteHost(ns string, crName string) (string, error) { routeName := model.RouteName(crName) hostBytes, err := Run(exec.Command( - GetPlatformTool(), "get", "route", routeName, "-o", "go-template={{if .spec.host}}{{.spec.host}}{{end}}", "-n", ns)) // #nosec G204 + GetPlatformTool(), "get", "route", routeName, + "-o", "go-template={{if .spec.host}}{{.spec.host}}{{end}}", "-n", ns)) // #nosec G204 if err != nil { return "", fmt.Errorf("unable to determine host for route %s/%s: %w", ns, routeName, err) } @@ -162,7 +166,8 @@ func GetBackstageRouteHost(ns string, crName string) (string, error) { // try with subdomain in case it was set subDomainBytes, err := Run(exec.Command( - GetPlatformTool(), "get", "route", routeName, "-o", "go-template={{if .spec.subdomain}}{{.spec.subdomain}}{{end}}", "-n", ns)) // #nosec G204 + GetPlatformTool(), "get", "route", routeName, + "-o", "go-template={{if .spec.subdomain}}{{.spec.subdomain}}{{end}}", "-n", ns)) // #nosec G204 if err != nil { return "", fmt.Errorf("unable to determine subdomain for route %s/%s: %w", ns, routeName, err) } @@ -170,7 +175,8 @@ func GetBackstageRouteHost(ns string, crName string) (string, error) { if subDomain == "" { return "", nil } - ingressDomainBytes, err := Run(exec.Command(GetPlatformTool(), "get", "ingresses.config/cluster", "-o", "jsonpath={.spec.domain}")) // #nosec G204 + ingressDomainBytes, err := Run(exec.Command( + GetPlatformTool(), "get", "ingresses.config/cluster", "-o", "jsonpath={.spec.domain}")) // #nosec G204 if err != nil { return "", fmt.Errorf("unable to determine ingress sub-domain: %w", err) } @@ -199,7 +205,11 @@ func VerifyBackstageRoute(g Gomega, ns string, crName string, tests []ApiEndpoin VerifyBackstageAppAccess(g, fmt.Sprintf("https://%s", host), tests) } -func VerifyBackstageAppAccessWithUrlProvider(g Gomega, baseUrlProvider func(g Gomega) (context.CancelFunc, string), tests []ApiEndpointTest) { +func VerifyBackstageAppAccessWithUrlProvider( + g Gomega, + baseUrlProvider func(g Gomega) (context.CancelFunc, string), + tests []ApiEndpointTest, +) { cancelFunc, appUrl := baseUrlProvider(g) if cancelFunc != nil { defer cancelFunc() @@ -225,7 +235,8 @@ func VerifyBackstageAppAccess(g Gomega, baseUrl string, tests []ApiEndpointTest) if tt.BearerTokenRetrievalFn != nil { bearerToken, tErr := tt.BearerTokenRetrievalFn(baseUrl) - g.Expect(tErr).ShouldNot(HaveOccurred(), fmt.Sprintf("error while retrieving bearer token, context: %q", tt.Endpoint)) + g.Expect(tErr).ShouldNot(HaveOccurred(), + fmt.Sprintf("error while retrieving bearer token, context: %q", tt.Endpoint)) if bearerToken != "" { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", bearerToken)) } @@ -242,7 +253,8 @@ func VerifyBackstageAppAccess(g Gomega, baseUrl string, tests []ApiEndpointTest) body, rErr := io.ReadAll(resp.Body) g.Expect(rErr).ShouldNot(HaveOccurred(), fmt.Sprintf("error while trying to read response body from 'GET %q'", url)) bodyStr := string(body) - g.Expect(resp.StatusCode).Should(Equal(tt.ExpectedHttpStatusCode), fmt.Sprintf("context: %s\n===Response body===\n%s", tt.Endpoint, bodyStr)) + g.Expect(resp.StatusCode).Should(Equal(tt.ExpectedHttpStatusCode), + fmt.Sprintf("context: %s\n===Response body===\n%s", tt.Endpoint, bodyStr)) if tt.BodyMatcher != nil { g.Expect(bodyStr).Should(tt.BodyMatcher, "context: "+tt.Endpoint) } diff --git a/tests/helper/utils.go b/tests/helper/utils.go index 7e3955cb3..260a8ea47 100644 --- a/tests/helper/utils.go +++ b/tests/helper/utils.go @@ -285,7 +285,12 @@ func IsOpenShift() bool { return _isOpenShift } -func StartPortForward(ctx context.Context, svc string, ns string, svcPort int) (localPort int, cancelFunc context.CancelFunc, err error) { +func StartPortForward( + ctx context.Context, + svc string, + ns string, + svcPort int, +) (localPort int, cancelFunc context.CancelFunc, err error) { ctx, cancel := context.WithCancel(ctx) cmd := exec.CommandContext( ctx,