From d89641d2b70deb8b3d9b8411149941375763dc7c Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 30 Apr 2024 13:29:27 -0400 Subject: [PATCH] ClusterExtension: add installNamespace, remove watchNamespaces Signed-off-by: Joe Lanford --- api/v1alpha1/clusterextension_types.go | 10 +- api/v1alpha1/zz_generated.deepcopy.go | 7 +- ...peratorframework.io_clusterextensions.yaml | 16 ++- go.mod | 8 +- go.sum | 35 ++--- .../clusterextension_admission_test.go | 60 +++++++- .../clusterextension_controller.go | 6 +- .../clusterextension_controller_test.go | 130 ++++++++++-------- test/e2e/cluster_extension_admission_test.go | 15 +- test/e2e/cluster_extension_install_test.go | 24 ++-- .../core.rukpak.io_bundledeployments.yaml | 38 +++-- 11 files changed, 218 insertions(+), 131 deletions(-) diff --git a/api/v1alpha1/clusterextension_types.go b/api/v1alpha1/clusterextension_types.go index 0d7f0b83d..cd87db021 100644 --- a/api/v1alpha1/clusterextension_types.go +++ b/api/v1alpha1/clusterextension_types.go @@ -66,11 +66,13 @@ type ClusterExtensionSpec struct { // Defines the policy for how to handle upgrade constraints UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"` - //+kubebuilder:Optional + //+kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + //+kubebuilder:validation:MaxLength:=63 // - // watchNamespaces indicates which namespaces the extension should watch. - // This feature is currently supported only with RegistryV1 bundles. - WatchNamespaces []string `json:"watchNamespaces,omitempty"` + // installNamespace is the namespace where the bundle should be installed. However, note that + // the bundle may contain resources that are cluster-scoped or that are + // installed in a different namespace. This namespace is expected to exist. + InstallNamespace string `json:"installNamespace"` } const ( diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index cef0eb870..bc1276b2f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -45,7 +45,7 @@ func (in *ClusterExtension) DeepCopyInto(out *ClusterExtension) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) + out.Spec = in.Spec in.Status.DeepCopyInto(&out.Status) } @@ -102,11 +102,6 @@ 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 - if in.WatchNamespaces != nil { - in, out := &in.WatchNamespaces, &out.WatchNamespaces - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionSpec. diff --git a/config/crd/bases/olm.operatorframework.io_clusterextensions.yaml b/config/crd/bases/olm.operatorframework.io_clusterextensions.yaml index bafec1096..2b0ebb960 100644 --- a/config/crd/bases/olm.operatorframework.io_clusterextensions.yaml +++ b/config/crd/bases/olm.operatorframework.io_clusterextensions.yaml @@ -44,6 +44,14 @@ spec: maxLength: 48 pattern: ^[a-z0-9]+([\.-][a-z0-9]+)*$ type: string + installNamespace: + description: |- + installNamespace is the namespace where the bundle should be installed. However, note that + the bundle may contain resources that are cluster-scoped or that are + installed in a different namespace. This namespace is expected to exist. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string packageName: maxLength: 48 pattern: ^[a-z0-9]+(-[a-z0-9]+)*$ @@ -66,14 +74,8 @@ spec: maxLength: 64 pattern: ^(\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|[x|X|\*])(\.(0|[1-9]\d*|x|X|\*]))?(\.(0|[1-9]\d*|x|X|\*))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)((?:\s+|,\s*|\s*\|\|\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|x|X|\*])(\.(0|[1-9]\d*|x|X|\*))?(\.(0|[1-9]\d*|x|X|\*]))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)*$ type: string - watchNamespaces: - description: |- - watchNamespaces indicates which namespaces the extension should watch. - This feature is currently supported only with RegistryV1 bundles. - items: - type: string - type: array required: + - installNamespace - packageName type: object status: diff --git a/go.mod b/go.mod index d00b6856c..d2b238551 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/operator-framework/catalogd v0.12.0 github.com/operator-framework/deppy v0.3.0 github.com/operator-framework/operator-registry v1.40.0 - github.com/operator-framework/rukpak v0.19.0 + github.com/operator-framework/rukpak v0.20.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 github.com/vmware-tanzu/carvel-kapp-controller v0.51.0 @@ -39,7 +39,7 @@ require ( github.com/go-air/gini v1.0.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect @@ -64,9 +64,9 @@ require ( github.com/operator-framework/api v0.23.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.47.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index b90d8ad02..ab6ec0e60 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -43,7 +43,8 @@ github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHa github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -63,8 +64,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb h1:LCMfzVg3sflxTs4UvuP4D8CkoZnfHLe2qzqgDn/4OHs= -github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= @@ -97,10 +98,10 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= -github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= -github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= -github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/operator-framework/api v0.23.0 h1:kHymOwcHBpBVujT49SKOCd4EVG7Odwj4wl3NbOR2LLA= github.com/operator-framework/api v0.23.0/go.mod h1:oKcFOz+Xc1UhMi2Pzcp6qsO7wjS4r+yP7EQprQBXrfM= github.com/operator-framework/catalogd v0.12.0 h1:Cww+CyowkfTFugB9ZjUDpKvumh2vPe/TjCUpMHDmVBM= @@ -109,18 +110,18 @@ github.com/operator-framework/deppy v0.3.0 h1:W8wpF0ehcTAdH2WfMyqMPI5Ja0Qv8M5FMO github.com/operator-framework/deppy v0.3.0/go.mod h1:EHDxZz8fKGvuymCng3G/Ou7wuX14GaLr0cmf2u29Oog= github.com/operator-framework/operator-registry v1.40.0 h1:CaYNE4F/jzahpC7UCILItaIHmB5/oE0sS066nK+5Glw= github.com/operator-framework/operator-registry v1.40.0/go.mod h1:D2YxapkfRDgjqNTO9d3h3v0DeREbV+8utCLG52zrOy4= -github.com/operator-framework/rukpak v0.19.0 h1:8cW43z4jsvARlsmj2eum5bAsZEvSxqDwfMW3dSq1zq8= -github.com/operator-framework/rukpak v0.19.0/go.mod h1:yRJe6JRwgae4s/tnzEDCsNvdT+t4eDARdtfoJMLYiP4= +github.com/operator-framework/rukpak v0.20.0 h1:BqF1nIlocyYLMmv6CvlbtB9QTwSMrEfTzhA+H3+do3c= +github.com/operator-framework/rukpak v0.20.0/go.mod h1:WAyS3DXZ19pLg/324PEoudWZmaRlYZ6i4j4NV3/T/mI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= -github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -199,8 +200,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/controllers/clusterextension_admission_test.go b/internal/controllers/clusterextension_admission_test.go index 28e2bc967..475354d05 100644 --- a/internal/controllers/clusterextension_admission_test.go +++ b/internal/controllers/clusterextension_admission_test.go @@ -8,6 +8,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + "strings" ) func TestClusterExtensionAdmissionPackageName(t *testing.T) { @@ -42,7 +43,8 @@ func TestClusterExtensionAdmissionPackageName(t *testing.T) { t.Parallel() cl := newClient(t) err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - PackageName: tc.pkgName, + PackageName: tc.pkgName, + InstallNamespace: "default", })) if tc.errMsg == "" { require.NoError(t, err, "unexpected error for package name %q: %w", tc.pkgName, err) @@ -129,8 +131,9 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) { t.Parallel() cl := newClient(t) err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - PackageName: "package", - Version: tc.version, + PackageName: "package", + Version: tc.version, + InstallNamespace: "default", })) if tc.errMsg == "" { require.NoError(t, err, "unexpected error for version %q: %w", tc.version, err) @@ -173,8 +176,9 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { t.Parallel() cl := newClient(t) err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - PackageName: "package", - Channel: tc.channelName, + PackageName: "package", + Channel: tc.channelName, + InstallNamespace: "default", })) if tc.errMsg == "" { require.NoError(t, err, "unexpected error for channel %q: %w", tc.channelName, err) @@ -186,6 +190,52 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { } } +func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { + tooLongError := "spec.installNamespace: Too long: may not be longer than 63" + regexMismatchError := "spec.installNamespace in body should match" + + testCases := []struct { + name string + installNamespace string + errMsg string + }{ + {"just alphanumeric", "justalphanumberic1", ""}, + {"hypen-separated", "hyphenated-name", ""}, + {"no install namespace", "", regexMismatchError}, + {"dot-separated", "dotted.name", regexMismatchError}, + {"includes version", "channel-has-version-1.0.1", regexMismatchError}, + {"longest valid install namespace", strings.Repeat("x", 63), ""}, + {"too long install namespace name", strings.Repeat("x", 64), tooLongError}, + {"spaces", "spaces spaces", regexMismatchError}, + {"capitalized", "Capitalized", regexMismatchError}, + {"camel case", "camelCase", regexMismatchError}, + {"invalid characters", "many/invalid$characters+in_name", regexMismatchError}, + {"starts with hyphen", "-start-with-hyphen", regexMismatchError}, + {"ends with hyphen", "end-with-hyphen-", regexMismatchError}, + {"starts with period", ".start-with-period", regexMismatchError}, + {"ends with period", "end-with-period.", regexMismatchError}, + } + + 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{ + PackageName: "package", + InstallNamespace: tc.installNamespace, + })) + if tc.errMsg == "" { + require.NoError(t, err, "unexpected error for installNamespace %q: %w", tc.installNamespace, 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.go b/internal/controllers/clusterextension_controller.go index b71baaf2c..f49ecb4c5 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -367,7 +367,7 @@ func (r *ClusterExtensionReconciler) GenerateExpectedBundleDeployment(o ocv1alph // identical to "kubectl apply -f" spec := map[string]interface{}{ - // TODO: Don't assume plain provisioner + "installNamespace": o.Spec.InstallNamespace, "provisionerClassName": bundleProvisioner, "source": map[string]interface{}{ // TODO: Don't assume image type @@ -378,10 +378,6 @@ func (r *ClusterExtensionReconciler) GenerateExpectedBundleDeployment(o ocv1alph }, } - if len(o.Spec.WatchNamespaces) > 0 { - spec["watchNamespaces"] = o.Spec.WatchNamespaces - } - bd := &unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": rukpakv1alpha2.GroupVersion.String(), "kind": rukpakv1alpha2.BundleDeploymentKind, diff --git a/internal/controllers/clusterextension_controller_test.go b/internal/controllers/clusterextension_controller_test.go index 5f5b7fdb4..508625043 100644 --- a/internal/controllers/clusterextension_controller_test.go +++ b/internal/controllers/clusterextension_controller_test.go @@ -53,7 +53,10 @@ func TestClusterExtensionNonExistentPackage(t *testing.T) { pkgName := fmt.Sprintf("non-existent-%s", rand.String(6)) clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -93,8 +96,9 @@ func TestClusterExtensionNonExistentVersion(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: "0.50.0", // this version of the package does not exist + PackageName: pkgName, + Version: "0.50.0", // this version of the package does not exist + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -139,7 +143,10 @@ func TestClusterExtensionBundleDeploymentDoesNotExist(t *testing.T) { t.Log("By initializing cluster state") clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -194,7 +201,10 @@ func TestClusterExtensionBundleDeploymentOutOfDate(t *testing.T) { t.Log("By initializing cluster state") clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -216,6 +226,7 @@ func TestClusterExtensionBundleDeploymentOutOfDate(t *testing.T) { }, }, Spec: rukpakv1alpha2.BundleDeploymentSpec{ + InstallNamespace: "default", ProvisionerClassName: "core-rukpak-io-registry", Source: rukpakv1alpha2.BundleSource{ Type: rukpakv1alpha2.SourceTypeImage, @@ -279,7 +290,10 @@ func TestClusterExtensionBundleDeploymentUpToDate(t *testing.T) { t.Log("By initializing cluster state") clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -301,6 +315,7 @@ func TestClusterExtensionBundleDeploymentUpToDate(t *testing.T) { }, }, Spec: rukpakv1alpha2.BundleDeploymentSpec{ + InstallNamespace: "default", ProvisionerClassName: "core-rukpak-io-registry", Source: rukpakv1alpha2.BundleSource{ Type: rukpakv1alpha2.SourceTypeImage, @@ -549,7 +564,10 @@ func TestClusterExtensionExpectedBundleDeployment(t *testing.T) { t.Log("By initializing cluster state") clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -558,6 +576,7 @@ func TestClusterExtensionExpectedBundleDeployment(t *testing.T) { bd := &rukpakv1alpha2.BundleDeployment{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: rukpakv1alpha2.BundleDeploymentSpec{ + InstallNamespace: "default", ProvisionerClassName: "bar", Source: rukpakv1alpha2.BundleSource{ Type: rukpakv1alpha2.SourceTypeHTTP, @@ -618,13 +637,19 @@ func TestClusterExtensionDuplicatePackage(t *testing.T) { t.Log("By initializing cluster state") dupClusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("orig-%s", extKey.Name)}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, dupClusterExtension)) clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{PackageName: pkgName}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + PackageName: pkgName, + InstallNamespace: "default", + }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -672,9 +697,10 @@ func TestClusterExtensionChannelVersionExists(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } err := cl.Create(ctx, clusterExtension) @@ -731,9 +757,10 @@ func TestClusterExtensionChannelExistsNoVersion(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -788,9 +815,10 @@ func TestClusterExtensionVersionNoChannel(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -838,8 +866,9 @@ func TestClusterExtensionNoChannel(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Channel: pkgChan, + PackageName: pkgName, + Channel: pkgChan, + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -887,9 +916,10 @@ func TestClusterExtensionNoVersion(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -937,9 +967,10 @@ func TestClusterExtensionPlainV0Bundle(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -994,9 +1025,10 @@ func TestClusterExtensionBadBundleMediaType(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } require.NoError(t, cl.Create(ctx, clusterExtension)) @@ -1064,21 +1096,6 @@ func TestGeneratedBundleDeployment(t *testing.T) { }{ { name: "when all the specs are provided.", - clusterExtension: ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-bd", - UID: types.UID("test"), - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - WatchNamespaces: []string{"alpha", "beta", "gamma"}, - }, - }, - bundlePath: "testpath", - bundleProvisioner: "foo", - expectedBundleDeployment: &unstructured.Unstructured{}, - }, - { - name: "when watchNamespaces are not provided.", clusterExtension: ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bd", @@ -1100,7 +1117,7 @@ func TestGeneratedBundleDeployment(t *testing.T) { require.Equal(t, tt.clusterExtension.GetName(), resultBundleDeployment.GetName()) require.Equal(t, tt.bundlePath, resultBundleDeployment.Spec.Source.Image.Ref) require.Equal(t, tt.bundleProvisioner, resultBundleDeployment.Spec.ProvisionerClassName) - require.Equal(t, tt.clusterExtension.Spec.WatchNamespaces, resultBundleDeployment.Spec.WatchNamespaces) + require.Equal(t, tt.clusterExtension.Spec.InstallNamespace, resultBundleDeployment.Spec.InstallNamespace) } } @@ -1122,9 +1139,10 @@ func TestClusterExtensionUpgrade(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + InstallNamespace: "default", }, } // Create a cluster extension @@ -1215,9 +1233,10 @@ func TestClusterExtensionUpgrade(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, + PackageName: pkgName, + InstallNamespace: "default", + Version: pkgVer, + Channel: pkgChan, }, } // Create a cluster extension @@ -1323,6 +1342,7 @@ func TestClusterExtensionUpgrade(t *testing.T) { Version: "1.0.0", Channel: "beta", UpgradeConstraintPolicy: ocv1alpha1.UpgradeConstraintPolicyIgnore, + InstallNamespace: "default", }, } // Create a cluster extension @@ -1407,9 +1427,10 @@ func TestClusterExtensionDowngrade(t *testing.T) { clusterExtension := &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: "prometheus", - Version: "1.0.1", - Channel: "beta", + PackageName: "prometheus", + Version: "1.0.1", + Channel: "beta", + InstallNamespace: "default", }, } // Create a cluster extension @@ -1493,6 +1514,7 @@ func TestClusterExtensionDowngrade(t *testing.T) { Version: "2.0.0", Channel: "beta", UpgradeConstraintPolicy: ocv1alpha1.UpgradeConstraintPolicyIgnore, + InstallNamespace: "default", }, } // Create a cluster extension diff --git a/test/e2e/cluster_extension_admission_test.go b/test/e2e/cluster_extension_admission_test.go index bfa58d4c5..b1cca6c3f 100644 --- a/test/e2e/cluster_extension_admission_test.go +++ b/test/e2e/cluster_extension_admission_test.go @@ -35,7 +35,8 @@ func TestClusterExtensionPackageUniqueness(t *testing.T) { Name: firstResourceName, }, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: firstResourcePackageName, + PackageName: firstResourcePackageName, + InstallNamespace: "default", }, } require.NoError(t, c.Create(ctx, clusterExtension1)) @@ -47,7 +48,8 @@ func TestClusterExtensionPackageUniqueness(t *testing.T) { GenerateName: "test-extension-", }, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: firstResourcePackageName, + PackageName: firstResourcePackageName, + InstallNamespace: "default", }, } err := c.Create(ctx, clusterExtension2) @@ -59,7 +61,8 @@ func TestClusterExtensionPackageUniqueness(t *testing.T) { GenerateName: "test-extension-", }, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: "package2", + PackageName: "package2", + InstallNamespace: "default", }, } require.NoError(t, c.Create(ctx, clusterExtension2)) @@ -75,7 +78,8 @@ func TestClusterExtensionPackageUniqueness(t *testing.T) { Name: clusterExtension2.Name, }, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: firstResourcePackageName, + PackageName: firstResourcePackageName, + InstallNamespace: "default", }, } err = c.Patch(ctx, intent, client.Apply, client.ForceOwnership, fieldOwner) @@ -91,7 +95,8 @@ func TestClusterExtensionPackageUniqueness(t *testing.T) { Name: clusterExtension2.Name, }, Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: "package3", + PackageName: "package3", + InstallNamespace: "default", }, } require.NoError(t, c.Patch(ctx, intent, client.Apply, client.ForceOwnership, fieldOwner)) diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index 1ac60b869..00c275ebe 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -73,7 +73,8 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { defer getArtifactsOutput(t) clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - PackageName: "prometheus", + PackageName: "prometheus", + InstallNamespace: "default", } t.Log("It resolves the specified package with correct bundle path") t.Log("By creating the ClusterExtension resource") @@ -129,7 +130,8 @@ func TestClusterExtensionInstallPlain(t *testing.T) { defer getArtifactsOutput(t) clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - PackageName: "plain", + PackageName: "plain", + InstallNamespace: "default", } t.Log("It resolves the specified package with correct bundle path") t.Log("By creating the ClusterExtension resource") @@ -186,7 +188,8 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { pkgName := "prometheus" clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, + PackageName: pkgName, + InstallNamespace: "default", } t.Log("By deleting the catalog first") @@ -247,8 +250,9 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { t.Log("By creating an ClusterExtension at a specified version") clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - PackageName: "prometheus", - Version: "1.0.0", + PackageName: "prometheus", + Version: "1.0.0", + InstallNamespace: "default", } require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution") @@ -292,8 +296,9 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) { t.Log("By creating an ClusterExtension at a specified version") clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - PackageName: "prometheus", - Version: "1.0.0", + PackageName: "prometheus", + Version: "1.0.0", + InstallNamespace: "default", } require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution") @@ -336,8 +341,9 @@ func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { t.Log("By creating an ClusterExtension at a specified version") clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - PackageName: "prometheus", - Version: "1.0.0", + PackageName: "prometheus", + Version: "1.0.0", + InstallNamespace: "default", } require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution") diff --git a/testdata/crds/core.rukpak.io_bundledeployments.yaml b/testdata/crds/core.rukpak.io_bundledeployments.yaml index 0cedcf0c5..c9a7e6f9e 100644 --- a/testdata/crds/core.rukpak.io_bundledeployments.yaml +++ b/testdata/crds/core.rukpak.io_bundledeployments.yaml @@ -50,21 +50,33 @@ spec: More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: + properties: + name: + maxLength: 52 + type: string type: object spec: description: BundleDeploymentSpec defines the desired state of BundleDeployment properties: config: - description: Config is provisioner specific configurations + description: config is provisioner specific configurations type: object x-kubernetes-preserve-unknown-fields: true + installNamespace: + description: |- + installNamespace is the namespace where the bundle should be installed. However, note that + the bundle may contain resources that are cluster-scoped or that are + installed in a different namespace. This namespace is expected to exist. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string provisionerClassName: - description: ProvisionerClassName sets the name of the provisioner + description: provisionerClassName sets the name of the provisioner that should reconcile this BundleDeployment. pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string source: - description: Source defines the configuration for the underlying Bundle + description: source defines the configuration for the underlying Bundle content. properties: configMaps: @@ -136,6 +148,13 @@ spec: from the specified repo. Ref is required, and exactly one field within Ref is required. Setting more than one field or zero fields will result in an error. + oneOf: + - required: + - branch + - required: + - commit + - required: + - tag properties: branch: description: |- @@ -221,22 +240,11 @@ spec: required: - type type: object - watchNamespaces: - description: watchNamespaces indicates which namespaces the operator - should watch. - items: - type: string - type: array required: + - installNamespace - provisionerClassName - source type: object - x-kubernetes-validations: - - message: Empty string not accepted if length of watchNamespaces is more - than 1. - rule: '!has(self.watchNamespaces) || size(self.watchNamespaces) <= 1 - || (size(self.watchNamespaces) > 1 && !self.watchNamespaces.exists(e, - e == ''''))' status: description: BundleDeploymentStatus defines the observed state of BundleDeployment properties: