diff --git a/api/v1alpha1/clusterextension_types.go b/api/v1alpha1/clusterextension_types.go index c0fba0098..8ccdb66c5 100644 --- a/api/v1alpha1/clusterextension_types.go +++ b/api/v1alpha1/clusterextension_types.go @@ -43,6 +43,34 @@ const ( // ClusterExtensionSpec defines the desired state of ClusterExtension type ClusterExtensionSpec struct { + // namespace is a reference to a Kubernetes namespace. + // This is the namespace in which the provided ServiceAccount must exist. + // It also designates the default namespace where namespace-scoped resources + // for the extension are applied to the cluster. + // Some extensions may contain namespace-scoped resources to be applied in other namespaces. + // This namespace must exist. + // + // namespace is required, immutable, and follows the DNS label standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + // start and end with an alphanumeric character, and be no longer than 63 characters + // + // [RFC 1123]: https://tools.ietf.org/html/rfc1123 + // + // +kubebuilder:validation:MaxLength:=63 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="namespace is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label" + // +kubebuilder:validation:Required + Namespace string `json:"namespace"` + + // 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. + // + // +kubebuilder:validation:Required + ServiceAccount ServiceAccountReference `json:"serviceAccount"` + // source is a required field which selects the installation source of content // for this ClusterExtension. Selection is performed by setting the sourceType. // @@ -59,18 +87,11 @@ type ClusterExtensionSpec struct { // +kubebuilder:validation:Required Source SourceConfig `json:"source"` - // install is a required field used to configure the installation options - // for the ClusterExtension such as the installation namespace, - // the service account and the pre-flight check configuration. + // install is an optional field used to configure the installation options + // for the ClusterExtension such as the pre-flight check configuration. // - // Below is a minimal example of an installation definition (in yaml): - // install: - // namespace: example-namespace - // serviceAccount: - // name: example-sa - // - // +kubebuilder:validation:Required - Install ClusterExtensionInstallConfig `json:"install"` + // +optional + Install *ClusterExtensionInstallConfig `json:"install,omitempty"` } const SourceTypeCatalog = "Catalog" @@ -104,38 +125,9 @@ type SourceConfig struct { // ClusterExtensionInstallConfig is a union which selects the clusterExtension installation config. // ClusterExtensionInstallConfig requires the namespace and serviceAccount which should be used for the installation of packages. // +// +kubebuilder:validation:XValidation:rule="has(self.preflight)",message="at least one of [preflight] are required when install is specified" // +union type ClusterExtensionInstallConfig struct { - // namespace designates the kubernetes Namespace where bundle content - // for the package, referenced in the 'packageName' field, will be applied and the necessary - // service account can be found. - // The bundle may contain cluster-scoped resources or resources that are - // applied to other Namespaces. This Namespace is expected to exist. - // - // namespace is required, immutable, and follows the DNS label standard - // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), - // start and end with an alphanumeric character, and be no longer than 63 characters - // - // [RFC 1123]: https://tools.ietf.org/html/rfc1123 - // - // +kubebuilder:validation:MaxLength:=63 - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="namespace is immutable" - // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label. It must contain only lowercase alphanumeric characters or hyphens (-), start and end with an alphanumeric character, and be no longer than 63 characters" - // +kubebuilder:validation:Required - Namespace string `json:"namespace"` - - // serviceAccount is a required reference to a ServiceAccount that exists - // in the installNamespace which is used to install and - // manage the content for the package specified in the packageName field. - // - // In order to successfully install and manage the content for the package, - // the ServiceAccount provided via this field should be configured with the - // appropriate permissions to perform the necessary operations on all the - // resources that are included in the bundle of content being applied. - // - // +kubebuilder:validation:Required - ServiceAccount ServiceAccountReference `json:"serviceAccount"` - // preflight is an optional field that can be used to configure the checks that are // run before installation or upgrade of the content for the package specified in the packageName field. // diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7ca0e7784..e3b9b0336 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -110,7 +110,6 @@ func (in *ClusterExtension) DeepCopyObject() runtime.Object { // 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 - out.ServiceAccount = in.ServiceAccount if in.Preflight != nil { in, out := &in.Preflight, &out.Preflight *out = new(PreflightConfig) @@ -179,8 +178,13 @@ func (in *ClusterExtensionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterExtensionSpec) DeepCopyInto(out *ClusterExtensionSpec) { *out = *in + out.ServiceAccount = in.ServiceAccount in.Source.DeepCopyInto(&out.Source) - in.Install.DeepCopyInto(&out.Install) + if in.Install != nil { + in, out := &in.Install, &out.Install + *out = new(ClusterExtensionInstallConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionSpec. diff --git a/cmd/manager/main.go b/cmd/manager/main.go index c353a4cb0..2efadb2a0 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -197,7 +197,7 @@ func main() { helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), systemNamespace)), helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) { ext := obj.(*ocv1alpha1.ClusterExtension) - return ext.Spec.Install.Namespace, nil + return ext.Spec.Namespace, nil }), helmclient.ClientRestConfigMapper(clientRestConfigMapper), ) diff --git a/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml b/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml index 106605424..ef946c56a 100644 --- a/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml +++ b/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml @@ -58,39 +58,9 @@ spec: properties: install: description: |- - install is a required field used to configure the installation options - for the ClusterExtension such as the installation namespace, - the service account and the pre-flight check configuration. - - Below is a minimal example of an installation definition (in yaml): - install: - namespace: example-namespace - serviceAccount: - name: example-sa + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. properties: - namespace: - description: |- - namespace designates the kubernetes Namespace where bundle content - for the package, referenced in the 'packageName' field, will be applied and the necessary - service account can be found. - The bundle may contain cluster-scoped resources or resources that are - applied to other Namespaces. This Namespace is expected to exist. - - namespace is required, immutable, and follows the DNS label standard - as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), - start and end with an alphanumeric character, and be no longer than 63 characters - - [RFC 1123]: https://tools.ietf.org/html/rfc1123 - maxLength: 63 - type: string - x-kubernetes-validations: - - message: namespace is immutable - rule: self == oldSelf - - message: namespace must be a valid DNS1123 label. It must contain - only lowercase alphanumeric characters or hyphens (-), start - and end with an alphanumeric character, and be no longer than - 63 characters - rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") preflight: description: |- preflight is an optional field that can be used to configure the checks that are @@ -133,58 +103,77 @@ spec: - message: at least one of [crdUpgradeSafety] are required when preflight is specified rule: has(self.crdUpgradeSafety) - serviceAccount: + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + 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. + properties: + name: description: |- - serviceAccount is a required reference to a ServiceAccount that exists - in the installNamespace which is used to install and - manage the content for the package specified in the packageName field. - - In order to successfully install and manage the content for the package, - the ServiceAccount provided via this field should be configured with the - appropriate permissions to perform the necessary operations on all the - resources that are included in the bundle of content being applied. - properties: - name: - description: |- - name is a required, immutable reference to the name of the ServiceAccount - to be used for installation and management of the content for the package - specified in the packageName field. + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. - This ServiceAccount must exist in the installNamespace. + This ServiceAccount must exist in the installNamespace. - name follows the DNS subdomain standard as defined in [RFC 1123]. - It must contain only lowercase alphanumeric characters, - hyphens (-) or periods (.), start and end with an alphanumeric character, - and be no longer than 253 characters. + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. - Some examples of valid values are: - - some-serviceaccount - - 123-serviceaccount - - 1-serviceaccount-2 - - someserviceaccount - - some.serviceaccount + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount - Some examples of invalid values are: - - -some-serviceaccount - - some-serviceaccount- + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- - [RFC 1123]: https://tools.ietf.org/html/rfc1123 - maxLength: 253 - type: string - x-kubernetes-validations: - - message: name is immutable - rule: self == oldSelf - - message: name must be a valid DNS1123 subdomain. It must - contain only lowercase alphanumeric characters, hyphens - (-) or periods (.), start and end with an alphanumeric - character, and be no longer than 253 characters - rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") - required: - - name - type: object + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") required: - - namespace - - serviceAccount + - name type: object source: description: |- @@ -468,7 +457,8 @@ spec: rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? has(self.catalog) : !has(self.catalog)' required: - - install + - namespace + - serviceAccount - source type: object status: diff --git a/config/samples/olm_v1alpha1_clusterextension.yaml b/config/samples/olm_v1alpha1_clusterextension.yaml index 7536c3d90..f9bc5ff10 100644 --- a/config/samples/olm_v1alpha1_clusterextension.yaml +++ b/config/samples/olm_v1alpha1_clusterextension.yaml @@ -272,12 +272,11 @@ kind: ClusterExtension metadata: name: argocd spec: + namespace: argocd + serviceAccount: + name: argocd-installer source: sourceType: Catalog catalog: packageName: argocd-operator version: 0.6.0 - install: - namespace: argocd - serviceAccount: - name: argocd-installer diff --git a/docs/api-reference/operator-controller-api-reference.md b/docs/api-reference/operator-controller-api-reference.md index f6553a7a4..8977d3a3a 100644 --- a/docs/api-reference/operator-controller-api-reference.md +++ b/docs/api-reference/operator-controller-api-reference.md @@ -120,8 +120,6 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `namespace` _string_ | namespace designates the kubernetes Namespace where bundle content
for the package, referenced in the 'packageName' field, will be applied and the necessary
service account can be found.
The bundle may contain cluster-scoped resources or resources that are
applied to other Namespaces. This Namespace is expected to exist.

namespace is required, immutable, and follows the DNS label standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
start and end with an alphanumeric character, and be no longer than 63 characters

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63
Required: \{\}
| -| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a required reference to a ServiceAccount that exists
in the installNamespace which is used to install and
manage the content for the package specified in the packageName field.

In order to successfully install and manage the content for the package,
the ServiceAccount provided via this field should be configured with the
appropriate permissions to perform the necessary operations on all the
resources that are included in the bundle of content being applied. | | Required: \{\}
| | `preflight` _[PreflightConfig](#preflightconfig)_ | preflight is an optional field that can be used to configure the checks that are
run before installation or upgrade of the content for the package specified in the packageName field.

When specified, it replaces the default preflight configuration for install/upgrade actions.
When not specified, the default configuration will be used. | | | @@ -174,8 +172,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | +| `namespace` _string_ | namespace is a reference to a Kubernetes namespace.
This is the namespace in which the provided ServiceAccount must exist.
It also designates the default namespace where namespace-scoped resources
for the extension are applied to the cluster.
Some extensions may contain namespace-scoped resources to be applied in other namespaces.
This namespace must exist.

namespace is required, immutable, and follows the DNS label standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
start and end with an alphanumeric character, and be no longer than 63 characters

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63
Required: \{\}
| +| `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 a required field used to configure the installation options
for the ClusterExtension such as the installation namespace,
the service account and the pre-flight check configuration.

Below is a minimal example of an installation definition (in yaml):
install:
namespace: example-namespace
serviceAccount:
name: example-sa | | 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. | | | #### ClusterExtensionStatus @@ -220,7 +220,7 @@ ServiceAccountReference identifies the serviceAccount used fo install a ClusterE _Appears in:_ -- [ClusterExtensionInstallConfig](#clusterextensioninstallconfig) +- [ClusterExtensionSpec](#clusterextensionspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | diff --git a/internal/action/restconfig.go b/internal/action/restconfig.go index a034ebb21..d01e302ad 100644 --- a/internal/action/restconfig.go +++ b/internal/action/restconfig.go @@ -16,8 +16,8 @@ func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) fun return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { cExt := o.(*ocv1alpha1.ClusterExtension) saKey := types.NamespacedName{ - Name: cExt.Spec.Install.ServiceAccount.Name, - Namespace: cExt.Spec.Install.Namespace, + Name: cExt.Spec.ServiceAccount.Name, + Namespace: cExt.Spec.Namespace, } saConfig := rest.AnonymousClientConfig(c) saConfig.Wrap(func(rt http.RoundTripper) http.RoundTripper { diff --git a/internal/applier/helm.go b/internal/applier/helm.go index b38a7d82a..9f288afa9 100644 --- a/internal/applier/helm.go +++ b/internal/applier/helm.go @@ -61,7 +61,7 @@ type Helm struct { // if it is set to enforcement None. func shouldSkipPreflight(ctx context.Context, preflight Preflight, ext *ocv1alpha1.ClusterExtension, state string) bool { l := log.FromContext(ctx) - hasCRDUpgradeSafety := ext.Spec.Install.Preflight != nil && ext.Spec.Install.Preflight.CRDUpgradeSafety != nil + hasCRDUpgradeSafety := ext.Spec.Install != nil && ext.Spec.Install.Preflight != nil && ext.Spec.Install.Preflight.CRDUpgradeSafety != nil _, isCRDUpgradeSafetyInstance := preflight.(*crdupgradesafety.Preflight) if hasCRDUpgradeSafety && isCRDUpgradeSafetyInstance { @@ -78,7 +78,7 @@ func shouldSkipPreflight(ctx context.Context, preflight Preflight, ext *ocv1alph } func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1alpha1.ClusterExtension, objectLabels map[string]string, storageLabels map[string]string) ([]client.Object, string, error) { - chrt, err := convert.RegistryV1ToHelmChart(ctx, contentFS, ext.Spec.Install.Namespace, []string{corev1.NamespaceAll}) + chrt, err := convert.RegistryV1ToHelmChart(ctx, contentFS, ext.Spec.Namespace, []string{corev1.NamespaceAll}) if err != nil { return nil, "", err } @@ -118,7 +118,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1alpha1.Clust switch state { case StateNeedsInstall: - rel, err = ac.Install(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(install *action.Install) error { + rel, err = ac.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(install *action.Install) error { install.CreateNamespace = false install.Labels = storageLabels return nil @@ -127,7 +127,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1alpha1.Clust return nil, state, err } case StateNeedsUpgrade: - rel, err = ac.Upgrade(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(upgrade *action.Upgrade) error { + rel, err = ac.Upgrade(ext.GetName(), ext.Spec.Namespace, chrt, values, func(upgrade *action.Upgrade) error { upgrade.MaxHistory = maxHelmReleaseHistory upgrade.Labels = storageLabels return nil @@ -161,7 +161,7 @@ func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1alpha1.Cl } if errors.Is(err, driver.ErrReleaseNotFound) { - desiredRelease, err := cl.Install(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(i *action.Install) error { + desiredRelease, err := cl.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(i *action.Install) error { i.DryRun = true i.DryRunOption = "server" return nil @@ -171,7 +171,7 @@ func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1alpha1.Cl } return nil, desiredRelease, StateNeedsInstall, nil } - desiredRelease, err := cl.Upgrade(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(upgrade *action.Upgrade) error { + desiredRelease, err := cl.Upgrade(ext.GetName(), ext.Spec.Namespace, chrt, values, func(upgrade *action.Upgrade) error { upgrade.MaxHistory = maxHelmReleaseHistory upgrade.DryRun = true upgrade.DryRunOption = "server" diff --git a/internal/controllers/clusterextension_admission_test.go b/internal/controllers/clusterextension_admission_test.go index cc3ac9b2d..9c180533c 100644 --- a/internal/controllers/clusterextension_admission_test.go +++ b/internal/controllers/clusterextension_admission_test.go @@ -43,11 +43,9 @@ func TestClusterExtensionSourceConfig(t *testing.T) { PackageName: "test-package", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, })) } @@ -56,11 +54,9 @@ func TestClusterExtensionSourceConfig(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: tc.sourceType, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, })) } @@ -117,11 +113,9 @@ func TestClusterExtensionAdmissionPackageName(t *testing.T) { PackageName: tc.pkgName, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -217,11 +211,9 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) { Version: tc.version, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -274,11 +266,9 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { Channels: tc.channels, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -292,7 +282,7 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { } func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { - tooLongError := "spec.install.namespace: Too long: may not be longer than 63" + tooLongError := "spec.namespace: Too long: may not be longer than 63" regexMismatchError := "namespace must be a valid DNS1123 label" testCases := []struct { @@ -329,11 +319,9 @@ func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { PackageName: "package", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: tc.namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: tc.namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -347,7 +335,7 @@ func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { } func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { - tooLongError := "spec.install.serviceAccount.name: Too long: may not be longer than 253" + tooLongError := "spec.serviceAccount.name: Too long: may not be longer than 253" regexMismatchError := "name must be a valid DNS1123 subdomain" testCases := []struct { @@ -385,11 +373,9 @@ func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { PackageName: "package", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: tc.serviceAccount, - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: tc.serviceAccount, }, })) if tc.errMsg == "" { @@ -402,6 +388,66 @@ func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { } } +func TestClusterExtensionAdmissionInstall(t *testing.T) { + oneOfErrMsg := "at least one of [preflight] are required when install is specified" + + testCases := []struct { + name string + installConfig *ocv1alpha1.ClusterExtensionInstallConfig + errMsg string + }{ + { + name: "install specified, nothing configured", + installConfig: &ocv1alpha1.ClusterExtensionInstallConfig{}, + errMsg: oneOfErrMsg, + }, + { + name: "install specified, preflight configured", + installConfig: &ocv1alpha1.ClusterExtensionInstallConfig{ + Preflight: &ocv1alpha1.PreflightConfig{ + CRDUpgradeSafety: &ocv1alpha1.CRDUpgradeSafetyPreflightConfig{ + Enforcement: ocv1alpha1.CRDUpgradeSafetyEnforcementNone, + }, + }, + }, + errMsg: "", + }, + { + name: "install not specified", + installConfig: nil, + errMsg: "", + }, + } + + t.Parallel() + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + cl := newClient(t) + err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ + Source: ocv1alpha1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1alpha1.CatalogSource{ + PackageName: "package", + }, + }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", + }, + Install: tc.installConfig, + })) + if tc.errMsg == "" { + require.NoError(t, err, "unexpected error for install configuration %v: %w", tc.installConfig, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMsg) + } + }) + } +} + func buildClusterExtension(spec ocv1alpha1.ClusterExtensionSpec) *ocv1alpha1.ClusterExtension { return &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/controllers/clusterextension_controller_test.go b/internal/controllers/clusterextension_controller_test.go index 47e85dd7e..448327944 100644 --- a/internal/controllers/clusterextension_controller_test.go +++ b/internal/controllers/clusterextension_controller_test.go @@ -67,11 +67,9 @@ func TestClusterExtensionResolutionFails(t *testing.T) { PackageName: pkgName, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: "default", }, }, } @@ -145,11 +143,9 @@ func TestClusterExtensionResolutionSuccessfulUnpackFails(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -229,11 +225,9 @@ func TestClusterExtensionUnpackUnexpectedState(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -289,11 +283,9 @@ func TestClusterExtensionResolutionAndUnpackSuccessfulApplierFails(t *testing.T) Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -371,11 +363,9 @@ func TestClusterExtensionApplierFailsWithBundleInstalled(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -472,11 +462,9 @@ func TestClusterExtensionManagerFailed(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -556,11 +544,9 @@ func TestClusterExtensionManagedContentCacheWatchFail(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: installNamespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: installNamespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -641,11 +627,9 @@ func TestClusterExtensionInstallationSucceeds(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -724,11 +708,9 @@ func TestClusterExtensionDeleteFinalizerFails(t *testing.T) { Channels: []string{pkgChan}, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, }, }, } @@ -1488,7 +1470,8 @@ func TestGetInstalledBundleHistory(t *testing.T) { labels.BundleReferenceKey: "bundle-ref", }, }, - }, nil, + }, + nil, &controllers.InstalledBundle{ BundleMetadata: ocv1alpha1.BundleMetadata{ Name: "test-ext", @@ -1522,7 +1505,8 @@ func TestGetInstalledBundleHistory(t *testing.T) { labels.BundleReferenceKey: "bundle-ref-1", }, }, - }, nil, + }, + nil, &controllers.InstalledBundle{ BundleMetadata: ocv1alpha1.BundleMetadata{ Name: "test-ext", diff --git a/internal/resolve/catalog_test.go b/internal/resolve/catalog_test.go index 755c767d2..ba6a70027 100644 --- a/internal/resolve/catalog_test.go +++ b/internal/resolve/catalog_test.go @@ -646,10 +646,8 @@ func buildFooClusterExtension(pkg string, channels []string, version string, upg Name: pkg, }, Spec: ocv1alpha1.ClusterExtensionSpec{ - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{Name: "default"}, - }, + Namespace: "default", + ServiceAccount: ocv1alpha1.ServiceAccountReference{Name: "default"}, Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index b1d8471ac..2a93a1de4 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -318,11 +318,9 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves the specified package with correct bundle path") @@ -373,11 +371,9 @@ func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) { PackageName: "prometheus", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves to multiple bundle paths") @@ -419,11 +415,9 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { // No Selector since this is an exact version match }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } require.NoError(t, c.Create(context.Background(), clusterExtension)) @@ -483,11 +477,9 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) { Version: "1.0.0", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } require.NoError(t, c.Create(context.Background(), clusterExtension)) @@ -534,11 +526,9 @@ func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { Version: "1.0.0", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } require.NoError(t, c.Create(context.Background(), clusterExtension)) @@ -591,11 +581,9 @@ func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) { }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves the specified package with correct bundle path") @@ -674,11 +662,9 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves the specified package with correct bundle path") @@ -737,11 +723,9 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It installs the specified package with correct bundle path") @@ -763,7 +747,7 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T prometheusService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "prometheus-operator", - Namespace: clusterExtension.Spec.Install.Namespace, + Namespace: clusterExtension.Spec.Namespace, }, } require.NoError(t, c.Delete(context.Background(), prometheusService)) @@ -802,11 +786,9 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: ns.Name, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves the specified package with correct bundle path") diff --git a/test/extension-developer-e2e/extension_developer_test.go b/test/extension-developer-e2e/extension_developer_test.go index 4a11a7530..bb6bdad39 100644 --- a/test/extension-developer-e2e/extension_developer_test.go +++ b/test/extension-developer-e2e/extension_developer_test.go @@ -75,11 +75,9 @@ func TestExtensionDeveloper(t *testing.T) { PackageName: os.Getenv("REG_PKG_NAME"), }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: installNamespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: installNamespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: sa.Name, }, }, }