From 0b6533076b75e6f1eef0276b373992939fb1cdc2 Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Tue, 28 Oct 2025 15:13:02 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"=E2=9C=A8=20Promote=20Single=20Own=20?= =?UTF-8?q?Feature=20Gate=20AND=20Config=20spec=20in=20the=20CR=20to=20GA?= =?UTF-8?q?=20(OPRUN-4098)=20(#2268)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 3c2fcb4c76acfea4f7ab5ba3b92adfdfecb23d57. Signed-off-by: Per G. da Silva --- api/v1/clusterextension_types.go | 1 + docs/api-reference/olmv1-api-reference.md | 2 +- .../howto/single-ownnamespace-install.md | 25 +++++- ...xplore-available-content-metas-endpoint.md | 3 + docs/project/olmv1_limitations.md | 2 +- docs/tutorials/explore-available-content.md | 3 + hack/demo/own-namespace-demo-script.sh | 23 +++-- hack/demo/single-namespace-demo-script.sh | 23 +++-- helm/experimental.yaml | 1 + ...peratorframework.io_clusterextensions.yaml | 39 -------- helm/tilt.yaml | 1 + .../operator-controller/features/features.go | 4 +- manifests/experimental-e2e.yaml | 1 + manifests/experimental.yaml | 1 + manifests/standard-e2e.yaml | 39 -------- manifests/standard.yaml | 39 -------- .../boxcutter_support_test.go | 70 --------------- test/experimental-e2e/experimental_test.go | 43 --------- .../single_namespace_support_test.go | 90 ++++++++++++++++++- 19 files changed, 164 insertions(+), 246 deletions(-) delete mode 100644 test/experimental-e2e/boxcutter_support_test.go delete mode 100644 test/experimental-e2e/experimental_test.go rename test/{e2e => experimental-e2e}/single_namespace_support_test.go (80%) diff --git a/api/v1/clusterextension_types.go b/api/v1/clusterextension_types.go index 343e8cbb4a..6de62b0e12 100644 --- a/api/v1/clusterextension_types.go +++ b/api/v1/clusterextension_types.go @@ -106,6 +106,7 @@ type ClusterExtensionSpec struct { // a configuration schema the final manifests will be derived on a best-effort basis. More information on how // to configure the bundle should be found in its end-user documentation. // + // // +optional Config *ClusterExtensionConfig `json:"config,omitempty"` } diff --git a/docs/api-reference/olmv1-api-reference.md b/docs/api-reference/olmv1-api-reference.md index c0da40c4bc..b21e404520 100644 --- a/docs/api-reference/olmv1-api-reference.md +++ b/docs/api-reference/olmv1-api-reference.md @@ -343,7 +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 is an optional field used to specify bundle specific configuration
used to configure the bundle. Configuration is bundle specific and a bundle may provide
a configuration schema. When not specified, the default configuration of the resolved bundle will be used.

