diff --git a/.github/workflows/crd-diff.yaml b/.github/workflows/crd-diff.yaml index 637fbf821f..3bb66d293a 100644 --- a/.github/workflows/crd-diff.yaml +++ b/.github/workflows/crd-diff.yaml @@ -9,7 +9,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index a8356cb73e..b387cc394a 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -28,7 +28,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -41,7 +41,7 @@ jobs: name: e2e-artifacts path: /tmp/artifacts/ - - uses: codecov/codecov-action@v5.4.3 + - uses: codecov/codecov-action@v5.5.0 with: disable_search: true files: coverage/e2e.out @@ -55,7 +55,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -68,7 +68,7 @@ jobs: name: experimental-e2e-artifacts path: /tmp/artifacts/ - - uses: codecov/codecov-action@v5.4.3 + - uses: codecov/codecov-action@v5.5.0 with: disable_search: true files: coverage/experimental-e2e.out @@ -80,7 +80,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -92,3 +92,22 @@ jobs: with: name: upgrade-e2e-artifacts path: /tmp/artifacts/ + + upgrade-experimental-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run the upgrade e2e test + run: ARTIFACT_PATH=/tmp/artifacts make test-upgrade-experimental-e2e + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: upgrade-experimental-e2e-artifacts + path: /tmp/artifacts/ + diff --git a/.github/workflows/go-apidiff.yaml b/.github/workflows/go-apidiff.yaml index bc9c87404a..44d53621cd 100644 --- a/.github/workflows/go-apidiff.yaml +++ b/.github/workflows/go-apidiff.yaml @@ -11,7 +11,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 391938deb6..b757aec9ea 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: 3.x cache: pip diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 757b42443d..bd472f578d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,7 +23,7 @@ jobs: fetch-depth: 0 - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version-file: "go.mod" diff --git a/.github/workflows/sanity.yaml b/.github/workflows/sanity.yaml index 24ffc432c8..7582fc00df 100644 --- a/.github/workflows/sanity.yaml +++ b/.github/workflows/sanity.yaml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: "go.mod" - name: Run verification checks @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: "go.mod" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a20ee47a6f..95cb101668 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -34,7 +34,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 90 diff --git a/.github/workflows/test-regression.yaml b/.github/workflows/test-regression.yaml index 1c0c532586..c4af7f879a 100644 --- a/.github/workflows/test-regression.yaml +++ b/.github/workflows/test-regression.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -23,7 +23,7 @@ jobs: run: | make test-regression - - uses: codecov/codecov-action@v5.4.3 + - uses: codecov/codecov-action@v5.5.0 with: disable_search: true files: coverage/regression.out diff --git a/.github/workflows/tilt.yaml b/.github/workflows/tilt.yaml index 877440fc5b..858250a132 100644 --- a/.github/workflows/tilt.yaml +++ b/.github/workflows/tilt.yaml @@ -21,7 +21,7 @@ jobs: with: path: operator-controller - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version-file: "operator-controller/go.mod" - name: Install Tilt diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index 7f5279d284..dd37d53393 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -23,7 +23,7 @@ jobs: run: | make test-unit - - uses: codecov/codecov-action@v5.4.3 + - uses: codecov/codecov-action@v5.5.0 with: disable_search: true files: coverage/unit.out diff --git a/.github/workflows/update-demos.yaml b/.github/workflows/update-demos.yaml index b1f85ab796..29d6fd4c47 100644 --- a/.github/workflows/update-demos.yaml +++ b/.github/workflows/update-demos.yaml @@ -25,7 +25,7 @@ jobs: steps: - run: sudo apt update && sudo apt install -y asciinema curl - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: "go.mod" - name: Run Demo Update diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e67cd25cb..156ae32e62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -151,7 +151,7 @@ Please follow this style to make the operator-controller project easier to revie ### Go version -Our goal is to minimize disruption by requiring the lowest possible Go language version. This means avoiding updaties to the go version specified in [go.mod](go.mod) (and other locations). +Our goal is to minimize disruption by requiring the lowest possible Go language version. This means avoiding updaties to the go version specified in the project's `go.mod` file (and other locations). There is a GitHub PR CI job named `go-verdiff` that will inform a PR author if the Go language version has been updated. It is not a required test, but failures should prompt authors and reviewers to have a discussion with the community about the Go language version change. diff --git a/Makefile b/Makefile index 53dd669b5d..acac46a5d5 100644 --- a/Makefile +++ b/Makefile @@ -90,9 +90,6 @@ EXPERIMENTAL_MANIFEST := ./manifests/experimental.yaml EXPERIMENTAL_E2E_MANIFEST := ./manifests/experimental-e2e.yaml CATALOGS_MANIFEST := ./manifests/default-catalogs.yaml -# Manifest used by kind-deploy, which may be overridden by other targets -SOURCE_MANIFEST := $(STANDARD_MANIFEST) - # Disable -j flag for make .NOTPARALLEL: @@ -274,14 +271,16 @@ test-e2e: SOURCE_MANIFEST := $(STANDARD_E2E_MANIFEST) test-e2e: KIND_CLUSTER_NAME := operator-controller-e2e test-e2e: GO_BUILD_EXTRA_FLAGS := -cover test-e2e: COVERAGE_NAME := e2e -test-e2e: run image-registry prometheus e2e e2e-coverage kind-clean #HELP Run e2e test suite on local kind cluster +test-e2e: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) +test-e2e: run-internal image-registry prometheus e2e e2e-coverage kind-clean #HELP Run e2e test suite on local kind cluster .PHONY: test-experimental-e2e test-experimental-e2e: SOURCE_MANIFEST := $(EXPERIMENTAL_E2E_MANIFEST) test-experimental-e2e: KIND_CLUSTER_NAME := operator-controller-e2e test-experimental-e2e: GO_BUILD_EXTRA_FLAGS := -cover test-experimental-e2e: COVERAGE_NAME := experimental-e2e -test-experimental-e2e: run image-registry prometheus experimental-e2e e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster +test-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST) +test-experimental-e2e: run-internal image-registry prometheus experimental-e2e e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster .PHONY: prometheus prometheus: PROMETHEUS_NAMESPACE := olmv1-system @@ -290,13 +289,15 @@ prometheus: #EXHELP Deploy Prometheus into specified namespace ./hack/test/install-prometheus.sh $(PROMETHEUS_NAMESPACE) $(PROMETHEUS_VERSION) $(KUSTOMIZE) $(VERSION) .PHONY: test-extension-developer-e2e +test-extension-developer-e2e: SOURCE_MANIFEST := $(STANDARD_E2E_MANIFEST) test-extension-developer-e2e: KIND_CLUSTER_NAME := operator-controller-ext-dev-e2e test-extension-developer-e2e: export INSTALL_DEFAULT_CATALOGS := false -test-extension-developer-e2e: run image-registry extension-developer-e2e kind-clean #HELP Run extension-developer e2e on local kind cluster +test-extension-developer-e2e: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) +test-extension-developer-e2e: run-internal image-registry extension-developer-e2e kind-clean #HELP Run extension-developer e2e on local kind cluster .PHONY: run-latest-release run-latest-release: - curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/$(notdir $(STANDARD_RELEASE_INSTALL)) | bash -s + curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/$(notdir $(RELEASE_INSTALL)) | bash -s .PHONY: pre-upgrade-setup pre-upgrade-setup: @@ -306,11 +307,27 @@ pre-upgrade-setup: post-upgrade-checks: go test -count=1 -v ./test/upgrade-e2e/... + +TEST_UPGRADE_E2E_TASKS := kind-cluster run-latest-release image-registry pre-upgrade-setup docker-build kind-load kind-deploy post-upgrade-checks kind-clean + .PHONY: test-upgrade-e2e +test-upgrade-e2e: SOURCE_MANIFEST := $(STANDARD_MANIFEST) +test-upgrade-e2e: RELEASE_INSTALL := $(STANDARD_RELEASE_INSTALL) test-upgrade-e2e: KIND_CLUSTER_NAME := operator-controller-upgrade-e2e +test-upgrade-e2e: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) test-upgrade-e2e: export TEST_CLUSTER_CATALOG_NAME := test-catalog test-upgrade-e2e: export TEST_CLUSTER_EXTENSION_NAME := test-package -test-upgrade-e2e: kind-cluster run-latest-release image-registry pre-upgrade-setup docker-build kind-load kind-deploy post-upgrade-checks kind-clean #HELP Run upgrade e2e tests on a local kind cluster +test-upgrade-e2e: $(TEST_UPGRADE_E2E_TASKS) #HELP Run upgrade e2e tests on a local kind cluster + +.PHONY: test-upgrade-experimental-e2e +test-upgrade-experimental-e2e: SOURCE_MANIFEST := $(EXPERIMENTAL_MANIFEST) +test-upgrade-experimental-e2e: RELEASE_INSTALL := $(EXPERIMENTAL_RELEASE_INSTALL) +test-upgrade-experimental-e2e: KIND_CLUSTER_NAME := operator-controller-upgrade-experimental-e2e +test-upgrade-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST) +test-upgrade-experimental-e2e: export TEST_CLUSTER_CATALOG_NAME := test-catalog +test-upgrade-experimental-e2e: export TEST_CLUSTER_EXTENSION_NAME := test-package +test-upgrade-experimental-e2e: $(TEST_UPGRADE_E2E_TASKS) #HELP Run upgrade e2e tests on a local kind cluster + .PHONY: e2e-coverage e2e-coverage: @@ -324,7 +341,6 @@ kind-load: $(KIND) #EXHELP Loads the currently constructed images into the KIND $(CONTAINER_RUNTIME) save $(CATD_IMG) | $(KIND) load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME) .PHONY: kind-deploy -kind-deploy: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) kind-deploy: export DEFAULT_CATALOG := $(RELEASE_CATALOGS) kind-deploy: manifests @echo -e "\n\U1F4D8 Using $(SOURCE_MANIFEST) as source manifest\n" @@ -395,12 +411,18 @@ go-build-linux: export GOOS=linux go-build-linux: export GOARCH=amd64 go-build-linux: $(BINARIES) +.PHONY: run-internal +run-internal: docker-build kind-cluster kind-load kind-deploy wait + .PHONY: run -run: docker-build kind-cluster kind-load kind-deploy wait #HELP Build the operator-controller then deploy it into a new kind cluster. +run: SOURCE_MANIFEST := $(STANDARD_MANIFEST) +run: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) +run: run-internal #HELP Build operator-controller then deploy it with the standard manifest into a new kind cluster. .PHONY: run-experimental run-experimental: SOURCE_MANIFEST := $(EXPERIMENTAL_MANIFEST) -run-experimental: run #HELP Build the operator-controller then deploy it with the experimental manifest into a new kind cluster. +run-experimental: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST) +run-experimental: run-internal #HELP Build the operator-controller then deploy it with the experimental manifest into a new kind cluster. CATD_NAMESPACE := olmv1-system wait: @@ -442,7 +464,7 @@ quickstart: manifests #EXHELP Generate the unified installation release manifest ##@ Docs .PHONY: crd-ref-docs -API_REFERENCE_FILENAME := operator-controller-api-reference.md +API_REFERENCE_FILENAME := olmv1-api-reference.md API_REFERENCE_DIR := $(ROOT_DIR)/docs/api-reference crd-ref-docs: $(CRD_REF_DOCS) #EXHELP Generate the API Reference Documents. rm -f $(API_REFERENCE_DIR)/$(API_REFERENCE_FILENAME) @@ -455,7 +477,7 @@ VENVDIR := $(abspath docs/.venv) .PHONY: build-docs build-docs: venv . $(VENV)/activate; \ - mkdocs build + mkdocs build --strict .PHONY: serve-docs serve-docs: venv @@ -465,7 +487,7 @@ serve-docs: venv .PHONY: deploy-docs deploy-docs: venv . $(VENV)/activate; \ - mkdocs gh-deploy --force + mkdocs gh-deploy --force --strict # The demo script requires to install asciinema with: brew install asciinema to run on mac os envs. # Please ensure that all demos are named with the demo name and the suffix -demo-script.sh diff --git a/README.md b/README.md index 783276d9b0..4be7f30d08 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +[![unit-test](https://github.com/operator-framework/operator-controller/actions/workflows/unit-test.yaml/badge.svg)](https://github.com/operator-framework/operator-controller/actions/workflows/unit-test.yaml) +[![e2e](https://github.com/operator-framework/operator-controller/actions/workflows/e2e.yaml/badge.svg)](https://github.com/operator-framework/operator-controller/actions/workflows/e2e.yaml) +[![codecov](https://codecov.io/gh/operator-framework/operator-controller/graph/badge.svg?token=5f34zaWaN7)](https://codecov.io/gh/operator-framework/operator-controller) + # operator-controller The operator-controller is the central component of Operator Lifecycle Manager (OLM) v1. It extends Kubernetes with an API through which users can install extensions. diff --git a/api/v1/clusterextension_types.go b/api/v1/clusterextension_types.go index 0141f1a7a4..a2dd890c3d 100644 --- a/api/v1/clusterextension_types.go +++ b/api/v1/clusterextension_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -25,6 +26,8 @@ var ClusterExtensionKind = "ClusterExtension" type ( UpgradeConstraintPolicy string CRDUpgradeSafetyEnforcement string + + ClusterExtensionConfigType string ) const ( @@ -39,6 +42,8 @@ const ( // Use with caution as this can lead to unknown and potentially // disastrous results such as data loss. UpgradeConstraintPolicySelfCertified UpgradeConstraintPolicy = "SelfCertified" + + ClusterExtensionConfigTypeInline ClusterExtensionConfigType = "Inline" ) // ClusterExtensionSpec defines the desired state of ClusterExtension @@ -92,6 +97,15 @@ type ClusterExtensionSpec struct { // // +optional Install *ClusterExtensionInstallConfig `json:"install,omitempty"` + + // config contains optional configuration values applied during rendering of the + // ClusterExtension's manifests. Values can be specified inline. + // + // config is optional. When not specified, the default configuration of the resolved bundle will be used. + // + // + // +optional + Config *ClusterExtensionConfig `json:"config,omitempty"` } const SourceTypeCatalog = "Catalog" @@ -138,6 +152,34 @@ type ClusterExtensionInstallConfig struct { Preflight *PreflightConfig `json:"preflight,omitempty"` } +// ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into +// the ClusterExtension's rendered manifests. +// +// +kubebuilder:validation:XValidation:rule="has(self.configType) && self.configType == 'Inline' ?has(self.inline) : !has(self.inline)",message="inline is required when configType is Inline, and forbidden otherwise" +// +union +type ClusterExtensionConfig struct { + // configType is a required reference to the type of configuration source. + // + // Allowed values are "Inline" + // + // When this field is set to "Inline", the cluster extension configuration is defined inline within the + // ClusterExtension resource. + // + // +unionDiscriminator + // +kubebuilder:validation:Enum:="Inline" + // +kubebuilder:validation:Required + ConfigType ClusterExtensionConfigType `json:"configType"` + + // inline contains JSON or YAML values specified directly in the + // ClusterExtension. + // + // inline must be set if configType is 'Inline'. + // + // +kubebuilder:validation:Type=object + // +optional + Inline *apiextensionsv1.JSON `json:"inline,omitempty"` +} + // CatalogFilter defines the attributes used to identify and filter content from a catalog. type CatalogFilter struct { // packageName is a reference to the name of the package to be installed diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 23fcf7d85e..01ad99562b 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -252,6 +252,25 @@ func (in *ClusterExtension) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionConfig) DeepCopyInto(out *ClusterExtensionConfig) { + *out = *in + if in.Inline != nil { + in, out := &in.Inline, &out.Inline + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionConfig. +func (in *ClusterExtensionConfig) DeepCopy() *ClusterExtensionConfig { + if in == nil { + return nil + } + out := new(ClusterExtensionConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterExtensionInstallConfig) DeepCopyInto(out *ClusterExtensionInstallConfig) { *out = *in @@ -330,6 +349,11 @@ func (in *ClusterExtensionSpec) DeepCopyInto(out *ClusterExtensionSpec) { *out = new(ClusterExtensionInstallConfig) (*in).DeepCopyInto(*out) } + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(ClusterExtensionConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionSpec. diff --git a/config/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml b/config/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml index 162683603d..ac24fe1b66 100644 --- a/config/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml +++ b/config/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml @@ -57,6 +57,40 @@ spec: description: spec is an optional field that defines the desired state of the ClusterExtension. properties: + config: + description: |- + config contains optional configuration values applied during rendering of the + ClusterExtension's manifests. Values can be specified inline. + + config is optional. When not specified, the default configuration of the resolved bundle will be used. + properties: + configType: + description: |- + configType is a required reference to the type of configuration source. + + Allowed values are "Inline" + + When this field is set to "Inline", the cluster extension configuration is defined inline within the + ClusterExtension resource. + enum: + - Inline + type: string + inline: + description: |- + inline contains JSON or YAML values specified directly in the + ClusterExtension. + + inline must be set if configType is 'Inline'. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configType + type: object + x-kubernetes-validations: + - message: inline is required when configType is Inline, and forbidden + otherwise + rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline) + : !has(self.inline)' install: description: |- install is an optional field used to configure the installation options diff --git a/docs/api-reference/operator-controller-api-reference.md b/docs/api-reference/olmv1-api-reference.md similarity index 96% rename from docs/api-reference/operator-controller-api-reference.md rename to docs/api-reference/olmv1-api-reference.md index 84fdbfa646..8aaa37e84c 100644 --- a/docs/api-reference/operator-controller-api-reference.md +++ b/docs/api-reference/olmv1-api-reference.md @@ -239,6 +239,40 @@ _Appears in:_ | `status` _[ClusterExtensionStatus](#clusterextensionstatus)_ | status is an optional field that defines the observed state of the ClusterExtension. | | | +#### ClusterExtensionConfig + + + +ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into +the ClusterExtension's rendered manifests. + + + +_Appears in:_ +- [ClusterExtensionSpec](#clusterextensionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `configType` _[ClusterExtensionConfigType](#clusterextensionconfigtype)_ | configType is a required reference to the type of configuration source.

