diff --git a/api/v1/clusterextension_types.go b/api/v1/clusterextension_types.go index 6de62b0e1..343e8cbb4 100644 --- a/api/v1/clusterextension_types.go +++ b/api/v1/clusterextension_types.go @@ -106,7 +106,6 @@ 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 b21e40452..c0da40c4b 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 fc36de0e7..41ecdb6cc 100644 --- a/docs/draft/howto/single-ownnamespace-install.md +++ b/docs/draft/howto/single-ownnamespace-install.md @@ -1,8 +1,7 @@ ## Description !!! note -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. +The `SingleOwnNamespaceInstallSupport` feature-gate is enabled by default. Use this guide to configure bundles that need Single or Own namespace install modes. --- @@ -31,28 +30,6 @@ 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 f17271d3e..5d04b02df 100644 --- a/docs/draft/tutorials/explore-available-content-metas-endpoint.md +++ b/docs/draft/tutorials/explore-available-content-metas-endpoint.md @@ -91,9 +91,6 @@ 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 01ce9436d..54e174b4c 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` install mode +* **must** support installation via the `AllNamespaces`, `SingleNamespace`, or `OwnNamespace` install modes. * **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 36e3cf883..98bb7733c 100644 --- a/docs/tutorials/explore-available-content.md +++ b/docs/tutorials/explore-available-content.md @@ -91,9 +91,6 @@ 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 611c6dfb0..86b3d2876 100755 --- a/hack/demo/own-namespace-demo-script.sh +++ b/hack/demo/own-namespace-demo-script.sh @@ -6,16 +6,14 @@ set -e trap 'echo "Demo ran into error"; trap - SIGTERM && kill -- -$$; exit 1' ERR SIGINT SIGTERM EXIT -# install experimental CRDs with config field support -kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/experimental.yaml" +# install standard CRDs +echo "Install standard CRDs..." +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/standard.yaml" -# wait for experimental CRDs to be available +# wait for standard CRDs to be available kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io -# 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 +# Ensure controller is healthy kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager # create install namespace @@ -57,17 +55,6 @@ 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 970268415..885854dd9 100755 --- a/hack/demo/single-namespace-demo-script.sh +++ b/hack/demo/single-namespace-demo-script.sh @@ -6,16 +6,14 @@ set -e trap 'echo "Demo ran into error"; trap - SIGTERM && kill -- -$$; exit 1' ERR SIGINT SIGTERM EXIT -# install experimental CRDs with config field support -kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/experimental.yaml" +# install standard CRDs +echo "Install standard CRDs..." +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/standard.yaml" -# wait for experimental CRDs to be available +# wait for standard CRDs to be available kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io -# 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 +# Ensure controller is healthy kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager # create install namespace @@ -60,17 +58,6 @@ 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 b14b1b303..1d2761714 100644 --- a/helm/experimental.yaml +++ b/helm/experimental.yaml @@ -9,7 +9,6 @@ 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 a0983e41f..61337bad6 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,6 +57,45 @@ 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 aaed7c71f..0fe3bec1f 100644 --- a/helm/tilt.yaml +++ b/helm/tilt.yaml @@ -14,7 +14,6 @@ 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 4926ff853..87e827c66 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: false, - PreRelease: featuregate.Alpha, + Default: true, + PreRelease: featuregate.GA, LockToDefault: false, }, diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 1efa8b8d9..5b09dc949 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -2188,7 +2188,6 @@ 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 664f8599c..4ddd45077 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -2101,7 +2101,6 @@ 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 783beec51..9c7a20f57 100644 --- a/manifests/standard-e2e.yaml +++ b/manifests/standard-e2e.yaml @@ -648,6 +648,45 @@ 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 95e400c26..cf7eb0ee5 100644 --- a/manifests/standard.yaml +++ b/manifests/standard.yaml @@ -613,6 +613,45 @@ 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/single_namespace_support_test.go b/test/e2e/single_namespace_support_test.go similarity index 62% rename from test/experimental-e2e/single_namespace_support_test.go rename to test/e2e/single_namespace_support_test.go index d1ca13465..d8e9b6c92 100644 --- a/test/experimental-e2e/single_namespace_support_test.go +++ b/test/e2e/single_namespace_support_test.go @@ -1,11 +1,10 @@ -package experimental_e2e +package e2e import ( "context" "fmt" "os" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,45 +15,12 @@ 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 TestClusterExtensionConfigSupport(t *testing.T) { t.Log("Test support for cluster extension config") defer utils.CollectTestArtifacts(t, artifactName, c, cfg) @@ -195,57 +161,3 @@ func TestClusterExtensionConfigSupport(t *testing.T) { require.Equal(ct, watchNamespace.GetName(), 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) -} diff --git a/test/experimental-e2e/boxcutter_support_test.go b/test/experimental-e2e/boxcutter_support_test.go new file mode 100644 index 000000000..b09d19ec8 --- /dev/null +++ b/test/experimental-e2e/boxcutter_support_test.go @@ -0,0 +1,70 @@ +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 new file mode 100644 index 000000000..de329b588 --- /dev/null +++ b/test/experimental-e2e/experimental_test.go @@ -0,0 +1,43 @@ +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) +}