config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide
a configuration schema the final manifests will be derived on a best-effort basis. More information on how
to configure the bundle should be found in its end-user documentation. | | | +| `config` _[ClusterExtensionConfig](#clusterextensionconfig)_ | config is an optional field used to specify bundle specific configuration
used to configure the bundle. Configuration is bundle specific and a bundle may provide
a configuration schema. When not specified, the default configuration of the resolved bundle will be used.

config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide
a configuration schema the final manifests will be derived on a best-effort basis. More information on how
to configure the bundle should be found in its end-user documentation.

| | | #### ClusterExtensionStatus diff --git a/docs/draft/howto/single-ownnamespace-install.md b/docs/draft/howto/single-ownnamespace-install.md index 4152946871..8e6f00fe49 100644 --- a/docs/draft/howto/single-ownnamespace-install.md +++ b/docs/draft/howto/single-ownnamespace-install.md @@ -1,7 +1,8 @@ ## Description !!! note -The `SingleOwnNamespaceInstallSupport` feature-gate is enabled by default. Use this guide to configure bundles that need Single or Own namespace install modes. +This feature is still in *alpha* the `SingleOwnNamespaceInstallSupport` feature-gate must be enabled to make use of it. +See the instructions below on how to enable it. --- @@ -30,6 +31,28 @@ include *installModes*. [![OwnNamespace Install Demo](https://asciinema.org/a/Rxx6WUwAU016bXFDW74XLcM5i.svg)](https://asciinema.org/a/Rxx6WUwAU016bXFDW74XLcM5i) +## Enabling the Feature-Gate + +!!! tip + +This guide assumes OLMv1 is already installed. If that is not the case, +you can follow the [getting started](../../getting-started/olmv1_getting_started.md) guide to install OLMv1. + +--- + +Patch the `operator-controller` `Deployment` adding `--feature-gates=SingleOwnNamespaceInstallSupport=true` to the +controller container arguments: + +```terminal title="Enable SingleOwnNamespaceInstallSupport feature-gate" +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' +``` + +Wait for `Deployment` rollout: + +```terminal title="Wait for Deployment rollout" +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager +``` + ## Configuring the `ClusterExtension` A `ClusterExtension` can be configured to install bundle in `Single-` or `OwnNamespace` mode through the diff --git a/docs/draft/tutorials/explore-available-content-metas-endpoint.md b/docs/draft/tutorials/explore-available-content-metas-endpoint.md index 5d04b02df1..f17271d3e4 100644 --- a/docs/draft/tutorials/explore-available-content-metas-endpoint.md +++ b/docs/draft/tutorials/explore-available-content-metas-endpoint.md @@ -91,6 +91,9 @@ Then you can query the catalog by using `curl` commands and the `jq` CLI tool to ... ``` + !!! important + OLM 1.0 supports installing extensions that define webhooks. Targeting a single or specified set of namespaces requires enabling the `SingleOwnNamespaceInstallSupport` feature-gate. + 3. Return list of packages which support `AllNamespaces` install mode, do not use webhooks, and where the channel head version uses `olm.csv.metadata` format: ``` terminal diff --git a/docs/project/olmv1_limitations.md b/docs/project/olmv1_limitations.md index 54e174b4ca..01ce9436d3 100644 --- a/docs/project/olmv1_limitations.md +++ b/docs/project/olmv1_limitations.md @@ -8,7 +8,7 @@ hide: Currently, OLM v1 only supports installing operators packaged in [OLM v0 bundles](https://olm.operatorframework.io/docs/tasks/creating-operator-bundle/) , also known as `registry+v1` bundles. Additionally, the bundled operator, or cluster extension: -* **must** support installation via the `AllNamespaces`, `SingleNamespace`, or `OwnNamespace` install modes. +* **must** support installation via the `AllNamespaces` install mode * **must not** declare dependencies using any of the following file-based catalog properties: * `olm.gvk.required` * `olm.package.required` diff --git a/docs/tutorials/explore-available-content.md b/docs/tutorials/explore-available-content.md index 98bb7733c6..36e3cf8834 100644 --- a/docs/tutorials/explore-available-content.md +++ b/docs/tutorials/explore-available-content.md @@ -91,6 +91,9 @@ Then you can query the catalog by using `curl` commands and the `jq` CLI tool to ... ``` + !!! important + OLM 1.0 supports installing extensions that define webhooks. Targeting a single or specified set of namespaces requires enabling the `SingleOwnNamespaceInstallSupport` feature-gate. + 3. Return list of packages that support `AllNamespaces` install mode and do not use webhooks: ``` terminal diff --git a/hack/demo/own-namespace-demo-script.sh b/hack/demo/own-namespace-demo-script.sh index 86b3d28760..611c6dfb05 100755 --- a/hack/demo/own-namespace-demo-script.sh +++ b/hack/demo/own-namespace-demo-script.sh @@ -6,14 +6,16 @@ set -e trap 'echo "Demo ran into error"; trap - SIGTERM && kill -- -$$; exit 1' ERR SIGINT SIGTERM EXIT -# install standard CRDs -echo "Install standard CRDs..." -kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/standard.yaml" +# install experimental CRDs with config field support +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/experimental.yaml" -# wait for standard CRDs to be available +# wait for experimental CRDs to be available kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io -# Ensure controller is healthy +# enable 'SingleOwnNamespaceInstallSupport' feature gate +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' + +# wait for operator-controller to become available kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager # create install namespace @@ -55,6 +57,17 @@ kubectl delete clusterextension argocd-operator --ignore-not-found=true kubectl delete namespace argocd-system --ignore-not-found=true kubectl delete clusterrolebinding argocd-installer-crb --ignore-not-found=true +# remove feature gate from deployment +echo "Removing feature gate from operator-controller..." +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/args", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' || true + +# restore standard CRDs +echo "Restoring standard CRDs..." +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/base.yaml" + +# wait for standard CRDs to be available +kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io + # wait for operator-controller to become available with standard config kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager diff --git a/hack/demo/single-namespace-demo-script.sh b/hack/demo/single-namespace-demo-script.sh index 885854dd9d..9702684152 100755 --- a/hack/demo/single-namespace-demo-script.sh +++ b/hack/demo/single-namespace-demo-script.sh @@ -6,14 +6,16 @@ set -e trap 'echo "Demo ran into error"; trap - SIGTERM && kill -- -$$; exit 1' ERR SIGINT SIGTERM EXIT -# install standard CRDs -echo "Install standard CRDs..." -kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/standard.yaml" +# install experimental CRDs with config field support +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/experimental.yaml" -# wait for standard CRDs to be available +# wait for experimental CRDs to be available kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io -# Ensure controller is healthy +# enable 'SingleOwnNamespaceInstallSupport' feature gate +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' + +# wait for operator-controller to become available kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager # create install namespace @@ -58,6 +60,17 @@ kubectl delete clusterextension argocd-operator --ignore-not-found=true kubectl delete namespace argocd-system argocd --ignore-not-found=true kubectl delete clusterrolebinding argocd-installer-crb --ignore-not-found=true +# remove feature gate from deployment +echo "Removing feature gate from operator-controller..." +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/args", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' || true + +# restore standard CRDs +echo "Restoring standard CRDs..." +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/base.yaml" + +# wait for standard CRDs to be available +kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io + # wait for operator-controller to become available with standard config kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager diff --git a/helm/experimental.yaml b/helm/experimental.yaml index 1d27617149..b14b1b3034 100644 --- a/helm/experimental.yaml +++ b/helm/experimental.yaml @@ -9,6 +9,7 @@ options: operatorController: features: enabled: + - SingleOwnNamespaceInstallSupport - PreflightPermissions - HelmChartSupport - BoxcutterRuntime diff --git a/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml b/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml index 61337bad60..a0983e41f9 100644 --- a/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml +++ b/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml @@ -57,45 +57,6 @@ spec: description: spec is an optional field that defines the desired state of the ClusterExtension. properties: - config: - description: |- - config is an optional field used to specify bundle specific configuration - used to configure the bundle. Configuration is bundle specific and a bundle may provide - a configuration schema. When not specified, the default configuration of the resolved bundle will be used. - - config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide - a configuration schema the final manifests will be derived on a best-effort basis. More information on how - to configure the bundle should be found in its end-user documentation. - 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'. - inline accepts arbitrary JSON/YAML objects. - inline is validation at runtime against the schema provided by the bundle if a schema is provided. - 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/helm/tilt.yaml b/helm/tilt.yaml index 0fe3bec1f7..aaed7c71fb 100644 --- a/helm/tilt.yaml +++ b/helm/tilt.yaml @@ -14,6 +14,7 @@ options: operatorController: features: enabled: + - SingleOwnNamespaceInstallSupport - PreflightPermissions - HelmChartSupport disabled: diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go index 87e827c66f..4926ff8539 100644 --- a/internal/operator-controller/features/features.go +++ b/internal/operator-controller/features/features.go @@ -33,8 +33,8 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature // registry+v1 cluster extensions with single or own namespaces modes // i.e. with a single watch namespace. SingleOwnNamespaceInstallSupport: { - Default: true, - PreRelease: featuregate.GA, + Default: false, + PreRelease: featuregate.Alpha, LockToDefault: false, }, diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 5b09dc949c..1efa8b8d99 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -2188,6 +2188,7 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=:8443 - --leader-elect + - --feature-gates=SingleOwnNamespaceInstallSupport=true - --feature-gates=PreflightPermissions=true - --feature-gates=HelmChartSupport=true - --feature-gates=BoxcutterRuntime=true diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index 4ddd450779..664f8599cc 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -2101,6 +2101,7 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=:8443 - --leader-elect + - --feature-gates=SingleOwnNamespaceInstallSupport=true - --feature-gates=PreflightPermissions=true - --feature-gates=HelmChartSupport=true - --feature-gates=BoxcutterRuntime=true diff --git a/manifests/standard-e2e.yaml b/manifests/standard-e2e.yaml index 9c7a20f573..783beec515 100644 --- a/manifests/standard-e2e.yaml +++ b/manifests/standard-e2e.yaml @@ -648,45 +648,6 @@ spec: description: spec is an optional field that defines the desired state of the ClusterExtension. properties: - config: - description: |- - config is an optional field used to specify bundle specific configuration - used to configure the bundle. Configuration is bundle specific and a bundle may provide - a configuration schema. When not specified, the default configuration of the resolved bundle will be used. - - config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide - a configuration schema the final manifests will be derived on a best-effort basis. More information on how - to configure the bundle should be found in its end-user documentation. - 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'. - inline accepts arbitrary JSON/YAML objects. - inline is validation at runtime against the schema provided by the bundle if a schema is provided. - 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/standard.yaml b/manifests/standard.yaml index cf7eb0ee5c..95e400c264 100644 --- a/manifests/standard.yaml +++ b/manifests/standard.yaml @@ -613,45 +613,6 @@ spec: description: spec is an optional field that defines the desired state of the ClusterExtension. properties: - config: - description: |- - config is an optional field used to specify bundle specific configuration - used to configure the bundle. Configuration is bundle specific and a bundle may provide - a configuration schema. When not specified, the default configuration of the resolved bundle will be used. - - config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide - a configuration schema the final manifests will be derived on a best-effort basis. More information on how - to configure the bundle should be found in its end-user documentation. - 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'. - inline accepts arbitrary JSON/YAML objects. - inline is validation at runtime against the schema provided by the bundle if a schema is provided. - 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/test/experimental-e2e/boxcutter_support_test.go b/test/experimental-e2e/boxcutter_support_test.go deleted file mode 100644 index b09d19ec89..0000000000 --- a/test/experimental-e2e/boxcutter_support_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package experimental_e2e - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - ocv1 "github.com/operator-framework/operator-controller/api/v1" - utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" - . "github.com/operator-framework/operator-controller/test/helpers" -) - -func TestClusterExtensionVersionUpdate(t *testing.T) { - t.Log("When a cluster extension is installed from a catalog") - t.Log("When resolving upgrade edges") - - clusterExtension, extensionCatalog, sa, ns := TestInit(t) - defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) - defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - - t.Log("By creating an ClusterExtension at a specified version") - clusterExtension.Spec = ocv1.ClusterExtensionSpec{ - Source: ocv1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1.CatalogFilter{ - PackageName: "test", - Version: "1.0.0", - }, - }, - Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ - Name: sa.Name, - }, - } - require.NoError(t, c.Create(context.Background(), clusterExtension)) - t.Log("By eventually reporting a successful resolution") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) - require.NotNil(ct, cond) - require.Equal(ct, metav1.ConditionTrue, cond.Status) - require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) - }, pollDuration, pollInterval) - - t.Log("It allows to upgrade the ClusterExtension to a non-successor version") - t.Log("By forcing update of ClusterExtension resource to a non-successor version") - // 1.2.0 does not replace/skip/skipRange 1.0.0. - clusterExtension.Spec.Source.Catalog.Version = "1.2.0" - clusterExtension.Spec.Source.Catalog.UpgradeConstraintPolicy = ocv1.UpgradeConstraintPolicySelfCertified - require.NoError(t, c.Update(context.Background(), clusterExtension)) - t.Log("By eventually reporting a satisfiable resolution") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) - require.NotNil(ct, cond) - require.Equal(ct, metav1.ConditionTrue, cond.Status) - require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) - }, pollDuration, pollInterval) - t.Log("We should have two ClusterExtensionRevision resources") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - cerList := &ocv1.ClusterExtensionRevisionList{} - require.NoError(ct, c.List(context.Background(), cerList)) - require.Len(ct, cerList.Items, 2) - }, pollDuration, pollInterval) -} diff --git a/test/experimental-e2e/experimental_test.go b/test/experimental-e2e/experimental_test.go deleted file mode 100644 index de329b588e..0000000000 --- a/test/experimental-e2e/experimental_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package experimental_e2e - -import ( - "os" - "testing" - "time" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" - utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" -) - -const ( - artifactName = "operator-controller-experimental-e2e" - pollDuration = time.Minute - pollInterval = time.Second -) - -var ( - cfg *rest.Config - c client.Client -) - -func TestMain(m *testing.M) { - cfg = ctrl.GetConfigOrDie() - - var err error - utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme)) - c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - utilruntime.Must(err) - - os.Exit(m.Run()) -} - -func TestNoop(t *testing.T) { - t.Log("Running experimental-e2e tests") - defer utils.CollectTestArtifacts(t, artifactName, c, cfg) -} diff --git a/test/e2e/single_namespace_support_test.go b/test/experimental-e2e/single_namespace_support_test.go similarity index 80% rename from test/e2e/single_namespace_support_test.go rename to test/experimental-e2e/single_namespace_support_test.go index 86ec782202..77a0cba42f 100644 --- a/test/e2e/single_namespace_support_test.go +++ b/test/experimental-e2e/single_namespace_support_test.go @@ -1,10 +1,11 @@ -package e2e +package experimental_e2e import ( "context" "fmt" "os" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,12 +16,45 @@ import ( apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" + . "github.com/operator-framework/operator-controller/test/helpers" ) +const ( + artifactName = "operator-controller-experimental-e2e" + pollDuration = time.Minute + pollInterval = time.Second +) + +var ( + cfg *rest.Config + c client.Client +) + +func TestMain(m *testing.M) { + cfg = ctrl.GetConfigOrDie() + + var err error + utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme)) + c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + utilruntime.Must(err) + + os.Exit(m.Run()) +} + +func TestNoop(t *testing.T) { + t.Log("Running experimental-e2e tests") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) +} + func TestClusterExtensionSingleNamespaceSupport(t *testing.T) { t.Log("Test support for cluster extension config") defer utils.CollectTestArtifacts(t, artifactName, c, cfg) @@ -346,3 +380,57 @@ func TestClusterExtensionOwnNamespaceSupport(t *testing.T) { require.Equal(ct, clusterExtension.Spec.Namespace, deployment.Spec.Template.GetAnnotations()["olm.targetNamespaces"]) }, pollDuration, pollInterval) } + +func TestClusterExtensionVersionUpdate(t *testing.T) { + t.Log("When a cluster extension is installed from a catalog") + t.Log("When resolving upgrade edges") + + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + t.Log("By creating an ClusterExtension at a specified version") + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Version: "1.0.0", + }, + }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, + }, + } + require.NoError(t, c.Create(context.Background(), clusterExtension)) + t.Log("By eventually reporting a successful resolution") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("It allows to upgrade the ClusterExtension to a non-successor version") + t.Log("By forcing update of ClusterExtension resource to a non-successor version") + // 1.2.0 does not replace/skip/skipRange 1.0.0. + clusterExtension.Spec.Source.Catalog.Version = "1.2.0" + clusterExtension.Spec.Source.Catalog.UpgradeConstraintPolicy = ocv1.UpgradeConstraintPolicySelfCertified + require.NoError(t, c.Update(context.Background(), clusterExtension)) + t.Log("By eventually reporting a satisfiable resolution") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) + t.Log("We should have two ClusterExtensionRevision resources") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + cerList := &ocv1.ClusterExtensionRevisionList{} + require.NoError(ct, c.List(context.Background(), cerList)) + require.Len(ct, cerList.Items, 2) + }, pollDuration, pollInterval) +}