Allowed values are "Inline"

When this field is set to "Inline", the cluster extension configuration is defined inline within the
ClusterExtension resource. | | Enum: [Inline]
Required: \{\}
| +| `inline` _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#json-v1-apiextensions-k8s-io)_ | inline contains JSON or YAML values specified directly in the
ClusterExtension.

inline must be set if configType is 'Inline'. | | Type: object
| + + +#### ClusterExtensionConfigType + +_Underlying type:_ _string_ + + + + + +_Appears in:_ +- [ClusterExtensionConfig](#clusterextensionconfig) + +| Field | Description | +| --- | --- | +| `Inline` | | + + #### ClusterExtensionInstallConfig @@ -309,6 +343,7 @@ _Appears in:_ | `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions
with the cluster that are required to manage the extension.
The ServiceAccount must be configured with the necessary permissions to perform these interactions.
The ServiceAccount must exist in the namespace referenced in the spec.
serviceAccount is required. | | Required: \{\}
| | `source` _[SourceConfig](#sourceconfig)_ | source is a required field which selects the installation source of content
for this ClusterExtension. Selection is performed by setting the sourceType.

Catalog is currently the only implemented sourceType, and setting the
sourcetype to "Catalog" requires the catalog field to also be defined.

Below is a minimal example of a source definition (in yaml):

source:
sourceType: Catalog
catalog:
packageName: example-package | | Required: \{\}
| | `install` _[ClusterExtensionInstallConfig](#clusterextensioninstallconfig)_ | install is an optional field used to configure the installation options
for the ClusterExtension such as the pre-flight check configuration. | | | +| `config` _[ClusterExtensionConfig](#clusterextensionconfig)_ | config contains optional configuration values applied during rendering of the
ClusterExtension's manifests. Values can be specified inline.

config is optional. When not specified, the default configuration of the resolved bundle will be used.

| | | #### ClusterExtensionStatus diff --git a/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md b/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md index 6b27ba27e2..eb70149dac 100644 --- a/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md +++ b/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md @@ -31,7 +31,7 @@ As an example, to access only the [package schema](https://olm.operatorframework the URL to access the service would be `https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio/api/v1/metas?schema=olm.package` -For more examples of valid queries that can be made to the `api/v1/metas` service endpoint, please see [Catalog Queries](../howto/catalog-queries.md). +For more examples of valid queries that can be made to the `api/v1/metas` service endpoint, please see [Catalog Queries](../../howto/catalog-queries.md). !!! note diff --git a/docs/draft/howto/catalog-queries-metas-endpoint.md b/docs/draft/howto/catalog-queries-metas-endpoint.md index f723d504b6..25b45a7a41 100644 --- a/docs/draft/howto/catalog-queries-metas-endpoint.md +++ b/docs/draft/howto/catalog-queries-metas-endpoint.md @@ -1,6 +1,6 @@ # Catalog queries -After you [add a catalog of extensions](../tutorials/add-catalog.md) to your cluster, you must port forward your catalog as a service. +After you [add a catalog of extensions](../../tutorials/add-catalog.md) to your cluster, you must port forward your catalog as a service. Then you can query the catalog by using `curl` commands and the `jq` CLI tool to find extensions to install. ## Prerequisites diff --git a/docs/draft/howto/single-ownnamespace-install.md b/docs/draft/howto/single-ownnamespace-install.md index 2f440f32d2..866bf1da5d 100644 --- a/docs/draft/howto/single-ownnamespace-install.md +++ b/docs/draft/howto/single-ownnamespace-install.md @@ -56,11 +56,11 @@ kubectl rollout status -n olmv1-system deployment/operator-controller-controller ## Configuring the `ClusterExtension` A `ClusterExtension` can be configured to install bundle in `Single-` or `OwnNamespace` mode through the -`olm.operatorframework.io/watch-namespace: ` annotation. The *installMode* is inferred in the following way: +`.spec.config.inline.watchNamespace` property. The *installMode* is inferred in the following way: - - *AllNamespaces*: `` is empty, or the annotation is not present - - *OwnNamespace*: `` is the install namespace (i.e. `.spec.namespace`) - - *SingleNamespace*: `` not the install namespace + - *AllNamespaces*: `watchNamespace` is empty, or not set + - *OwnNamespace*: `watchNamespace` is the install namespace (i.e. `.spec.namespace`) + - *SingleNamespace*: `watchNamespace` *not* the install namespace ### Examples @@ -70,12 +70,13 @@ apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: argocd - annotations: - olm.operatorframework.io/watch-namespace: argocd-watch spec: namespace: argocd serviceAccount: name: argocd-installer + config: + inline: + watchNamespace: argocd-watch source: sourceType: Catalog catalog: @@ -96,6 +97,9 @@ spec: namespace: argocd serviceAccount: name: argocd-installer + config: + inline: + watchNamespace: argocd source: sourceType: Catalog catalog: diff --git a/docs/draft/tutorials/explore-available-content-metas-endpoint.md b/docs/draft/tutorials/explore-available-content-metas-endpoint.md index 8ece0a75d8..70cb87424e 100644 --- a/docs/draft/tutorials/explore-available-content-metas-endpoint.md +++ b/docs/draft/tutorials/explore-available-content-metas-endpoint.md @@ -5,7 +5,7 @@ hide: # Explore Available Content -After you [add a catalog of extensions](add-catalog.md) to your cluster, you must port forward your catalog as a service. +After you [add a catalog of extensions](../../tutorials/add-catalog.md) to your cluster, you must port forward your catalog as a service. Then you can query the catalog by using `curl` commands and the `jq` CLI tool to find extensions to install. ## Prerequisites @@ -144,4 +144,4 @@ Then you can query the catalog by using `curl` commands and the `jq` CLI tool to ### Additional resources -* [Catalog queries](../howto/catalog-queries.md) +* [Catalog queries](../../howto/catalog-queries.md) diff --git a/docs/project/public-api.md b/docs/project/public-api.md index a0e45ce93d..687c14a2a5 100644 --- a/docs/project/public-api.md +++ b/docs/project/public-api.md @@ -2,8 +2,7 @@ The public API of OLM v1 is as follows: - Kubernetes APIs. For more information on these APIs, see: - - [operator-controller API reference](../api-reference/operator-controller-api-reference.md) - - [catalogd API reference](../api-reference/catalogd-api-reference.md) + - [OLMv1 API reference](../api-reference/olmv1-api-reference.md) - `Catalogd` web server. For more information on what this includes, see the [catalogd web server documentation](../api-reference/catalogd-webserver.md) !!! warning diff --git a/go.mod b/go.mod index 18ab3c00d9..33751eb4eb 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/cert-manager/cert-manager v1.18.2 github.com/containerd/containerd v1.7.28 - github.com/containers/image/v5 v5.36.1 + github.com/containers/image/v5 v5.36.2 github.com/fsnotify/fsnotify v1.9.0 github.com/go-logr/logr v1.4.3 github.com/golang-jwt/jwt/v5 v5.3.0 @@ -19,32 +19,32 @@ require ( github.com/klauspost/compress v1.18.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 - github.com/operator-framework/api v0.33.0 + github.com/operator-framework/api v0.34.0 github.com/operator-framework/helm-operator-plugins v0.8.0 - github.com/operator-framework/operator-registry v1.56.0 - github.com/prometheus/client_golang v1.23.0 - github.com/prometheus/common v0.65.0 - github.com/spf13/cobra v1.9.1 - github.com/stretchr/testify v1.10.0 + github.com/operator-framework/operator-registry v1.57.0 + github.com/prometheus/client_golang v1.23.1 + github.com/prometheus/common v0.66.0 + github.com/spf13/cobra v1.10.1 + github.com/stretchr/testify v1.11.1 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/mod v0.27.0 golang.org/x/sync v0.16.0 golang.org/x/tools v0.36.0 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.18.4 - k8s.io/api v0.33.2 - k8s.io/apiextensions-apiserver v0.33.2 - k8s.io/apimachinery v0.33.2 - k8s.io/apiserver v0.33.2 - k8s.io/cli-runtime v0.33.2 - k8s.io/client-go v0.33.2 - k8s.io/component-base v0.33.2 + helm.sh/helm/v3 v3.18.6 + k8s.io/api v0.33.4 + k8s.io/apiextensions-apiserver v0.33.4 + k8s.io/apimachinery v0.33.4 + k8s.io/apiserver v0.33.4 + k8s.io/cli-runtime v0.33.3 + k8s.io/client-go v0.33.4 + k8s.io/component-base v0.33.4 k8s.io/klog/v2 v2.130.1 k8s.io/kubernetes v1.33.2 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/controller-tools v0.18.0 - sigs.k8s.io/crdify v0.4.1-0.20250613143457-398e4483fb58 + sigs.k8s.io/crdify v0.5.0 sigs.k8s.io/yaml v1.6.0 ) @@ -82,7 +82,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect - github.com/containers/common v0.63.1 // indirect + github.com/containers/common v0.64.1 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.2.1 // indirect github.com/containers/storage v1.59.1 // indirect @@ -90,7 +90,7 @@ require ( github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v28.3.2+incompatible // indirect + github.com/docker/cli v28.3.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v28.3.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect @@ -108,7 +108,7 @@ require ( github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-git/v5 v5.16.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -126,6 +126,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 // indirect github.com/h2non/filetype v1.1.3 // indirect @@ -149,7 +150,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -168,7 +169,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/onsi/gomega v1.37.0 // indirect + github.com/onsi/gomega v1.38.2 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect github.com/operator-framework/operator-lib v0.17.0 // indirect github.com/otiai10/copy v1.14.1 // indirect @@ -182,6 +183,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/fulcio v1.7.1 // indirect @@ -190,32 +192,29 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smallstep/pkcs7 v0.2.1 // indirect github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect - github.com/ulikunitz/xz v0.5.12 // indirect + github.com/ulikunitz/xz v0.5.14 // indirect github.com/vbatts/tar-split v0.12.1 // indirect github.com/vbauerster/mpb/v8 v8.10.2 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.etcd.io/bbolt v1.4.2 // indirect + go.etcd.io/bbolt v1.4.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect @@ -226,16 +225,16 @@ require ( golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/controller-manager v0.33.2 // indirect - k8s.io/kubectl v0.33.2 // indirect + k8s.io/kubectl v0.33.3 // indirect oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect diff --git a/go.sum b/go.sum index fef7d9773c..285351c7da 100644 --- a/go.sum +++ b/go.sum @@ -77,10 +77,10 @@ github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRq github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= -github.com/containers/common v0.63.1 h1:6g02gbW34PaRVH4Heb2Pk11x0SdbQ+8AfeKKeQGqYBE= -github.com/containers/common v0.63.1/go.mod h1:+3GCotSqNdIqM3sPs152VvW7m5+Mg8Kk+PExT3G9hZw= -github.com/containers/image/v5 v5.36.1 h1:6zpXBqR59UcAzoKpa/By5XekeqFV+htWYfr65+Cgjqo= -github.com/containers/image/v5 v5.36.1/go.mod h1:b4GMKH2z/5t6/09utbse2ZiLK/c72GuGLFdp7K69eA4= +github.com/containers/common v0.64.1 h1:E8vSiL+B84/UCsyVSb70GoxY9cu+0bseLujm4EKF6GE= +github.com/containers/common v0.64.1/go.mod h1:CtfQNHoCAZqWeXMwdShcsxmMJSeGRgKKMqAwRKmWrHE= +github.com/containers/image/v5 v5.36.2 h1:GcxYQyAHRF/pLqR4p4RpvKllnNL8mOBn0eZnqJbfTwk= +github.com/containers/image/v5 v5.36.2/go.mod h1:b4GMKH2z/5t6/09utbse2ZiLK/c72GuGLFdp7K69eA4= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= @@ -108,8 +108,10 @@ github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.3.2+incompatible h1:mOt9fcLE7zaACbxW1GeS65RI67wIJrTnqS3hP2huFsY= -github.com/docker/cli v28.3.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= +github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= @@ -158,8 +160,8 @@ github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77 github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -229,8 +231,8 @@ github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2 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-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= -github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -246,6 +248,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= @@ -315,8 +319,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= -github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= @@ -362,24 +366,24 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= +github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/operator-framework/api v0.33.0 h1:Tdu9doXz6Key2riIiP3/JPahHEgFBXAqyWQN4kOITS8= -github.com/operator-framework/api v0.33.0/go.mod h1:sEh1VqwQCJUj+l/rKNWPDEJdFNAbdTu8QcM+x+wdYYo= +github.com/operator-framework/api v0.34.0 h1:REiEaYhG1CWmDoajdcAdZqtgoljWG+ixMY59vUX5pFI= +github.com/operator-framework/api v0.34.0/go.mod h1:eGncUNIYvWtfGCCKmLzGXvoi3P0TDf3Yd/Z0Sn9E6SQ= github.com/operator-framework/helm-operator-plugins v0.8.0 h1:0f6HOQC5likkf0b/OvGvw7nhDb6h8Cj5twdCNjwNzMc= github.com/operator-framework/helm-operator-plugins v0.8.0/go.mod h1:Sc+8bE38xTCgCChBUvtq/PxatEg9fAypr7S5iAw8nlA= github.com/operator-framework/operator-lib v0.17.0 h1:cbz51wZ9+GpWR1ZYP4CSKSSBxDlWxmmnseaHVZZjZt4= github.com/operator-framework/operator-lib v0.17.0/go.mod h1:TGopBxIE8L6E/Cojzo26R3NFp1eNlqhQNmzqhOblaLw= -github.com/operator-framework/operator-registry v1.56.0 h1:vbTyee/gahpnh7qw1hV1osnWy9YpTjIbEuHpwIdoEUs= -github.com/operator-framework/operator-registry v1.56.0/go.mod h1:NOmQyrgOGW0cwUxHG5ZqKxdObOzQNmO4Rxcf7JC32FU= +github.com/operator-framework/operator-registry v1.57.0 h1:mQ4c8A8VUxZPJ0QCFRNio+7JEsLX6eKxlDSl6ORCRdk= +github.com/operator-framework/operator-registry v1.57.0/go.mod h1:9rAZH/LZ/ttEuTvL1D4KApGqOtRDE6fJzzOrJNcBu7g= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= @@ -397,13 +401,13 @@ github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M= github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y= +github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY= +github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/redis/go-redis/extra/rediscmd/v9 v9.10.0 h1:uTiEyEyfLhkw678n6EulHVto8AkcXVr8zUcBJNZ0ark= @@ -441,10 +445,10 @@ github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw= github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= @@ -461,32 +465,25 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg= +github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM= github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0= 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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= -go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= @@ -505,8 +502,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= @@ -531,16 +528,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwW go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -553,8 +550,8 @@ 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.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -682,6 +679,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -689,17 +688,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -709,8 +708,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -729,8 +728,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -helm.sh/helm/v3 v3.18.4 h1:pNhnHM3nAmDrxz6/UC+hfjDY4yeDATQCka2/87hkZXQ= -helm.sh/helm/v3 v3.18.4/go.mod h1:WVnwKARAw01iEdjpEkP7Ii1tT1pTPYfM1HsakFKM3LI= +helm.sh/helm/v3 v3.18.6 h1:S/2CqcYnNfLckkHLI0VgQbxgcDaU3N4A/46E3n9wSNY= +helm.sh/helm/v3 v3.18.6/go.mod h1:L/dXDR2r539oPlFP1PJqKAC1CUgqHJDLkxKpDGrWnyg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= @@ -769,8 +768,8 @@ sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytI sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= sigs.k8s.io/controller-tools v0.18.0 h1:rGxGZCZTV2wJreeRgqVoWab/mfcumTMmSwKzoM9xrsE= sigs.k8s.io/controller-tools v0.18.0/go.mod h1:gLKoiGBriyNh+x1rWtUQnakUYEujErjXs9pf+x/8n1U= -sigs.k8s.io/crdify v0.4.1-0.20250613143457-398e4483fb58 h1:VTvhbqgZMVoDpHHPuZLaOgzjjsJBhO8+vDKA1COuLCY= -sigs.k8s.io/crdify v0.4.1-0.20250613143457-398e4483fb58/go.mod h1:ZIFxaYNgKYmFtZCLPysncXQ8oqwnNlHQbRUfxJHZwzU= +sigs.k8s.io/crdify v0.5.0 h1:mrMH9CgXQPTZUpTU6Klqfnlys8bggv/7uvLT2lXSP7A= +sigs.k8s.io/crdify v0.5.0/go.mod h1:ZIFxaYNgKYmFtZCLPysncXQ8oqwnNlHQbRUfxJHZwzU= sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= diff --git a/hack/test/pre-upgrade-setup.sh b/hack/test/pre-upgrade-setup.sh index d60c9f03c6..669f9da37f 100755 --- a/hack/test/pre-upgrade-setup.sh +++ b/hack/test/pre-upgrade-setup.sh @@ -109,8 +109,10 @@ rules: verbs: - get - list + - watch - create - update + - patch - delete - apiGroups: - "olm.operatorframework.io" diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index ecfb3fdc2b..e34ad2b280 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -27,6 +27,7 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" "github.com/operator-framework/operator-controller/internal/operator-controller/features" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" @@ -57,7 +58,7 @@ type Preflight interface { } type BundleToHelmChartConverter interface { - ToHelmChart(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) + ToHelmChart(bundle source.BundleSource, installNamespace string, config map[string]interface{}) (*chart.Chart, error) } type Helm struct { @@ -222,7 +223,10 @@ func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*char } } - return h.BundleToHelmChartConverter.ToHelmChart(source.FromFS(bundleFS), ext.Spec.Namespace, watchNamespace) + bundleConfig := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: watchNamespace, + } + return h.BundleToHelmChartConverter.ToHelmChart(source.FromFS(bundleFS), ext.Spec.Namespace, bundleConfig) } func (h *Helm) renderClientOnlyRelease(ctx context.Context, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, error) { diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index 89c94df88d..1f2a3cfd27 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -3,6 +3,7 @@ package applier_test import ( "context" "errors" + "fmt" "io" "os" "testing" @@ -16,6 +17,7 @@ import ( "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" @@ -26,6 +28,7 @@ import ( "github.com/operator-framework/operator-controller/internal/operator-controller/applier" "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" "github.com/operator-framework/operator-controller/internal/operator-controller/features" + registryv1Bundle "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert" ) @@ -557,8 +560,8 @@ func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testin }, }, BundleToHelmChartConverter: &fakeBundleToHelmChartConverter{ - fn: func(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) { - require.Equal(t, expectedWatchNamespace, watchNamespace) + fn: func(bundle source.BundleSource, installNamespace string, config map[string]interface{}) (*chart.Chart, error) { + require.Equal(t, expectedWatchNamespace, config[registryv1Bundle.BundleConfigWatchNamespaceKey]) return nil, nil }, }, @@ -567,8 +570,13 @@ func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testin testExt := &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "testExt", - Annotations: map[string]string{ - applier.AnnotationClusterExtensionWatchNamespace: expectedWatchNamespace, + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"%s":"%s"}`, registryv1Bundle.BundleConfigWatchNamespaceKey, expectedWatchNamespace)), + }, }, }, } @@ -590,8 +598,8 @@ func TestApply_RegistryV1ToChartConverterIntegration(t *testing.T) { }, }, BundleToHelmChartConverter: &fakeBundleToHelmChartConverter{ - fn: func(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) { - require.Equal(t, expectedWatchNamespace, watchNamespace) + fn: func(bundle source.BundleSource, installNamespace string, config map[string]interface{}) (*chart.Chart, error) { + require.Equal(t, expectedWatchNamespace, config[registryv1Bundle.BundleConfigWatchNamespaceKey]) return nil, nil }, }, @@ -610,7 +618,7 @@ func TestApply_RegistryV1ToChartConverterIntegration(t *testing.T) { }, }, BundleToHelmChartConverter: &fakeBundleToHelmChartConverter{ - fn: func(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) { + fn: func(bundle source.BundleSource, installNamespace string, config map[string]interface{}) (*chart.Chart, error) { return nil, errors.New("some error") }, }, @@ -622,9 +630,9 @@ func TestApply_RegistryV1ToChartConverterIntegration(t *testing.T) { } type fakeBundleToHelmChartConverter struct { - fn func(source.BundleSource, string, string) (*chart.Chart, error) + fn func(source.BundleSource, string, map[string]interface{}) (*chart.Chart, error) } -func (f fakeBundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) { - return f.fn(bundle, installNamespace, watchNamespace) +func (f fakeBundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, installNamespace string, config map[string]interface{}) (*chart.Chart, error) { + return f.fn(bundle, installNamespace, config) } diff --git a/internal/operator-controller/applier/watchnamespace.go b/internal/operator-controller/applier/watchnamespace.go index 193f456b3c..a89c95d294 100644 --- a/internal/operator-controller/applier/watchnamespace.go +++ b/internal/operator-controller/applier/watchnamespace.go @@ -1,9 +1,9 @@ package applier import ( + "encoding/json" "fmt" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/validation" ocv1 "github.com/operator-framework/operator-controller/api/v1" @@ -19,14 +19,28 @@ const ( // for registry+v1 bundles. This will go away once the ClusterExtension API is updated to include // (opaque) runtime configuration. func GetWatchNamespace(ext *ocv1.ClusterExtension) (string, error) { - if features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport) { - if ext != nil && ext.Annotations[AnnotationClusterExtensionWatchNamespace] != "" { - watchNamespace := ext.Annotations[AnnotationClusterExtensionWatchNamespace] - if errs := validation.IsDNS1123Subdomain(watchNamespace); len(errs) > 0 { - return "", fmt.Errorf("invalid watch namespace '%s': namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", watchNamespace) - } - return ext.Annotations[AnnotationClusterExtensionWatchNamespace], nil + if !features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport) { + return "", nil + } + + var watchNamespace string + if ext.Spec.Config != nil && ext.Spec.Config.Inline != nil { + cfg := struct { + WatchNamespace string `json:"watchNamespace"` + }{} + if err := json.Unmarshal(ext.Spec.Config.Inline.Raw, &cfg); err != nil { + return "", fmt.Errorf("invalid bundle configuration: %w", err) } + watchNamespace = cfg.WatchNamespace + } else if _, ok := ext.Annotations[AnnotationClusterExtensionWatchNamespace]; ok { + watchNamespace = ext.Annotations[AnnotationClusterExtensionWatchNamespace] + } else { + return "", nil } - return corev1.NamespaceAll, nil + + if errs := validation.IsDNS1123Subdomain(watchNamespace); len(errs) > 0 { + return "", fmt.Errorf("invalid watch namespace '%s': namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", watchNamespace) + } + + return watchNamespace, nil } diff --git a/internal/operator-controller/applier/watchnamespace_test.go b/internal/operator-controller/applier/watchnamespace_test.go index 90e018dc7c..4745f3dc54 100644 --- a/internal/operator-controller/applier/watchnamespace_test.go +++ b/internal/operator-controller/applier/watchnamespace_test.go @@ -5,26 +5,31 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" featuregatetesting "k8s.io/component-base/featuregate/testing" - v1 "github.com/operator-framework/operator-controller/api/v1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/applier" "github.com/operator-framework/operator-controller/internal/operator-controller/features" ) func TestGetWatchNamespacesWhenFeatureGateIsDisabled(t *testing.T) { - watchNamespace, err := applier.GetWatchNamespace(&v1.ClusterExtension{ + watchNamespace, err := applier.GetWatchNamespace(&ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "extension", - Annotations: map[string]string{ - "olm.operatorframework.io/watch-namespace": "watch-namespace", + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace":"watch-namespace"}`), + }, }, }, - Spec: v1.ClusterExtensionSpec{}, }) require.NoError(t, err) - t.Log("Check watchNamespace is '' even if the annotation is set") + t.Log("Check watchNamespace is '' even if the configuration is set") require.Equal(t, corev1.NamespaceAll, watchNamespace) } @@ -34,57 +39,140 @@ func TestGetWatchNamespace(t *testing.T) { for _, tt := range []struct { name string want string - csv *v1.ClusterExtension + csv *ocv1.ClusterExtension expectError bool }{ { - name: "cluster extension does not have watch namespace annotation", + name: "cluster extension does not configure a watch namespace", want: corev1.NamespaceAll, - csv: &v1.ClusterExtension{ + csv: &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "extension", Annotations: nil, }, - Spec: v1.ClusterExtensionSpec{}, + Spec: ocv1.ClusterExtensionSpec{}, + }, + expectError: false, + }, { + name: "cluster extension configures a watch namespace", + want: "watch-namespace", + csv: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace":"watch-namespace"}`), + }, + }, + }, }, expectError: false, }, { - name: "cluster extension has valid namespace annotation", + name: "cluster extension configures a watch namespace through annotation", want: "watch-namespace", - csv: &v1.ClusterExtension{ + csv: &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "extension", Annotations: map[string]string{ "olm.operatorframework.io/watch-namespace": "watch-namespace", }, }, - Spec: v1.ClusterExtensionSpec{}, }, expectError: false, }, { - name: "cluster extension has invalid namespace annotation: multiple watch namespaces", - want: "", - csv: &v1.ClusterExtension{ + name: "cluster extension configures a watch namespace through annotation with invalid ns", + csv: &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "extension", Annotations: map[string]string{ - "olm.operatorframework.io/watch-namespace": "watch-namespace,watch-namespace2,watch-namespace3", + "olm.operatorframework.io/watch-namespace": "watch-namespace-", }, }, - Spec: v1.ClusterExtensionSpec{}, }, expectError: true, }, { - name: "cluster extension has invalid namespace annotation: invalid name", - want: "", - csv: &v1.ClusterExtension{ + name: "cluster extension configures a watch namespace through annotation with empty ns", + csv: &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "extension", Annotations: map[string]string{ - "olm.operatorframework.io/watch-namespace": "watch-namespace-", + "olm.operatorframework.io/watch-namespace": "", + }, + }, + }, + expectError: true, + }, { + name: "cluster extension configures a watch namespace through annotation and config (take config)", + want: "watch-namespace", + csv: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension", + Annotations: map[string]string{ + "olm.operatorframework.io/watch-namespace": "dont-use-this-watch-namespace", + }, + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace":"watch-namespace"}`), + }, + }, + }, + }, + expectError: false, + }, { + name: "cluster extension configures an invalid watchNamespace: multiple watch namespaces", + want: "", + csv: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace":"watch-namespace,watch-namespace2,watch-namespace3"}`), + }, + }, + }, + }, + expectError: true, + }, { + name: "cluster extension configures an invalid watchNamespace: invalid name", + want: "", + csv: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace":"watch-namespace-"}`), + }, + }, + }, + }, + expectError: true, + }, { + name: "cluster extension configures an invalid watchNamespace: invalid json", + want: "", + csv: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`invalid json`), + }, }, }, - Spec: v1.ClusterExtensionSpec{}, }, expectError: true, }, diff --git a/internal/operator-controller/controllers/clusterextension_controller.go b/internal/operator-controller/controllers/clusterextension_controller.go index 24824bfd12..ce6f63c3aa 100644 --- a/internal/operator-controller/controllers/clusterextension_controller.go +++ b/internal/operator-controller/controllers/clusterextension_controller.go @@ -22,7 +22,6 @@ import ( "fmt" "io/fs" "strings" - "time" "github.com/go-logr/logr" "helm.sh/helm/v3/pkg/release" @@ -156,15 +155,13 @@ func ensureAllConditionsWithReason(ext *ocv1.ClusterExtension, reason v1alpha1.C cond := apimeta.FindStatusCondition(ext.Status.Conditions, condType) if cond == nil { // Create a new condition with a valid reason and add it - newCond := metav1.Condition{ + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: condType, Status: metav1.ConditionFalse, Reason: string(reason), Message: message, ObservedGeneration: ext.GetGeneration(), - LastTransitionTime: metav1.NewTime(time.Now()), - } - ext.Status.Conditions = append(ext.Status.Conditions, newCond) + }) } } } @@ -381,7 +378,7 @@ func SetDeprecationStatus(ext *ocv1.ClusterExtension, bundleName string, depreca if len(deprecationMessages) > 0 { status, reason, message = metav1.ConditionTrue, ocv1.ReasonDeprecated, strings.Join(deprecationMessages, ";") } - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: ocv1.TypeDeprecated, Reason: reason, Status: status, @@ -403,7 +400,7 @@ func SetDeprecationStatus(ext *ocv1.ClusterExtension, bundleName string, depreca message = fmt.Sprintf("%s\n%s", message, entry.Message) } } - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: conditionType, Reason: reason, Status: status, diff --git a/internal/operator-controller/controllers/common_controller.go b/internal/operator-controller/controllers/common_controller.go index 7cee10c100..9195a83f9d 100644 --- a/internal/operator-controller/controllers/common_controller.go +++ b/internal/operator-controller/controllers/common_controller.go @@ -27,6 +27,30 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" ) +const ( + // maxConditionMessageLength set the max message length allowed by Kubernetes. + maxConditionMessageLength = 32768 + // truncationSuffix is the suffix added when a message is cut. + truncationSuffix = "\n\n... [message truncated]" +) + +// truncateMessage cuts long messages to fit Kubernetes condition limits +func truncateMessage(message string) string { + if len(message) <= maxConditionMessageLength { + return message + } + + maxContent := maxConditionMessageLength - len(truncationSuffix) + return message[:maxContent] + truncationSuffix +} + +// SetStatusCondition wraps apimeta.SetStatusCondition and ensures the message is always truncated +// This should be used throughout the codebase instead of apimeta.SetStatusCondition directly +func SetStatusCondition(conditions *[]metav1.Condition, condition metav1.Condition) { + condition.Message = truncateMessage(condition.Message) + apimeta.SetStatusCondition(conditions, condition) +} + // setInstalledStatusFromBundle sets the installed status based on the given installedBundle. func setInstalledStatusFromBundle(ext *ocv1.ClusterExtension, installedBundle *InstalledBundle) { // Nothing is installed @@ -45,7 +69,7 @@ func setInstalledStatusFromBundle(ext *ocv1.ClusterExtension, installedBundle *I // setInstalledStatusConditionSuccess sets the installed status condition to success. func setInstalledStatusConditionSuccess(ext *ocv1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: ocv1.TypeInstalled, Status: metav1.ConditionTrue, Reason: ocv1.ReasonSucceeded, @@ -56,7 +80,7 @@ func setInstalledStatusConditionSuccess(ext *ocv1.ClusterExtension, message stri // setInstalledStatusConditionFailed sets the installed status condition to failed. func setInstalledStatusConditionFailed(ext *ocv1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: ocv1.TypeInstalled, Status: metav1.ConditionFalse, Reason: ocv1.ReasonFailed, @@ -67,7 +91,7 @@ func setInstalledStatusConditionFailed(ext *ocv1.ClusterExtension, message strin // setInstalledStatusConditionUnknown sets the installed status condition to unknown. func setInstalledStatusConditionUnknown(ext *ocv1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: ocv1.TypeInstalled, Status: metav1.ConditionUnknown, Reason: ocv1.ReasonFailed, @@ -99,5 +123,5 @@ func setStatusProgressing(ext *ocv1.ClusterExtension, err error) { progressingCond.Reason = ocv1.ReasonBlocked } - apimeta.SetStatusCondition(&ext.Status.Conditions, progressingCond) + SetStatusCondition(&ext.Status.Conditions, progressingCond) } diff --git a/internal/operator-controller/controllers/common_controller_test.go b/internal/operator-controller/controllers/common_controller_test.go index 7b644172d1..057a2c9dc5 100644 --- a/internal/operator-controller/controllers/common_controller_test.go +++ b/internal/operator-controller/controllers/common_controller_test.go @@ -2,6 +2,8 @@ package controllers import ( "errors" + "fmt" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -64,3 +66,177 @@ func TestSetStatusProgressing(t *testing.T) { }) } } + +func TestTruncateMessage(t *testing.T) { + tests := []struct { + name string + message string + expected string + }{ + { + name: "short message unchanged", + message: "This is a short message", + expected: "This is a short message", + }, + { + name: "empty message unchanged", + message: "", + expected: "", + }, + { + name: "exact max length message unchanged", + message: strings.Repeat("a", maxConditionMessageLength), + expected: strings.Repeat("a", maxConditionMessageLength), + }, + { + name: "message just over limit gets truncated", + message: strings.Repeat("a", maxConditionMessageLength+1), + expected: strings.Repeat("a", maxConditionMessageLength-len(truncationSuffix)) + truncationSuffix, + }, + { + name: "very long message gets truncated", + message: strings.Repeat("word ", 10000) + "finalword", + expected: strings.Repeat("word ", 10000)[:maxConditionMessageLength-len(truncationSuffix)] + truncationSuffix, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := truncateMessage(tc.message) + require.Equal(t, tc.expected, result) + + // Verify the result is within the limit + require.LessOrEqual(t, len(result), maxConditionMessageLength, + "truncated message should not exceed max length") + + // If the original message was over the limit, verify truncation occurred + if len(tc.message) > maxConditionMessageLength { + require.Contains(t, result, truncationSuffix, + "long messages should contain truncation suffix") + require.Less(t, len(result), len(tc.message), + "truncated message should be shorter than original") + } + }) + } +} + +func TestSetStatusProgressingWithLongMessage(t *testing.T) { + // Simulate a real ClusterExtension CRD upgrade safety check failure with many validation errors + longError := fmt.Sprintf("validating CRD upgrade safety for ClusterExtension 'my-operator': %s", + strings.Repeat("CRD \"myresources.example.com\" v1beta1->v1: field .spec.replicas changed from optional to required, field .spec.config.timeout type changed from string to integer, field .status.conditions[].observedGeneration removed\n", 500)) + + ext := &ocv1.ClusterExtension{ObjectMeta: metav1.ObjectMeta{Name: "my-operator"}} + err := errors.New(longError) + setStatusProgressing(ext, err) + + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, cond) + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength) + require.Contains(t, cond.Message, truncationSuffix) + require.Contains(t, cond.Message, "validating CRD upgrade safety") +} + +func TestClusterExtensionDeprecationMessageTruncation(t *testing.T) { + // Test truncation for ClusterExtension deprecation warnings with many deprecated APIs + ext := &ocv1.ClusterExtension{ObjectMeta: metav1.ObjectMeta{Name: "legacy-operator"}} + + // Simulate many deprecation warnings that would overflow the message limit + deprecationMessages := []string{} + for i := 0; i < 1000; i++ { + deprecationMessages = append(deprecationMessages, fmt.Sprintf("API version 'v1beta1' of resource 'customresources%d.example.com' is deprecated, use 'v1' instead", i)) + } + + longDeprecationMsg := strings.Join(deprecationMessages, "; ") + setInstalledStatusConditionUnknown(ext, longDeprecationMsg) + + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, cond) + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength) + require.Contains(t, cond.Message, truncationSuffix, "deprecation messages should be truncated when too long") + require.Contains(t, cond.Message, "API version", "should preserve important deprecation context") +} + +func TestClusterExtensionInstallationFailureTruncation(t *testing.T) { + // Test truncation for ClusterExtension installation failures with many bundle validation errors + installError := "failed to install ClusterExtension 'argocd-operator': bundle validation errors: " + + strings.Repeat("resource 'deployments/argocd-server' missing required label 'app.kubernetes.io/name', resource 'services/argocd-server-metrics' has invalid port configuration, resource 'configmaps/argocd-cm' contains invalid YAML in data field 'application.yaml'\n", 400) + + ext := &ocv1.ClusterExtension{ObjectMeta: metav1.ObjectMeta{Name: "argocd-operator"}} + setInstalledStatusConditionFailed(ext, installError) + + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, cond) + + // Verify message was truncated due to length + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength) + require.Contains(t, cond.Message, truncationSuffix, "installation failure messages should be truncated when too long") + require.Contains(t, cond.Message, "failed to install ClusterExtension", "should preserve important context") + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, ocv1.ReasonFailed, cond.Reason) + + // Verify original message was actually longer than the limit + require.Greater(t, len(installError), maxConditionMessageLength, "test should use a message that exceeds the limit") +} + +func TestSetStatusConditionWrapper(t *testing.T) { + tests := []struct { + name string + message string + expectedTruncated bool + }{ + { + name: "short message not truncated", + message: "This is a short message", + expectedTruncated: false, + }, + { + name: "long message gets truncated", + message: strings.Repeat("This is a very long message. ", 2000), + expectedTruncated: true, + }, + { + name: "message at exact limit not truncated", + message: strings.Repeat("a", maxConditionMessageLength), + expectedTruncated: false, + }, + { + name: "message over limit gets truncated", + message: strings.Repeat("a", maxConditionMessageLength+1), + expectedTruncated: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var conditions []metav1.Condition + + // Use our wrapper function + SetStatusCondition(&conditions, metav1.Condition{ + Type: "TestCondition", + Status: metav1.ConditionTrue, + Reason: "Testing", + Message: tc.message, + }) + + require.Len(t, conditions, 1, "should have exactly one condition") + cond := conditions[0] + + // Verify message is within limits + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength, + "condition message should not exceed max length") + + // Check if truncation occurred as expected + if tc.expectedTruncated { + require.Contains(t, cond.Message, truncationSuffix, + "long messages should contain truncation suffix") + require.Less(t, len(cond.Message), len(tc.message), + "truncated message should be shorter than original") + } else { + require.Equal(t, tc.message, cond.Message, + "short messages should remain unchanged") + require.NotContains(t, cond.Message, truncationSuffix, + "short messages should not contain truncation suffix") + } + }) + } +} diff --git a/internal/operator-controller/rukpak/bundle/registryv1.go b/internal/operator-controller/rukpak/bundle/registryv1.go index bc757e63d6..cffc374e91 100644 --- a/internal/operator-controller/rukpak/bundle/registryv1.go +++ b/internal/operator-controller/rukpak/bundle/registryv1.go @@ -7,6 +7,10 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" ) +const ( + BundleConfigWatchNamespaceKey = "watchNamespace" +) + type RegistryV1 struct { PackageName string CSV v1alpha1.ClusterServiceVersion diff --git a/internal/operator-controller/rukpak/convert/helm.go b/internal/operator-controller/rukpak/convert/helm.go index 786601fdf8..4a354b5697 100644 --- a/internal/operator-controller/rukpak/convert/helm.go +++ b/internal/operator-controller/rukpak/convert/helm.go @@ -7,6 +7,7 @@ import ( "helm.sh/helm/v3/pkg/chart" + bundlepkg "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" ) @@ -17,12 +18,21 @@ type BundleToHelmChartConverter struct { IsWebhookSupportEnabled bool } -func (r *BundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) { +func (r *BundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, installNamespace string, config map[string]interface{}) (*chart.Chart, error) { rv1, err := bundle.GetBundle() if err != nil { return nil, err } + opts := []render.Option{ + render.WithCertificateProvider(r.CertificateProvider), + } + if config != nil { + if watchNs, ok := config[bundlepkg.BundleConfigWatchNamespaceKey].(string); ok { + opts = append(opts, render.WithTargetNamespaces(watchNs)) + } + } + if len(rv1.CSV.Spec.APIServiceDefinitions.Owned) > 0 { return nil, fmt.Errorf("unsupported bundle: apiServiceDefintions are not supported") } @@ -39,11 +49,7 @@ func (r *BundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, ins return nil, fmt.Errorf("unsupported bundle: webhookDefinitions are not supported") } - objs, err := r.BundleRenderer.Render( - rv1, installNamespace, - render.WithTargetNamespaces(watchNamespace), - render.WithCertificateProvider(r.CertificateProvider), - ) + objs, err := r.BundleRenderer.Render(rv1, installNamespace, opts...) if err != nil { return nil, fmt.Errorf("error rendering bundle: %w", err) diff --git a/internal/operator-controller/rukpak/convert/helm_test.go b/internal/operator-controller/rukpak/convert/helm_test.go index fdfa9812b0..0151f73056 100644 --- a/internal/operator-controller/rukpak/convert/helm_test.go +++ b/internal/operator-controller/rukpak/convert/helm_test.go @@ -24,7 +24,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_ReturnsBundleSourceFailures(t * var failingBundleSource FakeBundleSource = func() (bundle.RegistryV1, error) { return bundle.RegistryV1{}, errors.New("some error") } - _, err := converter.ToHelmChart(failingBundleSource, "install-namespace", "watch-namespace") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "watch-namespace", + } + _, err := converter.ToHelmChart(failingBundleSource, "install-namespace", config) require.Error(t, err) require.Contains(t, err.Error(), "some error") } @@ -46,7 +49,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_ReturnsBundleRendererFailures(t }, ) - _, err := converter.ToHelmChart(b, "install-namespace", "") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "", + } + _, err := converter.ToHelmChart(b, "install-namespace", config) require.Error(t, err) require.Contains(t, err.Error(), "some error") } @@ -60,7 +66,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_NoAPIServiceDefinitions(t *test }, ) - _, err := converter.ToHelmChart(b, "install-namespace", "") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "", + } + _, err := converter.ToHelmChart(b, "install-namespace", config) require.Error(t, err) require.Contains(t, err.Error(), "unsupported bundle: apiServiceDefintions are not supported") } @@ -76,7 +85,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_NoWebhooksWithoutCertProvider(t }, ) - _, err := converter.ToHelmChart(b, "install-namespace", "") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "", + } + _, err := converter.ToHelmChart(b, "install-namespace", config) require.Error(t, err) require.Contains(t, err.Error(), "webhookDefinitions are not supported") } @@ -92,7 +104,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_WebhooksSupportDisabled(t *test }, ) - _, err := converter.ToHelmChart(b, "install-namespace", "") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "", + } + _, err := converter.ToHelmChart(b, "install-namespace", config) require.Error(t, err) require.Contains(t, err.Error(), "webhookDefinitions are not supported") } @@ -112,7 +127,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_WebhooksWithCertProvider(t *tes }, ) - _, err := converter.ToHelmChart(b, "install-namespace", "") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "", + } + _, err := converter.ToHelmChart(b, "install-namespace", config) require.NoError(t, err) } @@ -142,7 +160,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_BundleRendererIntegration(t *te }, ) - _, err := converter.ToHelmChart(b, expectedInstallNamespace, expectedWatchNamespace) + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: expectedWatchNamespace, + } + _, err := converter.ToHelmChart(b, expectedInstallNamespace, config) require.NoError(t, err) } @@ -181,7 +202,10 @@ func Test_BundleToHelmChartConverter_ToHelmChart_Success(t *testing.T) { }, ) - chart, err := converter.ToHelmChart(b, "install-namespace", "") + config := map[string]interface{}{ + bundle.BundleConfigWatchNamespaceKey: "", + } + chart, err := converter.ToHelmChart(b, "install-namespace", config) require.NoError(t, err) require.NotNil(t, chart) require.NotNil(t, chart.Metadata) diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go index fadc858737..46c5e674d0 100644 --- a/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go @@ -15,6 +15,7 @@ import ( "sigs.k8s.io/crdify/pkg/config" "sigs.k8s.io/crdify/pkg/runner" "sigs.k8s.io/crdify/pkg/validations" + "sigs.k8s.io/crdify/pkg/validations/property" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" ) @@ -130,6 +131,13 @@ func defaultConfig() *config.Config { Name: "description", Enforcement: config.EnforcementPolicyNone, }, + { + Name: "enum", + Enforcement: config.EnforcementPolicyError, + Configuration: map[string]interface{}{ + "additionPolicy": property.AdditionPolicyAllow, + }, + }, }, } } diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go index 73db9673be..d1bd539051 100644 --- a/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go @@ -38,7 +38,7 @@ func newMockPreflight(crd *apiextensionsv1.CustomResourceDefinition, err error) }, preflightOpts...) } -const crdFolder string = "../../../../../testdata/manifests" +const crdFolder string = "testdata/manifests" func getCrdFromManifestFile(t *testing.T, oldCrdFile string) *apiextensionsv1.CustomResourceDefinition { if oldCrdFile == "" { @@ -66,6 +66,14 @@ func getManifestString(t *testing.T, crdFile string) string { return string(buff) } +func wantErrorMsgs(wantMsgs []string) require.ErrorAssertionFunc { + return func(t require.TestingT, haveErr error, _ ...interface{}) { + for _, wantMsg := range wantMsgs { + require.ErrorContains(t, haveErr, wantMsg) + } + } +} + // TestInstall exists only for completeness as Install() is currently a no-op. It can be used as // a template for real tests in the future if the func is implemented. func TestInstall(t *testing.T) { @@ -73,7 +81,7 @@ func TestInstall(t *testing.T) { name string oldCrdPath string release *release.Release - wantErrMsgs []string + requireErr require.ErrorAssertionFunc wantCrdGetErr error }{ { @@ -91,7 +99,7 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: "abcd", }, - wantErrMsgs: []string{"json: cannot unmarshal string into Go value of type unstructured.detector"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal string into Go value of type unstructured.detector"}), }, { name: "release with no CRD objects", @@ -107,7 +115,7 @@ func TestInstall(t *testing.T) { Manifest: getManifestString(t, "crd-valid-upgrade.json"), }, wantCrdGetErr: fmt.Errorf("error!"), - wantErrMsgs: []string{"error!"}, + requireErr: wantErrorMsgs([]string{"error!"}), }, { name: "fail to get old crd, not found error", @@ -123,7 +131,7 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid"), }, - wantErrMsgs: []string{"json: cannot unmarshal"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal"}), }, { name: "valid upgrade", @@ -142,7 +150,7 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid-upgrade.json"), }, - wantErrMsgs: []string{ + requireErr: wantErrorMsgs([]string{ `scope:`, `storedVersionRemoval:`, `enum:`, @@ -156,7 +164,7 @@ func TestInstall(t *testing.T) { `minLength:`, `minProperties:`, `default:`, - }, + }), }, { name: "new crd validation failure for existing field removal", @@ -167,9 +175,9 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-field-removed.json"), }, - wantErrMsgs: []string{ + requireErr: wantErrorMsgs([]string{ `existingFieldRemoval:`, - }, + }), }, { name: "new crd validation should not fail on description changes", @@ -187,10 +195,8 @@ func TestInstall(t *testing.T) { t.Run(tc.name, func(t *testing.T) { preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr) err := preflight.Install(context.Background(), tc.release) - if len(tc.wantErrMsgs) != 0 { - for _, expectedErrMsg := range tc.wantErrMsgs { - require.ErrorContains(t, err, expectedErrMsg) - } + if tc.requireErr != nil { + tc.requireErr(t, err) } else { require.NoError(t, err) } @@ -203,7 +209,7 @@ func TestUpgrade(t *testing.T) { name string oldCrdPath string release *release.Release - wantErrMsgs []string + requireErr require.ErrorAssertionFunc wantCrdGetErr error }{ { @@ -221,7 +227,7 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: "abcd", }, - wantErrMsgs: []string{"json: cannot unmarshal string into Go value of type unstructured.detector"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal string into Go value of type unstructured.detector"}), }, { name: "release with no CRD objects", @@ -237,7 +243,7 @@ func TestUpgrade(t *testing.T) { Manifest: getManifestString(t, "crd-valid-upgrade.json"), }, wantCrdGetErr: fmt.Errorf("error!"), - wantErrMsgs: []string{"error!"}, + requireErr: wantErrorMsgs([]string{"error!"}), }, { name: "fail to get old crd, not found error", @@ -253,7 +259,7 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid"), }, - wantErrMsgs: []string{"json: cannot unmarshal"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal"}), }, { name: "valid upgrade", @@ -272,7 +278,7 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid-upgrade.json"), }, - wantErrMsgs: []string{ + requireErr: wantErrorMsgs([]string{ `scope:`, `storedVersionRemoval:`, `enum:`, @@ -286,7 +292,7 @@ func TestUpgrade(t *testing.T) { `minLength:`, `minProperties:`, `default:`, - }, + }), }, { name: "new crd validation failure for existing field removal", @@ -297,9 +303,9 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-field-removed.json"), }, - wantErrMsgs: []string{ + requireErr: wantErrorMsgs([]string{ `existingFieldRemoval:`, - }, + }), }, { name: "webhook conversion strategy exists", @@ -316,9 +322,9 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-conversion-no-webhook.json"), }, - wantErrMsgs: []string{ - `validating upgrade for CRD "crontabs.stable.example.com": v1 <-> v2: ^.spec.foobarbaz: enum: allowed enum values removed`, - }, + requireErr: wantErrorMsgs([]string{ + `validating upgrade for CRD "crontabs.stable.example.com": v1 -> v2: ^.spec.foobarbaz: enum: allowed enum values removed`, + }), }, { name: "new crd validation should not fail on description changes", @@ -330,16 +336,43 @@ func TestUpgrade(t *testing.T) { Manifest: getManifestString(t, "crd-description-changed.json"), }, }, + { + name: "success when old crd and new crd contain the exact same validation issues", + oldCrdPath: "crd-conversion-no-webhook.json", + release: &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-conversion-no-webhook.json"), + }, + }, + { + name: "failure when old crd and new crd contain the exact same validation issues, but new crd introduces another validation issue", + oldCrdPath: "crd-conversion-no-webhook.json", + release: &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-conversion-no-webhook-extra-issue.json"), + }, + requireErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorContains(t, err, + `validating upgrade for CRD "crontabs.stable.example.com":`, + ) + // The newly introduced issue is reported + require.Contains(t, err.Error(), + `v1 -> v2: ^.spec.extraField: type: type changed : "boolean" -> "string"`, + ) + // The existing issue is not reported + require.NotContains(t, err.Error(), + `v1 -> v2: ^.spec.foobarbaz: enum: allowed enum values removed`, + ) + }, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr) err := preflight.Upgrade(context.Background(), tc.release) - if len(tc.wantErrMsgs) != 0 { - for _, expectedErrMsg := range tc.wantErrMsgs { - require.ErrorContains(t, err, expectedErrMsg) - } + if tc.requireErr != nil { + tc.requireErr(t, err) } else { require.NoError(t, err) } diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook-extra-issue.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook-extra-issue.json new file mode 100644 index 0000000000..0bfd133843 --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook-extra-issue.json @@ -0,0 +1,76 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "name": "crontabs.stable.example.com" + }, + "spec": { + "group": "stable.example.com", + "versions": [ + { + "name": "v2", + "served": true, + "storage": false, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "foobarbaz": { + "type":"string", + "enum":[ + "bark", + "woof" + ] + }, + "extraField": { + "type":"string" + } + } + } + } + } + } + }, + { + "name": "v1", + "served": true, + "storage": false, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "foobarbaz": { + "type":"string", + "enum":[ + "foo", + "bar", + "baz" + ] + }, + "extraField": { + "type":"boolean" + } + } + } + } + } + } + } + ], + "scope": "Cluster", + "names": { + "plural": "crontabs", + "singular": "crontab", + "kind": "CronTab", + "shortNames": [ + "ct" + ] + } + } +} diff --git a/testdata/manifests/crd-conversion-no-webhook.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook.json similarity index 100% rename from testdata/manifests/crd-conversion-no-webhook.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook.json diff --git a/testdata/manifests/crd-conversion-webhook-old.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook-old.json similarity index 100% rename from testdata/manifests/crd-conversion-webhook-old.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook-old.json diff --git a/testdata/manifests/crd-conversion-webhook.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook.json similarity index 100% rename from testdata/manifests/crd-conversion-webhook.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook.json diff --git a/testdata/manifests/crd-description-changed.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-description-changed.json similarity index 94% rename from testdata/manifests/crd-description-changed.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-description-changed.json index ae30459e32..0e7f9a600b 100644 --- a/testdata/manifests/crd-description-changed.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-description-changed.json @@ -23,7 +23,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" @@ -70,7 +71,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" diff --git a/testdata/manifests/crd-field-removed.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-field-removed.json similarity index 96% rename from testdata/manifests/crd-field-removed.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-field-removed.json index 86ba06e409..650b13fd48 100644 --- a/testdata/manifests/crd-field-removed.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-field-removed.json @@ -22,7 +22,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" @@ -66,7 +67,8 @@ "type": "object", "properties": { "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" diff --git a/testdata/manifests/crd-invalid b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid similarity index 100% rename from testdata/manifests/crd-invalid rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid diff --git a/testdata/manifests/crd-invalid-upgrade.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid-upgrade.json similarity index 92% rename from testdata/manifests/crd-invalid-upgrade.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid-upgrade.json index 4131a68fbf..3c95ccb25a 100644 --- a/testdata/manifests/crd-invalid-upgrade.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid-upgrade.json @@ -24,11 +24,8 @@ "type":"integer" }, "enum": { - "type":"integer", - "enum":[ - 1, - 2 - ] + "type": "string", + "enum": ["a", "b"] }, "minMaxValue": { "type":"integer", diff --git a/testdata/manifests/crd-valid-upgrade.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-valid-upgrade.json similarity index 93% rename from testdata/manifests/crd-valid-upgrade.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-valid-upgrade.json index 52380dc92b..cbc2e3ec14 100644 --- a/testdata/manifests/crd-valid-upgrade.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-valid-upgrade.json @@ -22,7 +22,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c", "adding-enum-is-allowed"] }, "minMaxValue": { "type":"integer" @@ -69,7 +70,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c", "adding-enum-is-allowed"] }, "minMaxValue": { "type":"integer" @@ -116,7 +118,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c", "adding-enum-is-allowed"] }, "minMaxValue": { "type":"integer" diff --git a/testdata/manifests/no-crds.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/no-crds.json similarity index 100% rename from testdata/manifests/no-crds.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/no-crds.json diff --git a/testdata/manifests/old-crd.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/old-crd.json similarity index 94% rename from testdata/manifests/old-crd.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/old-crd.json index 1f3ff5a4b9..5a8c55b321 100644 --- a/testdata/manifests/old-crd.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/old-crd.json @@ -23,7 +23,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" @@ -70,7 +71,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" diff --git a/internal/operator-controller/rukpak/render/render.go b/internal/operator-controller/rukpak/render/render.go index 70063f1d48..384b16796c 100644 --- a/internal/operator-controller/rukpak/render/render.go +++ b/internal/operator-controller/rukpak/render/render.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" @@ -74,9 +74,6 @@ func (o *Options) apply(opts ...Option) *Options { func (o *Options) validate(rv1 *bundle.RegistryV1) (*Options, []error) { var errs []error - if len(o.TargetNamespaces) == 0 { - errs = append(errs, errors.New("at least one target namespace must be specified")) - } if o.UniqueNameGenerator == nil { errs = append(errs, errors.New("unique name generator must be specified")) } @@ -88,9 +85,14 @@ func (o *Options) validate(rv1 *bundle.RegistryV1) (*Options, []error) { type Option func(*Options) +// WithTargetNamespaces sets the target namespaces to be used when rendering the bundle +// The value will only be used if len(namespaces) > 0. Otherwise, the default value for the bundle +// derived from its install mode support will be used (if such a value can be defined). func WithTargetNamespaces(namespaces ...string) Option { return func(o *Options) { - o.TargetNamespaces = namespaces + if len(namespaces) > 0 { + o.TargetNamespaces = namespaces + } } } @@ -121,7 +123,7 @@ func (r BundleRenderer) Render(rv1 bundle.RegistryV1, installNamespace string, o genOpts, errs := (&Options{ // default options InstallNamespace: installNamespace, - TargetNamespaces: []string{metav1.NamespaceAll}, + TargetNamespaces: defaultTargetNamespacesForBundle(&rv1, installNamespace), UniqueNameGenerator: DefaultUniqueNameGenerator, CertificateProvider: nil, }).apply(opts...).validate(&rv1) @@ -147,31 +149,69 @@ func DefaultUniqueNameGenerator(base string, o interface{}) (string, error) { } func validateTargetNamespaces(rv1 *bundle.RegistryV1, installNamespace string, targetNamespaces []string) error { - supportedInstallModes := sets.New[string]() - for _, im := range rv1.CSV.Spec.InstallModes { - if im.Supported { - supportedInstallModes.Insert(string(im.Type)) - } - } + supportedInstallModes := supportedBundleInstallModes(rv1) set := sets.New[string](targetNamespaces...) switch { - case set.Len() == 0 || (set.Len() == 1 && set.Has("")): - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeAllNamespaces)) { + case set.Len() == 0: + // Note: this function generally expects targetNamespace to contain at least one value set by default + // in case the user does not specify the value. The option to set the targetNamespace is a no-op if it is empty. + // The only case for which a default targetNamespace is undefined is in the case of a bundle that only + // supports SingleNamespace install mode. The if statement here is added to provide a more friendly error + // message than just the generic (at least one target namespace must be specified) which would occur + // in case only the MultiNamespace install mode is supported by the bundle. + // If AllNamespaces mode is supported, the default will be [""] -> watch all namespaces + // If only OwnNamespace is supported, the default will be [install-namespace] -> only watch the install/own namespace + if supportedInstallModes.Len() == 1 && supportedInstallModes.Has(v1alpha1.InstallModeTypeSingleNamespace) { + return errors.New("exactly one target namespace must be specified") + } + return errors.New("at least one target namespace must be specified") + case set.Len() == 1 && set.Has(""): + if supportedInstallModes.Has(v1alpha1.InstallModeTypeAllNamespaces) { return nil } return fmt.Errorf("supported install modes %v do not support targeting all namespaces", sets.List(supportedInstallModes)) case set.Len() == 1 && !set.Has(""): - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeSingleNamespace)) { + if targetNamespaces[0] == installNamespace { + if !supportedInstallModes.Has(v1alpha1.InstallModeTypeOwnNamespace) { + return fmt.Errorf("supported install modes %v do not support targeting own namespace", sets.List(supportedInstallModes)) + } return nil } - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeOwnNamespace)) && targetNamespaces[0] == installNamespace { + if supportedInstallModes.Has(v1alpha1.InstallModeTypeSingleNamespace) { return nil } default: - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeMultiNamespace)) && !set.Has("") { + if !supportedInstallModes.Has(v1alpha1.InstallModeTypeOwnNamespace) && set.Has(installNamespace) { + return fmt.Errorf("supported install modes %v do not support targeting own namespace", sets.List(supportedInstallModes)) + } + if supportedInstallModes.Has(v1alpha1.InstallModeTypeMultiNamespace) && !set.Has("") { return nil } } - return fmt.Errorf("supported install modes %v do not support target namespaces %v", sets.List[string](supportedInstallModes), targetNamespaces) + return fmt.Errorf("supported install modes %v do not support target namespaces %v", sets.List[v1alpha1.InstallModeType](supportedInstallModes), targetNamespaces) +} + +func defaultTargetNamespacesForBundle(rv1 *bundle.RegistryV1, installNamespace string) []string { + supportedInstallModes := supportedBundleInstallModes(rv1) + + if supportedInstallModes.Has(v1alpha1.InstallModeTypeAllNamespaces) { + return []string{corev1.NamespaceAll} + } + + if supportedInstallModes.Has(v1alpha1.InstallModeTypeOwnNamespace) { + return []string{installNamespace} + } + + return nil +} + +func supportedBundleInstallModes(rv1 *bundle.RegistryV1) sets.Set[v1alpha1.InstallModeType] { + supportedInstallModes := sets.New[v1alpha1.InstallModeType]() + for _, im := range rv1.CSV.Spec.InstallModes { + if im.Supported { + supportedInstallModes.Insert(im.Type) + } + } + return supportedInstallModes } diff --git a/internal/operator-controller/rukpak/render/render_test.go b/internal/operator-controller/rukpak/render/render_test.go index 0760c4fa10..0461ea3bea 100644 --- a/internal/operator-controller/rukpak/render/render_test.go +++ b/internal/operator-controller/rukpak/render/render_test.go @@ -70,13 +70,12 @@ func Test_BundleRenderer_ValidatesRenderOptions(t *testing.T) { err error }{ { - name: "rejects empty targetNamespaces", + name: "accepts empty targetNamespaces (because it is ignored)", installNamespace: "install-namespace", csv: MakeCSV(WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces)), opts: []render.Option{ render.WithTargetNamespaces(), }, - err: errors.New("invalid option(s): at least one target namespace must be specified"), }, { name: "rejects nil unique name generator", installNamespace: "install-namespace", @@ -100,7 +99,7 @@ func Test_BundleRenderer_ValidatesRenderOptions(t *testing.T) { opts: []render.Option{ render.WithTargetNamespaces("install-namespace"), }, - err: errors.New("invalid option(s): invalid target namespaces [install-namespace]: supported install modes [AllNamespaces] do not support target namespaces [install-namespace]"), + err: errors.New("invalid option(s): invalid target namespaces [install-namespace]: supported install modes [AllNamespaces] do not support targeting own namespace"), }, { name: "rejects install out of own namespace if only OwnNamespace install mode is supported", installNamespace: "install-namespace", @@ -160,6 +159,14 @@ func Test_BundleRenderer_ValidatesRenderOptions(t *testing.T) { opts: []render.Option{ render.WithTargetNamespaces("n1", "n2", "n3"), }, + }, { + name: "reject multi namespace render if OwnNamespace install mode is not supported and target namespaces include install namespace", + installNamespace: "install-namespace", + csv: MakeCSV(WithInstallModeSupportFor(v1alpha1.InstallModeTypeMultiNamespace)), + opts: []render.Option{ + render.WithTargetNamespaces("n1", "n2", "n3", "install-namespace"), + }, + err: errors.New("invalid option(s): invalid target namespaces [n1 n2 n3 install-namespace]: supported install modes [MultiNamespace] do not support targeting own namespace"), }, } { t.Run(tc.name, func(t *testing.T) { diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index a91833bd75..36ef3661aa 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -511,6 +511,40 @@ spec: description: spec is an optional field that defines the desired state of the ClusterExtension. properties: + config: + description: |- + config contains optional configuration values applied during rendering of the + ClusterExtension's manifests. Values can be specified inline. + + config is optional. When not specified, the default configuration of the resolved bundle will be used. + properties: + configType: + description: |- + configType is a required reference to the type of configuration source. + + Allowed values are "Inline" + + When this field is set to "Inline", the cluster extension configuration is defined inline within the + ClusterExtension resource. + enum: + - Inline + type: string + inline: + description: |- + inline contains JSON or YAML values specified directly in the + ClusterExtension. + + inline must be set if configType is 'Inline'. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configType + type: object + x-kubernetes-validations: + - message: inline is required when configType is Inline, and forbidden + otherwise + rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline) + : !has(self.inline)' install: description: |- install is an optional field used to configure the installation options diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index 00dc141531..291f34cffa 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -511,6 +511,40 @@ spec: description: spec is an optional field that defines the desired state of the ClusterExtension. properties: + config: + description: |- + config contains optional configuration values applied during rendering of the + ClusterExtension's manifests. Values can be specified inline. + + config is optional. When not specified, the default configuration of the resolved bundle will be used. + properties: + configType: + description: |- + configType is a required reference to the type of configuration source. + + Allowed values are "Inline" + + When this field is set to "Inline", the cluster extension configuration is defined inline within the + ClusterExtension resource. + enum: + - Inline + type: string + inline: + description: |- + inline contains JSON or YAML values specified directly in the + ClusterExtension. + + inline must be set if configType is 'Inline'. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configType + type: object + x-kubernetes-validations: + - message: inline is required when configType is Inline, and forbidden + otherwise + rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline) + : !has(self.inline)' install: description: |- install is an optional field used to configure the installation options diff --git a/mkdocs.yml b/mkdocs.yml index f7b20ae070..e891896222 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,8 +49,7 @@ nav: - Content Resolution: concepts/controlling-catalog-selection.md - Version Ranges: concepts/version-ranges.md - API Reference: - - Operator Controller API reference: api-reference/operator-controller-api-reference.md - - CatalogD API reference: api-reference/catalogd-api-reference.md + - OLMv1 API reference: api-reference/olmv1-api-reference.md - CatalogD Web Server reference: api-reference/catalogd-webserver.md - Contribute: - Contributing: contribute/contributing.md diff --git a/requirements.txt b/requirements.txt index 241ff2c8ae..aca833e67d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Babel==2.17.0 -beautifulsoup4==4.13.4 +beautifulsoup4==4.13.5 certifi==2025.8.3 charset-normalizer==3.4.3 click==8.1.8 @@ -8,18 +8,18 @@ cssselect==1.3.0 ghp-import==2.1.0 idna==3.10 Jinja2==3.1.6 -lxml==6.0.0 +lxml==6.0.1 Markdown==3.8.2 markdown2==2.5.4 MarkupSafe==3.0.2 mergedeep==1.3.4 mkdocs==1.6.1 -mkdocs-material==9.6.16 +mkdocs-material==9.6.18 mkdocs-material-extensions==1.3.1 packaging==25.0 paginate==0.5.7 pathspec==0.12.1 -platformdirs==4.3.8 +platformdirs==4.4.0 Pygments==2.19.2 pymdown-extensions==10.16.1 pyquery==2.0.1 @@ -27,9 +27,9 @@ python-dateutil==2.9.0.post0 PyYAML==6.0.2 pyyaml_env_tag==1.1 readtime==3.0.0 -regex==2025.7.34 -requests==2.32.4 +regex==2025.9.1 +requests==2.32.5 six==1.17.0 -soupsieve==2.7 +soupsieve==2.8 urllib3==2.5.0 watchdog==6.0.0 diff --git a/test/experimental-e2e/experimental_e2e_test.go b/test/experimental-e2e/experimental_e2e_test.go index 39c16e97f8..eba429b913 100644 --- a/test/experimental-e2e/experimental_e2e_test.go +++ b/test/experimental-e2e/experimental_e2e_test.go @@ -248,6 +248,147 @@ func TestWebhookSupport(t *testing.T) { }, res.Object["spec"]) } +func TestClusterExtensionConfigSupport(t *testing.T) { + t.Log("Test support for cluster extension config") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + t.Log("By creating install namespace, watch namespace and necessary rbac resources") + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator", + }, + } + require.NoError(t, c.Create(t.Context(), &namespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &namespace)) + }) + + watchNamespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator-watch", + }, + } + require.NoError(t, c.Create(t.Context(), &watchNamespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &watchNamespace)) + }) + + serviceAccount := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator-installer", + Namespace: namespace.GetName(), + }, + } + require.NoError(t, c.Create(t.Context(), &serviceAccount)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &serviceAccount)) + }) + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator-installer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: corev1.GroupName, + Name: serviceAccount.GetName(), + Namespace: serviceAccount.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + } + require.NoError(t, c.Create(t.Context(), clusterRoleBinding)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterRoleBinding)) + }) + + t.Log("By creating the test-operator ClusterCatalog") + extensionCatalog := &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator-catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("CLUSTER_REGISTRY_HOST")), + PollIntervalMinutes: ptr.To(1), + }, + }, + }, + } + require.NoError(t, c.Create(t.Context(), extensionCatalog)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), extensionCatalog)) + }) + + t.Log("By waiting for the catalog to serve its metadata") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By installing the test-operator ClusterExtension configured in SingleNamespace mode") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator-extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, + }, + Namespace: namespace.GetName(), + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount.GetName(), + }, + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, watchNamespace.GetName())), + }, + }, + }, + } + require.NoError(t, c.Create(t.Context(), clusterExtension)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterExtension)) + }) + + t.Log("By waiting for test-operator extension to be installed successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotNil(ct, clusterExtension.Status.Install) + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + }, pollDuration, pollInterval) + + t.Log("By ensuring the test-operator deployment is correctly configured to watch the watch namespace") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + deployment := &appsv1.Deployment{} + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "test-operator"}, deployment)) + require.NotNil(ct, deployment.Spec.Template.GetAnnotations()) + require.Equal(ct, watchNamespace.GetName(), deployment.Spec.Template.GetAnnotations()["olm.targetNamespaces"]) + }, pollDuration, pollInterval) +} + func getWebhookOperatorResource(name string, namespace string, valid bool) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ diff --git a/testdata/images/bundles/test-operator/v1.0.0/manifests/testoperator.clusterserviceversion.yaml b/testdata/images/bundles/test-operator/v1.0.0/manifests/testoperator.clusterserviceversion.yaml index 3520f53dbd..54924492b2 100644 --- a/testdata/images/bundles/test-operator/v1.0.0/manifests/testoperator.clusterserviceversion.yaml +++ b/testdata/images/bundles/test-operator/v1.0.0/manifests/testoperator.clusterserviceversion.yaml @@ -130,7 +130,7 @@ spec: installModes: - supported: false type: OwnNamespace - - supported: false + - supported: true type: SingleNamespace - supported: false type: MultiNamespace