From a0bc8ef96c7044e45e4a3c0e3da0c8c2df36c525 Mon Sep 17 00:00:00 2001 From: perdasilva Date: Sun, 13 Mar 2022 13:02:23 +0100 Subject: [PATCH] Refactor csv e2e Signed-off-by: perdasilva --- test/e2e/copied_csv_e2e_test.go | 262 + test/e2e/csv_e2e_test.go | 7151 ++++++++++++-------------- test/e2e/operator_groups_e2e_test.go | 2 +- test/e2e/subscription_e2e_test.go | 4 +- test/e2e/util.go | 6 + test/e2e/util/resource_manager.go | 24 +- test/e2e/webhook_e2e_test.go | 2 +- 7 files changed, 3552 insertions(+), 3899 deletions(-) create mode 100644 test/e2e/copied_csv_e2e_test.go diff --git a/test/e2e/copied_csv_e2e_test.go b/test/e2e/copied_csv_e2e_test.go new file mode 100644 index 00000000000..f38a15d224b --- /dev/null +++ b/test/e2e/copied_csv_e2e_test.go @@ -0,0 +1,262 @@ +package e2e + +import ( + "context" + "fmt" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + k8slabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + apitypes "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("Disabling copied CSVs", func() { + var ( + ns corev1.Namespace + csv operatorsv1alpha1.ClusterServiceVersion + ) + + BeforeEach(func() { + nsname := genName("csv-toggle-test-") + og := operatorsv1.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-operatorgroup", nsname), + Namespace: nsname, + }, + } + ns = SetupGeneratedTestNamespaceWithOperatorGroup(nsname, og) + + csv = operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv-toggle-test-"), + Namespace: nsname, + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: newNginxInstallStrategy(genName("csv-toggle-test-"), nil, nil), + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + }, + } + err := ctx.Ctx().Client().Create(context.Background(), &csv) + Expect(err).ShouldNot(HaveOccurred()) + }) + + AfterEach(func() { + Eventually(func() error { + err := ctx.Ctx().Client().Delete(context.Background(), &csv) + if err != nil && k8serrors.IsNotFound(err) { + return err + } + + return nil + }).Should(Succeed()) + TeardownNamespace(ns.GetName()) + }) + + When("an operator is installed in AllNamespace mode", func() { + It("should have Copied CSVs in all other namespaces", func() { + Eventually(func() error { + requirement, err := k8slabels.NewRequirement(operatorsv1alpha1.CopiedLabelKey, selection.Equals, []string{csv.GetNamespace()}) + if err != nil { + return err + } + + var copiedCSVs operatorsv1alpha1.ClusterServiceVersionList + err = ctx.Ctx().Client().List(context.TODO(), &copiedCSVs, &client.ListOptions{ + LabelSelector: k8slabels.NewSelector().Add(*requirement), + }) + if err != nil { + return err + } + + var namespaces corev1.NamespaceList + if err := ctx.Ctx().Client().List(context.TODO(), &namespaces, &client.ListOptions{}); err != nil { + return err + } + + if len(namespaces.Items)-1 != len(copiedCSVs.Items) { + return fmt.Errorf("%d copied CSVs found, expected %d", len(copiedCSVs.Items), len(namespaces.Items)-1) + } + + return nil + }).Should(Succeed()) + }) + }) + + When("Copied CSVs are disabled", func() { + BeforeEach(func() { + Eventually(func() error { + var olmConfig operatorsv1.OLMConfig + if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { + ctx.Ctx().Logf("Error getting olmConfig %v", err) + return err + } + + // Exit early if copied CSVs are disabled. + if !olmConfig.CopiedCSVsAreEnabled() { + return nil + } + + olmConfig.Spec = operatorsv1.OLMConfigSpec{ + Features: &operatorsv1.Features{ + DisableCopiedCSVs: getPointer(true), + }, + } + + if err := ctx.Ctx().Client().Update(context.TODO(), &olmConfig); err != nil { + ctx.Ctx().Logf("Error setting olmConfig %v", err) + return err + } + + return nil + }).Should(Succeed()) + }) + + It("should not have any copied CSVs", func() { + Eventually(func() error { + requirement, err := k8slabels.NewRequirement(operatorsv1alpha1.CopiedLabelKey, selection.Equals, []string{csv.GetNamespace()}) + if err != nil { + return err + } + + var copiedCSVs operatorsv1alpha1.ClusterServiceVersionList + err = ctx.Ctx().Client().List(context.TODO(), &copiedCSVs, &client.ListOptions{ + LabelSelector: k8slabels.NewSelector().Add(*requirement), + }) + if err != nil { + return err + } + + if numCSVs := len(copiedCSVs.Items); numCSVs != 0 { + return fmt.Errorf("Found %d copied CSVs, should be 0", numCSVs) + } + return nil + }).Should(Succeed()) + }) + + It("should be reflected in the olmConfig.Status.Condition array that the expected number of copied CSVs exist", func() { + Eventually(func() error { + var olmConfig operatorsv1.OLMConfig + if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { + return err + } + + foundCondition := meta.FindStatusCondition(olmConfig.Status.Conditions, operatorsv1.DisabledCopiedCSVsConditionType) + if foundCondition == nil { + return fmt.Errorf("%s condition not found", operatorsv1.DisabledCopiedCSVsConditionType) + } + + expectedCondition := metav1.Condition{ + Reason: "NoCopiedCSVsFound", + Message: "Copied CSVs are disabled and none were found for operators installed in AllNamespace mode", + Status: metav1.ConditionTrue, + } + + if foundCondition.Reason != expectedCondition.Reason || + foundCondition.Message != expectedCondition.Message || + foundCondition.Status != expectedCondition.Status { + return fmt.Errorf("condition does not have expected reason, message, and status. Expected %v, got %v", expectedCondition, foundCondition) + } + + return nil + }).Should(Succeed()) + }) + }) + + When("Copied CSVs are toggled back on", func() { + BeforeEach(func() { + Eventually(func() error { + var olmConfig operatorsv1.OLMConfig + if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { + return err + } + + // Exit early if copied CSVs are enabled. + if olmConfig.CopiedCSVsAreEnabled() { + return nil + } + + olmConfig.Spec = operatorsv1.OLMConfigSpec{ + Features: &operatorsv1.Features{ + DisableCopiedCSVs: getPointer(false), + }, + } + + if err := ctx.Ctx().Client().Update(context.TODO(), &olmConfig); err != nil { + return err + } + + return nil + }).Should(Succeed()) + }) + + It("should have copied CSVs in all other Namespaces", func() { + Eventually(func() error { + // find copied csvs... + requirement, err := k8slabels.NewRequirement(operatorsv1alpha1.CopiedLabelKey, selection.Equals, []string{csv.GetNamespace()}) + if err != nil { + return err + } + + var copiedCSVs operatorsv1alpha1.ClusterServiceVersionList + err = ctx.Ctx().Client().List(context.TODO(), &copiedCSVs, &client.ListOptions{ + LabelSelector: k8slabels.NewSelector().Add(*requirement), + }) + if err != nil { + return err + } + + var namespaces corev1.NamespaceList + if err := ctx.Ctx().Client().List(context.TODO(), &namespaces, &client.ListOptions{FieldSelector: fields.SelectorFromSet(map[string]string{"status.phase": "Active"})}); err != nil { + return err + } + + if len(namespaces.Items)-1 != len(copiedCSVs.Items) { + return fmt.Errorf("%d copied CSVs found, expected %d", len(copiedCSVs.Items), len(namespaces.Items)-1) + } + + return nil + }).Should(Succeed()) + }) + + It("should be reflected in the olmConfig.Status.Condition array that the expected number of copied CSVs exist", func() { + Eventually(func() error { + var olmConfig operatorsv1.OLMConfig + if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { + return err + } + foundCondition := meta.FindStatusCondition(olmConfig.Status.Conditions, operatorsv1.DisabledCopiedCSVsConditionType) + if foundCondition == nil { + return fmt.Errorf("%s condition not found", operatorsv1.DisabledCopiedCSVsConditionType) + } + + expectedCondition := metav1.Condition{ + Reason: "CopiedCSVsEnabled", + Message: "Copied CSVs are enabled and present across the cluster", + Status: metav1.ConditionFalse, + } + + if foundCondition.Reason != expectedCondition.Reason || + foundCondition.Message != expectedCondition.Message || + foundCondition.Status != expectedCondition.Status { + return fmt.Errorf("condition does not have expected reason, message, and status. Expected %v, got %v", expectedCondition, foundCondition) + } + + return nil + }).Should(Succeed()) + }) + }) +}) diff --git a/test/e2e/csv_e2e_test.go b/test/e2e/csv_e2e_test.go index 3f06844eb16..4e48dbf27c6 100644 --- a/test/e2e/csv_e2e_test.go +++ b/test/e2e/csv_e2e_test.go @@ -3,14 +3,17 @@ package e2e import ( "context" "fmt" - "strconv" - "strings" - "time" - + "github.com/google/go-cmp/cmp" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/types" - + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" + "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" + "github.com/operator-framework/operator-lifecycle-manager/test/e2e/util" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -18,337 +21,156 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/equality" k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" k8slabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/selection" - apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" "sigs.k8s.io/controller-runtime/pkg/client" - - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" - "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" + "strconv" + "strings" + "time" ) -var _ = Describe("ClusterServiceVersion", func() { - HavePhase := func(goal operatorsv1alpha1.ClusterServiceVersionPhase) types.GomegaMatcher { - return WithTransform(func(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.ClusterServiceVersionPhase { - return csv.Status.Phase - }, Equal(goal)) - } +var _ = Describe("ClusterServiceVersion2", func() { var ( - c operatorclient.ClientInterface - crc versioned.Interface + ns *corev1.Namespace + resourceManager = util.NewK8sResourceManager(ctx.Ctx()) ) - BeforeEach(func() { - c = newKubeClient() - crc = newCRClient() - }) - AfterEach(func() { - TearDown(testNamespace) + Expect(resourceManager.Reset(ns.GetName())).To(Succeed()) }) - When("a CustomResourceDefinition was installed alongside a ClusterServiceVersion", func() { - var ( - ns corev1.Namespace - crd apiextensionsv1.CustomResourceDefinition - og operatorsv1.OperatorGroup - apiname string - apifullname string - ) - + Context("SingleNamespace OperatorGroup", func() { BeforeEach(func() { - ns = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: genName("test-namespace-"), - }, - } - - Eventually(func() error { - return ctx.Ctx().Client().Create(context.Background(), &ns) - }).Should(Succeed()) - - og = operatorsv1.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: genName(fmt.Sprintf("%s-operatorgroup-", ns.GetName())), - Namespace: ns.GetName(), - }, - Spec: operatorsv1.OperatorGroupSpec{ - TargetNamespaces: []string{ns.GetName()}, - }, - } - Eventually(func() error { - return ctx.Ctx().Client().Create(context.Background(), &og) - }).Should(Succeed()) - - apiname = genName("api") - apifullname = apiname + "s.example.com" - crd = apiextensionsv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: apifullname, - Annotations: map[string]string{ - "operatorframework.io/installed-alongside-0": fmt.Sprintf("%s/associated-csv", ns.GetName()), - }, - }, - Spec: apiextensionsv1.CustomResourceDefinitionSpec{ - Group: "example.com", - Scope: apiextensionsv1.ClusterScoped, - Names: apiextensionsv1.CustomResourceDefinitionNames{ - Plural: apiname + "s", - Singular: apiname, - Kind: strings.Title(apiname), - ListKind: strings.Title(apiname) + "List", - }, - Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ - Name: "v1", - Served: true, - Storage: true, - Schema: &apiextensionsv1.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ - Type: "object", - }, - }, - }}, - }, - } - Eventually(func() error { - return ctx.Ctx().Client().Create(context.Background(), &crd) - }).Should(Succeed()) + var err error + name := genName("csv-e2e-") + ns, err = resourceManager.SetupTestNamespace(name, util.WithOperatorGroup(name)) + Expect(err).To(Succeed()) }) - AfterEach(func() { - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &crd) - }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &og) - }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &ns) - }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - }) + When("a CustomResourceDefinition was installed alongside a ClusterServiceVersion", func() { + var ( + crd apiextensionsv1.CustomResourceDefinition + apiname string + apifullname string + ) - // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2646 - It("[FLAKE] can satisfy an associated ClusterServiceVersion's ownership requirement", func() { - associated := operatorsv1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "associated-csv", - Namespace: ns.GetName(), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{{ - Name: apifullname, - Version: "v1", - Kind: "Test", - }}, - }, - InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + BeforeEach(func() { + apiname = genName("api") + apifullname = apiname + "s.example.com" + crd = apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: apifullname, + Annotations: map[string]string{ + "operatorframework.io/installed-alongside-0": fmt.Sprintf("%s/associated-csv", ns.GetName()), }, }, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &associated)).To(Succeed()) - - Eventually(func() ([]operatorsv1alpha1.RequirementStatus, error) { - if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&associated), &associated); err != nil { - return nil, err - } - var result []operatorsv1alpha1.RequirementStatus - for _, s := range associated.Status.RequirementStatus { - result = append(result, operatorsv1alpha1.RequirementStatus{ - Group: s.Group, - Version: s.Version, - Kind: s.Kind, - Name: s.Name, - Status: s.Status, - }) - } - return result, nil - }).Should(ContainElement( - operatorsv1alpha1.RequirementStatus{ - Group: apiextensionsv1.SchemeGroupVersion.Group, - Version: apiextensionsv1.SchemeGroupVersion.Version, - Kind: "CustomResourceDefinition", - Name: crd.GetName(), - Status: operatorsv1alpha1.RequirementStatusReasonPresent, - }, - )) - - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &associated) - }).Should(Succeed()) - }) - - // Without this exception, upgrades can become blocked - // when the original CSV's CRD requirement becomes - // unsatisfied. - It("can satisfy an unassociated ClusterServiceVersion's ownership requirement if replaced by an associated ClusterServiceVersion", func() { - unassociated := operatorsv1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unassociated-csv", - Namespace: ns.GetName(), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{{ - Name: apifullname, - Version: "v1", - Kind: "Test", - }}, - }, - InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Scope: apiextensionsv1.ClusterScoped, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: apiname + "s", + Singular: apiname, + Kind: strings.Title(apiname), + ListKind: strings.Title(apiname) + "List", }, - }, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &unassociated)).To(Succeed()) - - associated := operatorsv1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "associated-csv", - Namespace: ns.GetName(), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{{ - Name: apifullname, - Version: "v1", - Kind: "Test", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ + Name: "v1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + }, + }, }}, }, - InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - }, - Replaces: unassociated.GetName(), - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &associated)).To(Succeed()) - - Eventually(func() error { - return ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&unassociated), &unassociated) - }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &associated) - }).Should(Succeed()) - }) + } + Expect(resourceManager.Create(&crd)).To(Succeed()) + }) - // issue:https://github.com/operator-framework/operator-lifecycle-manager/issues/2639 - It("[FLAKE] can satisfy an unassociated ClusterServiceVersion's non-ownership requirement", func() { - unassociated := operatorsv1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unassociated-csv", - Namespace: ns.GetName(), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Required: []operatorsv1alpha1.CRDDescription{{ - Name: apifullname, - Version: "v1", - Kind: "Test", - }}, + It("can satisfy an associated ClusterServiceVersion's ownership requirement", func() { + associated := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "associated-csv", + Namespace: ns.GetName(), }, - InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{{ + Name: apifullname, + Version: "v1", + Kind: "Test", + }}, + }, + InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, }, }, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &unassociated)).To(Succeed()) - - Eventually(func() ([]operatorsv1alpha1.RequirementStatus, error) { - if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&unassociated), &unassociated); err != nil { - return nil, err - } - var result []operatorsv1alpha1.RequirementStatus - for _, s := range unassociated.Status.RequirementStatus { - result = append(result, operatorsv1alpha1.RequirementStatus{ - Group: s.Group, - Version: s.Version, - Kind: s.Kind, - Name: s.Name, - Status: s.Status, - }) } - return result, nil - }).Should(ContainElement( - operatorsv1alpha1.RequirementStatus{ - Group: apiextensionsv1.SchemeGroupVersion.Group, - Version: apiextensionsv1.SchemeGroupVersion.Version, - Kind: "CustomResourceDefinition", - Name: crd.GetName(), - Status: operatorsv1alpha1.RequirementStatusReasonPresent, - }, - )) - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &unassociated) - }).Should(Succeed()) - }) - - When("an unassociated ClusterServiceVersion in different namespace owns the same CRD", func() { - var ( - ns corev1.Namespace - ) + Expect(resourceManager.Create(&associated)).To(Succeed()) - BeforeEach(func() { - ns = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: genName("test-namespace-2-"), + Eventually(func() ([]operatorsv1alpha1.RequirementStatus, error) { + if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&associated), &associated); err != nil { + return nil, err + } + var result []operatorsv1alpha1.RequirementStatus + for _, s := range associated.Status.RequirementStatus { + result = append(result, operatorsv1alpha1.RequirementStatus{ + Group: s.Group, + Version: s.Version, + Kind: s.Kind, + Name: s.Name, + Status: s.Status, + }) + } + return result, nil + }).Should(ContainElement( + operatorsv1alpha1.RequirementStatus{ + Group: apiextensionsv1.SchemeGroupVersion.Group, + Version: apiextensionsv1.SchemeGroupVersion.Version, + Kind: "CustomResourceDefinition", + Name: crd.GetName(), + Status: operatorsv1alpha1.RequirementStatusReasonPresent, }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &ns)).To(Succeed()) + )) + }) - og = operatorsv1.OperatorGroup{ + // Without this exception, upgrades can become blocked + // when the original CSV's CRD requirement becomes + // unsatisfied. + It("can satisfy an unassociated ClusterServiceVersion's ownership requirement if replaced by an associated ClusterServiceVersion", func() { + unassociated := operatorsv1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-operatorgroup", ns.GetName()), + Name: "unassociated-csv", Namespace: ns.GetName(), }, - Spec: operatorsv1.OperatorGroupSpec{ - TargetNamespaces: []string{ns.GetName()}, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{{ + Name: apifullname, + Version: "v1", + Kind: "Test", + }}, + }, + InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + }, }, } - Expect(ctx.Ctx().Client().Create(context.TODO(), &og)).To(Succeed()) - }) - - AfterEach(func() { - Eventually(func() error { - return ctx.Ctx().Client().Delete(context.Background(), &ns) - }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - }) + Expect(resourceManager.Create(&unassociated)).To(Succeed()) - It("can satisfy the unassociated ClusterServiceVersion's ownership requirement", func() { associated := operatorsv1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "associated-csv", @@ -369,10 +191,17 @@ var _ = Describe("ClusterServiceVersion", func() { Supported: true, }, }, + Replaces: unassociated.GetName(), }, } - Expect(ctx.Ctx().Client().Create(context.Background(), &associated)).To(Succeed()) + Expect(resourceManager.Create(&associated)).To(Succeed()) + + Eventually(func() error { + return resourceManager.Get(&unassociated) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) + }) + It("can satisfy an unassociated ClusterServiceVersion's non-ownership requirement", func() { unassociated := operatorsv1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "unassociated-csv", @@ -380,7 +209,7 @@ var _ = Describe("ClusterServiceVersion", func() { }, Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{{ + Required: []operatorsv1alpha1.CRDDescription{{ Name: apifullname, Version: "v1", Kind: "Test", @@ -395,7 +224,7 @@ var _ = Describe("ClusterServiceVersion", func() { }, }, } - Expect(ctx.Ctx().Client().Create(context.Background(), &unassociated)).To(Succeed()) + Expect(resourceManager.Create(&unassociated)).To(Succeed()) Eventually(func() ([]operatorsv1alpha1.RequirementStatus, error) { if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&unassociated), &unassociated); err != nil { @@ -423,745 +252,849 @@ var _ = Describe("ClusterServiceVersion", func() { )) }) }) - }) - When("a csv exists specifying two replicas with one max unavailable", func() { - var ( - csv operatorsv1alpha1.ClusterServiceVersion - ) + When("an unassociated ClusterServiceVersion in different namespace owns the same CRD", func() { - const ( - TestReadinessGate = "operatorframework.io/test-readiness-gate" - ) + var ( + crd apiextensionsv1.CustomResourceDefinition + apiname string + apifullname string + ) - BeforeEach(func() { - csv = operatorsv1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-csv", - Namespace: testNamespace, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: "deployment", - Spec: appsv1.DeploymentSpec{ - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: &[]intstr.IntOrString{intstr.FromInt(1)}[0], - }, - }, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "foobar"}, - }, - Replicas: &[]int32{2}[0], - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"app": "foobar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "foobar", - Image: *dummyImage, - }, - }, - ReadinessGates: []corev1.PodReadinessGate{ - {ConditionType: TestReadinessGate}, - }, - }, - }, - }, + BeforeEach(func() { + apiname = genName("api") + apifullname = apiname + "s.example.com" + crd = apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: apifullname, + Annotations: map[string]string{ + "operatorframework.io/installed-alongside-0": fmt.Sprintf("%s/associated-csv", ns.GetName()), + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Scope: apiextensionsv1.ClusterScoped, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: apiname + "s", + Singular: apiname, + Kind: strings.Title(apiname), + ListKind: strings.Title(apiname) + "List", + }, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ + Name: "v1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", }, }, - }, + }}, }, - InstallModes: []operatorsv1alpha1.InstallMode{{ - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }}, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed()) - - Eventually(func() (*operatorsv1alpha1.ClusterServiceVersion, error) { - var ps corev1.PodList - if err := ctx.Ctx().Client().List(context.Background(), &ps, client.MatchingLabels{"app": "foobar"}); err != nil { - return nil, err } + Expect(resourceManager.Create(&crd)).To(Succeed()) + }) - if len(ps.Items) != 2 { - return nil, fmt.Errorf("%d pods match deployment selector, want %d", len(ps.Items), 2) + It("can satisfy the unassociated ClusterServiceVersion's ownership requirement", func() { + associated := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "associated-csv", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{{ + Name: apifullname, + Version: "v1", + Kind: "Test", + }}, + }, + InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + }, + }, } + Expect(resourceManager.Create(&associated)).To(Succeed()) - for _, pod := range ps.Items { - index := -1 - for i, c := range pod.Status.Conditions { - if c.Type == TestReadinessGate { - index = i - break - } - } - if index == -1 { - index = len(pod.Status.Conditions) - pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{Type: TestReadinessGate}) - } - if pod.Status.Conditions[index].Status == corev1.ConditionTrue { - continue - } - pod.Status.Conditions[index].Status = corev1.ConditionTrue - if err := ctx.Ctx().Client().Status().Update(context.Background(), &pod); err != nil { + unassociated := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unassociated-csv", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{{ + Name: apifullname, + Version: "v1", + Kind: "Test", + }}, + }, + InstallStrategy: newNginxInstallStrategy(genName("deployment-"), nil, nil), + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + }, + }, + } + Expect(resourceManager.Create(&unassociated)).To(Succeed()) + + Eventually(func() ([]operatorsv1alpha1.RequirementStatus, error) { + if err := resourceManager.Get(&unassociated); err != nil { return nil, err } - } + var result []operatorsv1alpha1.RequirementStatus + for _, s := range unassociated.Status.RequirementStatus { + result = append(result, operatorsv1alpha1.RequirementStatus{ + Group: s.Group, + Version: s.Version, + Kind: s.Kind, + Name: s.Name, + Status: s.Status, + }) + } + return result, nil + }).Should(ContainElement( + operatorsv1alpha1.RequirementStatus{ + Group: apiextensionsv1.SchemeGroupVersion.Group, + Version: apiextensionsv1.SchemeGroupVersion.Version, + Kind: "CustomResourceDefinition", + Name: crd.GetName(), + Status: operatorsv1alpha1.RequirementStatusReasonPresent, + }, + )) + }) + }) + }) - if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&csv), &csv); err != nil { - return nil, err - } - return &csv, nil - }).Should(HavePhase(operatorsv1alpha1.CSVPhaseSucceeded)) + Context("AllNamespaces OperatorGroup", func() { + BeforeEach(func() { + var err error + ns, err = resourceManager.SetupTestNamespace(genName("csv-e2e-"), util.WithOperatorGroup()) + Expect(err).ToNot(HaveOccurred()) }) - It("remains in phase Succeeded when only one pod is available", func() { - Eventually(func() int32 { - dep, err := c.GetDeployment(testNamespace, "deployment") - if err != nil || dep == nil { - return 0 - } - return dep.Status.ReadyReplicas - }).Should(Equal(int32(2))) + When("a csv exists specifying two replicas with one max unavailable", func() { + var ( + csv operatorsv1alpha1.ClusterServiceVersion + ) - var ps corev1.PodList - Expect(ctx.Ctx().Client().List(context.Background(), &ps, client.MatchingLabels{"app": "foobar"})).To(Succeed()) - Expect(ps.Items).To(Not(BeEmpty())) + const ( + TestReadinessGate = "operatorframework.io/test-readiness-gate" + ) - Expect(ctx.Ctx().Client().Delete(context.Background(), &ps.Items[0])).To(Succeed()) + BeforeEach(func() { + csv = operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-csv", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: "deployment", + Spec: appsv1.DeploymentSpec{ + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxUnavailable: &[]intstr.IntOrString{intstr.FromInt(1)}[0], + }, + }, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "foobar"}, + }, + Replicas: &[]int32{2}[0], + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": "foobar"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "foobar", + Image: *dummyImage, + }, + }, + ReadinessGates: []corev1.PodReadinessGate{ + {ConditionType: TestReadinessGate}, + }, + }, + }, + }, + }, + }, + }, + }, + InstallModes: []operatorsv1alpha1.InstallMode{{ + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }}, + }, + } + Expect(resourceManager.Create(&csv)) - Consistently(func() (*operatorsv1alpha1.ClusterServiceVersion, error) { - return &csv, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&csv), &csv) - }).Should(HavePhase(operatorsv1alpha1.CSVPhaseSucceeded)) - }) - }) + Eventually(func() (*operatorsv1alpha1.ClusterServiceVersion, error) { + var ps corev1.PodList + if err := ctx.Ctx().Client().List(context.Background(), &ps, client.MatchingLabels{"app": "foobar"}); err != nil { + return nil, err + } - When("a copied csv exists", func() { - var ( - target corev1.Namespace - original operatorsv1alpha1.ClusterServiceVersion - copy operatorsv1alpha1.ClusterServiceVersion - ) + if len(ps.Items) != 2 { + return nil, fmt.Errorf("%d pods match deployment selector, want %d", len(ps.Items), 2) + } - BeforeEach(func() { - target = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "watched-", - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &target)).To(Succeed()) + for _, pod := range ps.Items { + index := -1 + for i, c := range pod.Status.Conditions { + if c.Type == TestReadinessGate { + index = i + break + } + } + if index == -1 { + index = len(pod.Status.Conditions) + pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{Type: TestReadinessGate}) + } + if pod.Status.Conditions[index].Status == corev1.ConditionTrue { + continue + } + pod.Status.Conditions[index].Status = corev1.ConditionTrue + if err := ctx.Ctx().Client().Status().Update(context.Background(), &pod); err != nil { + return nil, err + } + } - original = operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "csv-", - Namespace: testNamespace, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallStrategy: newNginxInstallStrategy(genName("csv-"), nil, nil), - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&csv), &csv); err != nil { + return nil, err + } + return &csv, nil + }).Should(CSVHasPhase(operatorsv1alpha1.CSVPhaseSucceeded)) + }) + + It("remains in phase Succeeded when only one pod is available", func() { + Eventually(func() int32 { + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment", + Namespace: ns.GetName(), }, - }, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &original)).To(Succeed()) + } + err := resourceManager.Get(dep) - Eventually(func() error { - key := client.ObjectKeyFromObject(&original) - key.Namespace = target.GetName() - return ctx.Ctx().Client().Get(context.Background(), key, ©) - }).Should(Succeed()) - }) + if err != nil || dep == nil { + return 0 + } + return dep.Status.ReadyReplicas + }).Should(Equal(int32(2))) - AfterEach(func() { - if target.GetName() != "" { - Expect(ctx.Ctx().Client().Delete(context.Background(), &target)).To(Succeed()) - } + var ps corev1.PodList + Expect(ctx.Ctx().Client().List(context.Background(), &ps, client.MatchingLabels{"app": "foobar"})).To(Succeed()) + Expect(ps.Items).To(Not(BeEmpty())) + + Expect(resourceManager.Delete(&ps.Items[0])).To(Succeed()) + + Consistently(func() (*operatorsv1alpha1.ClusterServiceVersion, error) { + return &csv, resourceManager.Get(&csv) + }).Should(CSVHasPhase(operatorsv1alpha1.CSVPhaseSucceeded)) + }) }) + When("a copied csv exists", func() { + var ( + target corev1.Namespace + original operatorsv1alpha1.ClusterServiceVersion + copyCSV operatorsv1alpha1.ClusterServiceVersion + ) - It("is synchronized with the original csv", func() { - Eventually(func() error { - key := client.ObjectKeyFromObject(©) + BeforeEach(func() { + target = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "watched-", + }, + } + Expect(resourceManager.Create(&target)).To(Succeed()) - key.Namespace = target.Name - if err := ctx.Ctx().Client().Get(context.Background(), key, ©); err != nil { - return err + original = operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "csv-", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: newNginxInstallStrategy(genName("csv-"), nil, nil), + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + }, } + Expect(resourceManager.Create(&original)).To(Succeed()) - copy.Status.LastUpdateTime = &metav1.Time{Time: time.Unix(1, 0)} - return ctx.Ctx().Client().Status().Update(context.Background(), ©) - }).Should(Succeed()) + Eventually(func() error { + copyCSV.Name = original.GetName() + copyCSV.Namespace = target.GetName() + return resourceManager.Get(©CSV) + }).Should(Succeed()) + }) - Eventually(func() (bool, error) { - key := client.ObjectKeyFromObject(&original) + It("is synchronized with the original csv", func() { + Eventually(func() error { + if err := resourceManager.Get(©CSV); err != nil { + return err + } + copyCSV.Status.LastUpdateTime = &metav1.Time{Time: time.Unix(1, 0)} + return resourceManager.Update(©CSV) + }).Should(Succeed()) - if err := ctx.Ctx().Client().Get(context.Background(), key, &original); err != nil { - return false, err - } + Eventually(func() (bool, error) { + if err := resourceManager.Get(&original); err != nil { + return false, err + } - key.Namespace = target.Name - if err := ctx.Ctx().Client().Get(context.Background(), key, ©); err != nil { - return false, err - } + if err := resourceManager.Get(©CSV); err != nil { + return false, err + } - return original.Status.LastUpdateTime.Equal(copy.Status.LastUpdateTime), nil - }).Should(BeTrue(), "Change to status of copy should have been reverted") + return original.Status.LastUpdateTime.Equal(copyCSV.Status.LastUpdateTime), nil + }).Should(BeTrue(), "Change to status of copy should have been reverted") + }) }) - }) - When("a csv requires a serviceaccount solely owned by a non-csv", func() { - var ( - cm corev1.ConfigMap - sa corev1.ServiceAccount - csv operatorsv1alpha1.ClusterServiceVersion - ) + When("a csv requires a serviceaccount solely owned by a non-csv", func() { + var ( + cm corev1.ConfigMap + sa corev1.ServiceAccount + csv operatorsv1alpha1.ClusterServiceVersion + ) - BeforeEach(func() { - cm = corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cm-", - Namespace: testNamespace, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &cm)).To(Succeed()) + BeforeEach(func() { + cm = corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cm-", + Namespace: ns.GetName(), + }, + } + Expect(resourceManager.Create(&cm)).To(Succeed()) - sa = corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "sa-", - Namespace: testNamespace, - OwnerReferences: []metav1.OwnerReference{ - { - Name: cm.GetName(), - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "ConfigMap", - UID: cm.GetUID(), + sa = corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "sa-", + Namespace: ns.GetName(), + OwnerReferences: []metav1.OwnerReference{ + { + Name: cm.GetName(), + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + UID: cm.GetUID(), + }, }, }, - }, - } - Expect(ctx.Ctx().Client().Create(context.Background(), &sa)).To(Succeed()) + } + Expect(resourceManager.Create(&sa)).To(Succeed()) - csv = operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "csv-", - Namespace: testNamespace, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: "foo", - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "foo"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"app": "foo"}, + csv = operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "csv-", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: "foo", + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "foo"}, }, - Spec: corev1.PodSpec{Containers: []corev1.Container{ - { - Name: genName("foo"), - Image: *dummyImage, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": "foo"}, }, - }}, + Spec: corev1.PodSpec{Containers: []corev1.Container{ + { + Name: genName("foo"), + Image: *dummyImage, + }, + }}, + }, }, }, }, - }, - Permissions: []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: sa.GetName(), - Rules: []rbacv1.PolicyRule{}, + Permissions: []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: sa.GetName(), + Rules: []rbacv1.PolicyRule{}, + }, }, }, }, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, }, + } + Expect(resourceManager.Create(&csv)) + }) + + It("considers the serviceaccount requirement satisfied", func() { + Eventually(func() (operatorsv1alpha1.StatusReason, error) { + if err := resourceManager.Get(&csv); err != nil { + return "", err + } + for _, requirement := range csv.Status.RequirementStatus { + if requirement.Name != sa.GetName() { + continue + } + return requirement.Status, nil + } + return "", fmt.Errorf("missing expected requirement %q", sa.GetName()) + }).Should(Equal(operatorsv1alpha1.RequirementStatusReasonPresent)) + }) + }) + + It("create with unmet requirements min kube version", func() { + depName := genName("dep-") + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "999.999.999", InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, { Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, Supported: true, }, }, + InstallStrategy: newNginxInstallStrategy(depName, nil, nil), }, } - Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed()) - }) - - AfterEach(func() { - if cm.GetName() != "" { - Expect(ctx.Ctx().Client().Delete(context.Background(), &cm)).To(Succeed()) - } - }) + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending))).To(Succeed()) - It("considers the serviceaccount requirement satisfied", func() { - Eventually(func() (operatorsv1alpha1.StatusReason, error) { - if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&csv), &csv); err != nil { - return "", err - } - for _, requirement := range csv.Status.RequirementStatus { - if requirement.Name != sa.GetName() { - continue - } - return requirement.Status, nil + // Shouldn't create deployment + Consistently(func() bool { + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: depName, + Namespace: ns.GetName(), + }, } - return "", fmt.Errorf("missing expected requirement %q", sa.GetName()) - }).Should(Equal(operatorsv1alpha1.RequirementStatusReasonPresent)) + err := resourceManager.Get(dep) + return k8serrors.IsNotFound(err) + }).Should(BeTrue()) }) - }) - - It("create with unmet requirements min kube version", func() { - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "999.999.999", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + // TODO: same test but missing serviceaccount instead + It("create with unmet requirements CRD", func() { + depName := genName("dep-") + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - InstallStrategy: newNginxInstallStrategy(depName, nil, nil), - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Shouldn't create deployment - Consistently(func() bool { - _, err := c.GetDeployment(testNamespace, depName) - return k8serrors.IsNotFound(err) - }).Should(BeTrue()) - }) - // TODO: same test but missing serviceaccount instead - It("create with unmet requirements CRD", func() { - - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - InstallStrategy: newNginxInstallStrategy(depName, nil, nil), - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, { - DisplayName: "Not In Cluster", - Description: "A CRD that is not currently in the cluster", - Name: "not.in.cluster.com", - Version: "v1alpha1", - Kind: "NotInCluster", + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: newNginxInstallStrategy(depName, nil, nil), + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + DisplayName: "Not In Cluster", + Description: "A CRD that is not currently in the cluster", + Name: "not.in.cluster.com", + Version: "v1alpha1", + Kind: "NotInCluster", + }, }, }, }, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Shouldn't create deployment - Consistently(func() bool { - _, err := c.GetDeployment(testNamespace, depName) - return k8serrors.IsNotFound(err) - }).Should(BeTrue()) - }) + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending))).To(Succeed()) - It("create with unmet permissions CRD", func() { + // Shouldn't create deployment + Consistently(func() bool { + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: depName, + Namespace: ns.GetName(), + }, + } + err := resourceManager.Get(dep) + return k8serrors.IsNotFound(err) + }).Should(BeTrue()) + }) - saName := genName("dep-") - permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: saName, - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + It("create with unmet permissions CRD", func() { + saName := genName("dep-") + permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: saName, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, + }, }, }, - }, - } + } - clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: saName, - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: saName, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, + }, }, }, - }, - } + } - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + depName := genName("dep-") + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: crdName, + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, - }, - }, - } - - // Create dependency first (CRD) - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", + InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: crdName, }, }, }, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Shouldn't create deployment - Consistently(func() bool { - _, err := c.GetDeployment(testNamespace, depName) - return k8serrors.IsNotFound(err) - }).Should(BeTrue()) - }) - It("create with unmet requirements API service", func() { + } - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + // Create dependency first (CRD) + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + Expect(resourceManager.Create(&crd)).To(Succeed()) + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending))).To(Succeed()) + + // Shouldn't create deployment + Consistently(func() bool { + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: depName, + Namespace: ns.GetName(), }, + } + err := resourceManager.Get(dep) + return k8serrors.IsNotFound(err) + }).Should(BeTrue()) + }) + + It("create with unmet requirements native API", func() { + depName := genName("dep-") + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - InstallStrategy: newNginxInstallStrategy(depName, nil, nil), - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Required: []operatorsv1alpha1.APIServiceDescription{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, { - DisplayName: "Not In Cluster", - Description: "An apiservice that is not currently in the cluster", - Group: "not.in.cluster.com", - Version: "v1alpha1", - Kind: "NotInCluster", + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, + InstallStrategy: newNginxInstallStrategy(depName, nil, nil), + NativeAPIs: []metav1.GroupVersionKind{{Group: "kubenative.io", Version: "v1", Kind: "Native"}}, }, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending))).To(Succeed()) - // Shouldn't create deployment - Consistently(func() bool { - _, err := c.GetDeployment(testNamespace, depName) - return k8serrors.IsNotFound(err) - }).Should(BeTrue()) - }) - It("create with unmet permissions API service", func() { + // Shouldn't create deployment + Consistently(func() bool { + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: depName, + Namespace: ns.GetName(), + }, + } + err := resourceManager.Get(dep) + return k8serrors.IsNotFound(err) + }).Should(BeTrue()) + }) - saName := genName("dep-") - permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: saName, - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + // TODO: same test but create serviceaccount instead + It("create requirements met CRD", func() { + saName := genName("sa-") + permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: saName, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, + }, }, }, - }, - } + } - clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: saName, - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: saName, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, + }, + { + Verbs: []string{"put", "post", "get"}, + NonResourceURLs: []string{"/osb", "/osb/*"}, + }, }, }, - }, - } + } - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + depName := genName("dep-") + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), - // Cheating a little; this is an APIservice that will exist for the e2e tests - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Required: []operatorsv1alpha1.APIServiceDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ { - Group: "packages.operators.coreos.com", - Version: "v1", - Kind: "PackageManifest", - DisplayName: "Package Manifest", - Description: "An apiservice that exists", + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: crdName, + }, }, }, }, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&csv)) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending))) + Expect(resourceManager.Get(&csv)).To(Succeed()) - // Shouldn't create deployment - Consistently(func() bool { - _, err := c.GetDeployment(testNamespace, depName) - return k8serrors.IsNotFound(err) - }).Should(BeTrue()) - }) - It("create with unmet requirements native API", func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: saName, + Namespace: ns.GetName(), + OwnerReferences: []metav1.OwnerReference{{ + Name: csv.GetName(), + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + UID: csv.GetUID(), + }}, + }, + } + Expect(resourceManager.Create(&sa)).To(Succeed()) - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + OwnerReferences: []metav1.OwnerReference{{ + Name: csv.GetName(), + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + UID: csv.GetUID(), + }}, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, - }, - InstallStrategy: newNginxInstallStrategy(depName, nil, nil), - NativeAPIs: []metav1.GroupVersionKind{{Group: "kubenative.io", Version: "v1", Kind: "Native"}}, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Shouldn't create deployment - Consistently(func() bool { - _, err := c.GetDeployment(testNamespace, depName) - return k8serrors.IsNotFound(err) - }).Should(BeTrue()) - }) - // TODO: same test but create serviceaccount instead - It("create requirements met CRD", func() { + Scope: apiextensionsv1.NamespaceScoped, + }, + } + Expect(resourceManager.Create(&crd)).To(Succeed()) - saName := genName("sa-") - permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: saName, + // Create Role/Cluster Roles and RoleBindings + role := rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + Namespace: ns.GetName(), + }, Rules: []rbacv1.PolicyRule{ { Verbs: []string{"create"}, @@ -1169,3288 +1102,2763 @@ var _ = Describe("ClusterServiceVersion", func() { Resources: []string{"deployment"}, }, }, - }, - } + } + Expect(resourceManager.Create(&role)).To(Succeed()) + + roleBinding := rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + Namespace: ns.GetName(), + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: sa.GetName(), + Namespace: sa.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: role.GetName(), + }, + } + Expect(resourceManager.Create(&roleBinding)).To(Succeed()) - clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: saName, + clusterRole := rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + }, Rules: []rbacv1.PolicyRule{ { Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"deployment"}, }, + }, + } + Expect(resourceManager.Create(&clusterRole)).To(Succeed()) + + nonResourceClusterRole := rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + }, + Rules: []rbacv1.PolicyRule{ { Verbs: []string{"put", "post", "get"}, NonResourceURLs: []string{"/osb", "/osb/*"}, }, }, - }, - } + } + Expect(resourceManager.Create(&nonResourceClusterRole)).To(Succeed()) - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ - { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: crdName, - }, - }, + clusterRoleBinding := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), }, - }, - } - - // Create CSV first, knowing it will fail - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) - - sa := corev1.ServiceAccount{} - sa.SetName(saName) - sa.SetNamespace(testNamespace) - sa.SetOwnerReferences([]metav1.OwnerReference{{ - Name: fetchedCSV.GetName(), - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - UID: fetchedCSV.GetUID(), - }}) - _, err = c.CreateServiceAccount(&sa) - Expect(err).ShouldNot(HaveOccurred(), "could not create ServiceAccount %#v", sa) - - crd := apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ + Subjects: []rbacv1.Subject{ { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, + Kind: "ServiceAccount", + APIGroup: "", + Name: sa.GetName(), + Namespace: sa.GetNamespace(), }, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - } - crd.SetOwnerReferences([]metav1.OwnerReference{{ - Name: fetchedCSV.GetName(), - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - UID: fetchedCSV.GetUID(), - }}) - cleanupCRD, err := createCRD(c, crd) - defer cleanupCRD() - Expect(err).ShouldNot(HaveOccurred()) - - // Create Role/Cluster Roles and RoleBindings - role := rbacv1.Role{ - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: clusterRole.GetName(), }, - }, - } - role.SetName(genName("dep-")) - role.SetNamespace(testNamespace) - _, err = c.CreateRole(&role) - Expect(err).ShouldNot(HaveOccurred(), "could not create Role") + } + Expect(resourceManager.Create(&clusterRoleBinding)).To(Succeed()) - roleBinding := rbacv1.RoleBinding{ - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: "", - Name: sa.GetName(), - Namespace: sa.GetNamespace(), + nonResourceClusterRoleBinding := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: role.GetName(), - }, - } - roleBinding.SetName(genName("dep-")) - roleBinding.SetNamespace(testNamespace) - _, err = c.CreateRoleBinding(&roleBinding) - Expect(err).ShouldNot(HaveOccurred(), "could not create RoleBinding") - - clusterRole := rbacv1.ClusterRole{ - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: sa.GetName(), + Namespace: sa.GetNamespace(), + }, }, - }, - } - clusterRole.SetName(genName("dep-")) - _, err = c.CreateClusterRole(&clusterRole) - Expect(err).ShouldNot(HaveOccurred(), "could not create ClusterRole") - - nonResourceClusterRole := rbacv1.ClusterRole{ - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"put", "post", "get"}, - NonResourceURLs: []string{"/osb", "/osb/*"}, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: nonResourceClusterRole.GetName(), }, - }, - } - nonResourceClusterRole.SetName(genName("dep-")) - _, err = c.CreateClusterRole(&nonResourceClusterRole) - Expect(err).ShouldNot(HaveOccurred(), "could not create ClusterRole") + } + Expect(resourceManager.Create(&nonResourceClusterRoleBinding)).To(Succeed()) - clusterRoleBinding := rbacv1.ClusterRoleBinding{ - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: "", - Name: sa.GetName(), - Namespace: sa.GetNamespace(), - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: clusterRole.GetName(), - }, - } - clusterRoleBinding.SetName(genName("dep-")) - _, err = c.CreateClusterRoleBinding(&clusterRoleBinding) - Expect(err).ShouldNot(HaveOccurred(), "could not create ClusterRoleBinding") + ctx.Ctx().Logf("checking for deployment") - nonResourceClusterRoleBinding := rbacv1.ClusterRoleBinding{ - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: "", - Name: sa.GetName(), - Namespace: sa.GetNamespace(), + // wait for deployment to be ready + deployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: depName, + Namespace: ns.GetName(), }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: nonResourceClusterRole.GetName(), - }, - } - nonResourceClusterRoleBinding.SetName(genName("dep-")) - _, err = c.CreateClusterRoleBinding(&nonResourceClusterRoleBinding) - Expect(err).ShouldNot(HaveOccurred(), "could not create ClusterRoleBinding") - - ctx.Ctx().Logf("checking for deployment") - // Poll for deployment to be ready - Eventually(func() (bool, error) { - dep, err := c.GetDeployment(testNamespace, depName) - if k8serrors.IsNotFound(err) { - ctx.Ctx().Logf("deployment %s not found\n", depName) - return false, nil - } else if err != nil { - ctx.Ctx().Logf("unexpected error fetching deployment %s\n", depName) - return false, err - } - if dep.Status.UpdatedReplicas == *(dep.Spec.Replicas) && - dep.Status.Replicas == *(dep.Spec.Replicas) && - dep.Status.AvailableReplicas == *(dep.Spec.Replicas) { - ctx.Ctx().Logf("deployment ready") - return true, nil } - ctx.Ctx().Logf("deployment not ready") - return false, nil - }).Should(BeTrue()) - - fetchedCSV, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.WaitForConditions(&deployment, util.DeploymentReady())).To(Succeed()) - // Delete CRD - cleanupCRD() + // Wait for CSV to succeed + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // Wait for CSV failure - fetchedCSV, err = fetchCSV(crc, csv.Name, testNamespace, csvPendingChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Recreate the CRD - cleanupCRD, err = createCRD(c, crd) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() + // Delete CRD + Expect(resourceManager.Delete(&crd)).To(Succeed()) - // Wait for CSV success again - fetchedCSV, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("create requirements met API service", func() { + // Wait for CSV failure + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending))).To(Succeed()) - sa := corev1.ServiceAccount{} - sa.SetName(genName("sa-")) - sa.SetNamespace(testNamespace) - _, err := c.CreateServiceAccount(&sa) - Expect(err).ShouldNot(HaveOccurred(), "could not create ServiceAccount") + // Recreate the CRD + Expect(resourceManager.Create(&crd)).To(Succeed()) - permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: sa.GetName(), - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, - }, + // Wait for CSV success again + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) + }) + It("create requirements met API service", func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("sa-"), + Namespace: ns.GetName(), }, - }, - } + } + Expect(resourceManager.Create(&sa)).To(Succeed()) - clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: sa.GetName(), - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: sa.GetName(), + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, + }, }, }, - }, - } + } - depName := genName("dep-") - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), - // Cheating a little; this is an APIservice that will exist for the e2e tests - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Required: []operatorsv1alpha1.APIServiceDescription{ + clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: sa.GetName(), + Rules: []rbacv1.PolicyRule{ { - Group: "packages.operators.coreos.com", - Version: "v1", - Kind: "PackageManifest", - DisplayName: "Package Manifest", - Description: "An apiservice that exists", + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, }, }, }, - }, - } + } - // Create Role/Cluster Roles and RoleBindings - role := rbacv1.Role{ - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + depName := genName("dep-") + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - }, - } - role.SetName(genName("dep-")) - role.SetNamespace(testNamespace) - _, err = c.CreateRole(&role) - Expect(err).ShouldNot(HaveOccurred(), "could not create Role") - - roleBinding := rbacv1.RoleBinding{ - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: "", - Name: sa.GetName(), - Namespace: sa.GetNamespace(), + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: role.GetName(), - }, - } - roleBinding.SetName(genName("dep-")) - roleBinding.SetNamespace(testNamespace) - _, err = c.CreateRoleBinding(&roleBinding) - Expect(err).ShouldNot(HaveOccurred(), "could not create RoleBinding") - - clusterRole := rbacv1.ClusterRole{ - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"deployment"}, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: newNginxInstallStrategy(depName, permissions, clusterPermissions), + // Cheating a little; this is an APIservice that will exist for the e2e tests + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Required: []operatorsv1alpha1.APIServiceDescription{ + { + Group: "packages.operators.coreos.com", + Version: "v1", + Kind: "PackageManifest", + DisplayName: "Package Manifest", + Description: "An apiservice that exists", + }, + }, + }, }, - }, - } - clusterRole.SetName(genName("dep-")) - _, err = c.CreateClusterRole(&clusterRole) - Expect(err).ShouldNot(HaveOccurred(), "could not create ClusterRole") + } - clusterRoleBinding := rbacv1.ClusterRoleBinding{ - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: "", - Name: sa.GetName(), - Namespace: sa.GetNamespace(), + // Create Role/Cluster Roles and RoleBindings + role := rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + Namespace: ns.GetName(), }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: clusterRole.GetName(), - }, - } - clusterRoleBinding.SetName(genName("dep-")) - _, err = c.CreateClusterRoleBinding(&clusterRoleBinding) - Expect(err).ShouldNot(HaveOccurred(), "could not create ClusterRoleBinding") - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Fetch cluster service version again to check for unnecessary control loops - sameCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - Expect(equality.Semantic.DeepEqual(fetchedCSV, sameCSV)).Should(BeTrue(), diff.ObjectDiff(fetchedCSV, sameCSV)) - }) - It("create with owned API service", func() { - - depName := genName("hat-server") - mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) - version := "v1alpha1" - mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") - mockKinds := []string{"fez", "fedora"} - depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) - apiServiceName := strings.Join([]string{version, mockGroup}, ".") - - // Create CSV for the package-server - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: depName, - Spec: depSpec, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, + }, }, - }, - } - - owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) - for i, kind := range mockKinds { - owned[i] = operatorsv1alpha1.APIServiceDescription{ - Name: apiServiceName, - Group: mockGroup, - Version: version, - Kind: kind, - DeploymentName: depName, - ContainerPort: int32(5443), - DisplayName: kind, - Description: fmt.Sprintf("A %s", kind), } - } + Expect(resourceManager.Create(&role)).To(Succeed()) - csv := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, + roleBinding := rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + Namespace: ns.GetName(), + }, + Subjects: []rbacv1.Subject{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Kind: "ServiceAccount", + APIGroup: "", + Name: sa.GetName(), + Namespace: sa.GetNamespace(), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: role.GetName(), }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, - }, - }, - } - csv.SetName(depName) - - // Create the APIService CSV - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer func() { - watcher, err := c.ApiregistrationV1Interface().ApiregistrationV1().APIServices().Watch(context.TODO(), metav1.ListOptions{FieldSelector: "metadata.name=" + apiServiceName}) - Expect(err).ShouldNot(HaveOccurred()) - - deleted := make(chan struct{}) - go func() { - defer GinkgoRecover() - events := watcher.ResultChan() - for { - select { - case evt := <-events: - if evt.Type == watch.Deleted { - deleted <- struct{}{} - return - } - case <-time.After(pollDuration): - Fail("API service not cleaned up after CSV deleted") - } - } - }() - - cleanupCSV() - <-deleted - }() - - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Should create Deployment - dep, err := c.GetDeployment(testNamespace, depName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Deployment") - - // Should create APIService - apiService, err := c.GetAPIService(apiServiceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected APIService") - - // Should create Service - serviceName := fmt.Sprintf("%s-service", depName) - _, err = c.GetService(testNamespace, serviceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Service") - - // Should create certificate Secret - secretName := fmt.Sprintf("%s-cert", serviceName) - _, err = c.GetSecret(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Secret") - - // Should create a Role for the Secret - _, err = c.GetRole(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Secret Role") - - // Should create a RoleBinding for the Secret - _, err = c.GetRoleBinding(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting exptected Secret RoleBinding") - - // Should create a system:auth-delegator Cluster RoleBinding - _, err = c.GetClusterRoleBinding(fmt.Sprintf("%s-system:auth-delegator", serviceName)) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected system:auth-delegator ClusterRoleBinding") - - // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system - _, err = c.GetRoleBinding("kube-system", fmt.Sprintf("%s-auth-reader", serviceName)) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected extension-apiserver-authentication-reader RoleBinding") - - // Store the ca sha annotation - oldCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey] - Expect(ok).Should(BeTrue(), "expected olm sha annotation not present on existing pod template") - - // Induce a cert rotation - Eventually(Apply(fetchedCSV, func(csv *operatorsv1alpha1.ClusterServiceVersion) error { - now := metav1.Now() - csv.Status.CertsLastUpdated = &now - csv.Status.CertsRotateAt = &now - return nil - })).Should(Succeed()) - - _, err = fetchCSV(crc, csv.Name, testNamespace, func(csv *operatorsv1alpha1.ClusterServiceVersion) bool { - // Should create deployment - dep, err = c.GetDeployment(testNamespace, depName) - if err != nil { - return false - } - - // Should have a new ca hash annotation - newCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey] - if !ok { - ctx.Ctx().Logf("expected olm sha annotation not present in new pod template") - return false - } - - if newCAAnnotation != oldCAAnnotation { - // Check for success - return csvSucceededChecker(csv) } + Expect(resourceManager.Create(&roleBinding)).To(Succeed()) - return false - }) - Expect(err).ShouldNot(HaveOccurred(), "failed to rotate cert") - - // Get the APIService UID - oldAPIServiceUID := apiService.GetUID() - - // Delete the APIService - err = c.DeleteAPIService(apiServiceName, &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - // Wait for CSV success - fetchedCSV, err = fetchCSV(crc, csv.GetName(), testNamespace, func(csv *operatorsv1alpha1.ClusterServiceVersion) bool { - // Should create an APIService - apiService, err := c.GetAPIService(apiServiceName) - if err != nil { - return false - } - - if csvSucceededChecker(csv) { - return apiService.GetUID() != oldAPIServiceUID - } - return false - }) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("update with owned API service", func() { - - depName := genName("hat-server") - mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) - version := "v1alpha1" - mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") - mockKinds := []string{"fedora"} - depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) - apiServiceName := strings.Join([]string{version, mockGroup}, ".") - - // Create CSVs for the hat-server - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: depName, - Spec: depSpec, + clusterRole := rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), }, - }, - } - - owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) - for i, kind := range mockKinds { - owned[i] = operatorsv1alpha1.APIServiceDescription{ - Name: apiServiceName, - Group: mockGroup, - Version: version, - Kind: kind, - DeploymentName: depName, - ContainerPort: int32(5443), - DisplayName: kind, - Description: fmt.Sprintf("A %s", kind), - } - } - - csv := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, + Rules: []rbacv1.PolicyRule{ { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"deployment"}, }, + }, + } + Expect(resourceManager.Create(&clusterRole)).To(Succeed()) + + clusterRoleBinding := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("dep-"), + }, + Subjects: []rbacv1.Subject{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Kind: "ServiceAccount", + APIGroup: "", + Name: sa.GetName(), + Namespace: sa.GetNamespace(), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: clusterRole.GetName(), }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, - }, - }, - } - csv.SetName("csv-hat-1") + } + Expect(resourceManager.Create(&clusterRoleBinding)).To(Succeed()) - // Create the APIService CSV - _, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.Get(&csv)).To(Succeed()) - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Fetch cluster service version again to check for unnecessary control loops + sameCSV := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: csv.GetName(), + Namespace: csv.GetNamespace(), + }, + } + Expect(resourceManager.Get(&sameCSV)).To(Succeed()) + Expect(equality.Semantic.DeepEqual(csv, sameCSV)).Should(BeTrue(), cmp.Diff(csv, sameCSV)) + }) - // Should create Deployment - _, err = c.GetDeployment(testNamespace, depName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Deployment") - - // Should create APIService - _, err = c.GetAPIService(apiServiceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected APIService") - - // Should create Service - serviceName := fmt.Sprintf("%s-service", depName) - _, err = c.GetService(testNamespace, serviceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Service") - - // Should create certificate Secret - secretName := fmt.Sprintf("%s-cert", serviceName) - _, err = c.GetSecret(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Secret") - - // Should create a Role for the Secret - _, err = c.GetRole(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Secret Role") - - // Should create a RoleBinding for the Secret - _, err = c.GetRoleBinding(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting exptected Secret RoleBinding") - - // Should create a system:auth-delegator Cluster RoleBinding - _, err = c.GetClusterRoleBinding(fmt.Sprintf("%s-system:auth-delegator", serviceName)) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected system:auth-delegator ClusterRoleBinding") - - // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system - _, err = c.GetRoleBinding("kube-system", fmt.Sprintf("%s-auth-reader", serviceName)) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected extension-apiserver-authentication-reader RoleBinding") - - // Create a new CSV that owns the same API Service and replace the old CSV - csv2 := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: csv.Name, - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ + It("create with owned API service", func() { + depName := genName("hat-server") + mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) + version := "v1alpha1" + mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") + mockKinds := []string{"fez", "fedora"} + depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) + apiServiceName := strings.Join([]string{version, mockGroup}, ".") + + // Create CSV for the package-server + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + Name: depName, + Spec: depSpec, }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + }, + } + + owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) + for i, kind := range mockKinds { + owned[i] = operatorsv1alpha1.APIServiceDescription{ + Name: apiServiceName, + Group: mockGroup, + Version: version, + Kind: kind, + DeploymentName: depName, + ContainerPort: int32(5443), + DisplayName: kind, + Description: fmt.Sprintf("A %s", kind), + } + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: depName, + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, - }, - }, - } - csv2.SetName("csv-hat-2") + } - // Create CSV2 to replace CSV - cleanupCSV2, err := createCSV(c, crc, csv2, testNamespace, false, true) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV2() + // Create the APIService CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) - _, err = fetchCSV(crc, csv2.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Wait for CSV to succeed + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // Should create Deployment - _, err = c.GetDeployment(testNamespace, depName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Deployment") + // Should create Deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), depName, dep) + }).Should(Succeed()) - // Should create APIService - _, err = c.GetAPIService(apiServiceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected APIService") + // Should create APIService + apiService := &apiregistrationv1.APIService{} + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, apiService) + }).Should(Succeed()) - // Should create Service - Eventually(func() error { - _, err := c.GetService(testNamespace, serviceName) - return err - }, timeout, interval).ShouldNot(HaveOccurred()) + // Should create Service + serviceName := fmt.Sprintf("%s-service", depName) + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), serviceName, &corev1.Service{}) + }).Should(Succeed()) - // Should create certificate Secret - secretName = fmt.Sprintf("%s-cert", serviceName) - Eventually(func() error { - _, err = c.GetSecret(testNamespace, secretName) - return err - }, timeout, interval).ShouldNot(HaveOccurred()) + // Should create certificate Secret + secretName := fmt.Sprintf("%s-cert", serviceName) + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &corev1.Secret{}) + }).Should(Succeed()) - // Should create a Role for the Secret - _, err = c.GetRole(testNamespace, secretName) - Eventually(func() error { - _, err = c.GetRole(testNamespace, secretName) - return err - }, timeout, interval).ShouldNot(HaveOccurred()) + // Should create a Role for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.Role{}) + }).Should(Succeed()) - // Should create a RoleBinding for the Secret - Eventually(func() error { - _, err = c.GetRoleBinding(testNamespace, secretName) - return err - }, timeout, interval).ShouldNot(HaveOccurred()) + // Should create a RoleBinding for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - // Should create a system:auth-delegator Cluster RoleBinding - Eventually(func() error { - _, err = c.GetClusterRoleBinding(fmt.Sprintf("%s-system:auth-delegator", serviceName)) - return err - }, timeout, interval).ShouldNot(HaveOccurred()) + // Should create a system:auth-delegator Cluster RoleBinding + clusterRoleBindingName := fmt.Sprintf("%s-system:auth-delegator", serviceName) + Eventually(func() error { + return resourceManager.GetByKey("", clusterRoleBindingName, &rbacv1.ClusterRoleBinding{}) + }).Should(Succeed()) - // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system - Eventually(func() error { - _, err = c.GetRoleBinding("kube-system", fmt.Sprintf("%s-auth-reader", serviceName)) - return err - }, timeout, interval).ShouldNot(HaveOccurred()) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected extension-apiserver-authentication-reader RoleBinding") + // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system + roleBindingName := fmt.Sprintf("%s-auth-reader", serviceName) + Eventually(func() error { + return resourceManager.GetByKey("kube-system", roleBindingName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - // Should eventually GC the CSV - Eventually(func() bool { - return csvExists(crc, csv.Name) - }).Should(BeFalse()) + // Store the ca sha annotation + oldCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey] + Expect(ok).Should(BeTrue(), "expected olm sha annotation not present on existing pod template") - // Rename the initial CSV - csv.SetName("csv-hat-3") + // Induce a cert rotation + Expect(resourceManager.Apply(&csv, func(csv *operatorsv1alpha1.ClusterServiceVersion) error { + now := metav1.Now() + csv.Status.CertsLastUpdated = &now + csv.Status.CertsRotateAt = &now + return nil + })).To(Succeed()) - // Recreate the old CSV - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, true) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + // Wait for deployment certificate to rotate + Expect(resourceManager.WaitForConditions(dep, util.DeploymentCertificateRotated(oldCAAnnotation))).To(Succeed()) - fetched, err := fetchCSV(crc, csv.Name, testNamespace, buildCSVReasonChecker(operatorsv1alpha1.CSVReasonOwnerConflict)) - Expect(err).ShouldNot(HaveOccurred()) - Expect(fetched.Status.Phase).Should(Equal(operatorsv1alpha1.CSVPhaseFailed)) - }) - It("create same CSV with owned API service multi namespace", func() { + // Expect that csv to reach success state + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // Create new namespace in a new operator group - secondNamespaceName := genName(testNamespace + "-") - matchingLabel := map[string]string{"inGroup": secondNamespaceName} + // Get the APIService UID + oldAPIServiceUID := apiService.GetUID() - _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: secondNamespaceName, - Labels: matchingLabel, - }, - }, metav1.CreateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - defer func() { - err = c.KubernetesInterface().CoreV1().Namespaces().Delete(context.TODO(), secondNamespaceName, metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - }() + // Delete the APIService + Expect(resourceManager.Delete(apiService)).To(Succeed()) - // Create a new operator group for the new namespace - operatorGroup := operatorsv1.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: genName("e2e-operator-group-"), - Namespace: secondNamespaceName, - }, - Spec: operatorsv1.OperatorGroupSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: matchingLabel, + // Wait for re-creation + Eventually(func() error { + return resourceManager.Get(apiService) + }).Should(Succeed()) + + // Wait for csv to reach a success state + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) + + // Get api service again and check if it has been updated + Expect(resourceManager.Get(apiService)).To(Succeed()) + Expect(apiService.GetUID()).ToNot(Equal(oldAPIServiceUID)) + + // Delete CSV + Expect(resourceManager.Delete(&csv)).To(Succeed()) + + // Wait for api service to be deleted + Eventually(func() error { + return resourceManager.Get(apiService) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) + }) + + It("update with owned API service", func() { + depName := genName("hat-server") + mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) + version := "v1alpha1" + mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") + mockKinds := []string{"fedora"} + depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) + apiServiceName := strings.Join([]string{version, mockGroup}, ".") + + // Create CSVs for the hat-server + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: depName, + Spec: depSpec, + }, }, - }, - } - _, err = crc.OperatorsV1().OperatorGroups(secondNamespaceName).Create(context.TODO(), &operatorGroup, metav1.CreateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - defer func() { - err = crc.OperatorsV1().OperatorGroups(secondNamespaceName).Delete(context.TODO(), operatorGroup.Name, metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - }() - - ctx.Ctx().Logf("Waiting on new operator group to have correct status") - Eventually(func() ([]string, error) { - og, err := crc.OperatorsV1().OperatorGroups(secondNamespaceName).Get(context.TODO(), operatorGroup.Name, metav1.GetOptions{}) - if err != nil { - return nil, err } - return og.Status.Namespaces, nil - }).Should(ConsistOf([]string{secondNamespaceName})) - - depName := genName("hat-server") - mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) - version := "v1alpha1" - mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") - mockKinds := []string{"fedora"} - depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) - apiServiceName := strings.Join([]string{version, mockGroup}, ".") - - // Create CSVs for the hat-server - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: depName, - Spec: depSpec, - }, - }, - } - owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) - for i, kind := range mockKinds { - owned[i] = operatorsv1alpha1.APIServiceDescription{ - Name: apiServiceName, - Group: mockGroup, - Version: version, - Kind: kind, - DeploymentName: depName, - ContainerPort: int32(5443), - DisplayName: kind, - Description: fmt.Sprintf("A %s", kind), + owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) + for i, kind := range mockKinds { + owned[i] = operatorsv1alpha1.APIServiceDescription{ + Name: apiServiceName, + Group: mockGroup, + Version: version, + Kind: kind, + DeploymentName: depName, + ContainerPort: int32(5443), + DisplayName: kind, + Description: fmt.Sprintf("A %s", kind), + } } - } - csv := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + csv := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-1", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, - }, - }, - } - csv.SetName("csv-hat-1") + } - // Create the initial CSV - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + // Create the APIService CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should create Deployment + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), depName, &appsv1.Deployment{}) + }).Should(Succeed()) - // Should create Deployment - _, err = c.GetDeployment(testNamespace, depName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Deployment") - - // Should create APIService - _, err = c.GetAPIService(apiServiceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected APIService") - - // Should create Service - serviceName := fmt.Sprintf("%s-service", depName) - _, err = c.GetService(testNamespace, serviceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Service") - - // Should create certificate Secret - secretName := fmt.Sprintf("%s-cert", serviceName) - _, err = c.GetSecret(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Secret") - - // Should create a Role for the Secret - _, err = c.GetRole(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected Secret Role") - - // Should create a RoleBinding for the Secret - _, err = c.GetRoleBinding(testNamespace, secretName) - Expect(err).ShouldNot(HaveOccurred(), "error getting exptected Secret RoleBinding") - - // Should create a system:auth-delegator Cluster RoleBinding - _, err = c.GetClusterRoleBinding(fmt.Sprintf("%s-system:auth-delegator", serviceName)) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected system:auth-delegator ClusterRoleBinding") - - // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system - _, err = c.GetRoleBinding("kube-system", fmt.Sprintf("%s-auth-reader", serviceName)) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected extension-apiserver-authentication-reader RoleBinding") - - // Create a new CSV that owns the same API Service but in a different namespace - csv2 := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, - }, - }, - } - csv2.SetName("csv-hat-2") + // Should create APIService + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, &apiregistrationv1.APIService{}) + }).Should(Succeed()) - // Create CSV2 to replace CSV - _, err = createCSV(c, crc, csv2, secondNamespaceName, false, true) - Expect(err).ShouldNot(HaveOccurred()) + // Should create Service + serviceName := fmt.Sprintf("%s-service", depName) + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), serviceName, &corev1.Service{}) + }).Should(Succeed()) - _, err = fetchCSV(crc, csv2.Name, secondNamespaceName, csvFailedChecker) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("orphaned API service clean up", func() { + // Should create certificate Secret + secretName := fmt.Sprintf("%s-cert", serviceName) + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &corev1.Secret{}) + }).Should(Succeed()) - mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) - version := "v1alpha1" - apiServiceName := strings.Join([]string{version, mockGroup}, ".") + // Should create a Role for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.Role{}) + }).Should(Succeed()) - apiService := &apiregistrationv1.APIService{ - ObjectMeta: metav1.ObjectMeta{ - Name: apiServiceName, - }, - Spec: apiregistrationv1.APIServiceSpec{ - Group: mockGroup, - Version: version, - GroupPriorityMinimum: 100, - VersionPriority: 100, - }, - } + // Should create a RoleBinding for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - watcher, err := c.ApiregistrationV1Interface().ApiregistrationV1().APIServices().Watch(context.TODO(), metav1.ListOptions{FieldSelector: "metadata.name=" + apiServiceName}) - Expect(err).ShouldNot(HaveOccurred()) + // Should create a system:auth-delegator Cluster RoleBinding + clusterRoleBindingName := fmt.Sprintf("%s-system:auth-delegator", serviceName) + Eventually(func() error { + return resourceManager.GetByKey("", clusterRoleBindingName, &rbacv1.ClusterRoleBinding{}) + }).Should(Succeed()) - deleted := make(chan struct{}) - quit := make(chan struct{}) - defer close(quit) - go func() { - defer GinkgoRecover() - events := watcher.ResultChan() - for { - select { - case <-quit: - return - case evt := <-events: - if evt.Type == watch.Deleted { - deleted <- struct{}{} - } - case <-time.After(pollDuration): - Fail("orphaned apiservice not cleaned up as expected") - } - } - }() - - _, err = c.CreateAPIService(apiService) - Expect(err).ShouldNot(HaveOccurred(), "error creating expected APIService") - orphanedAPISvc, err := c.GetAPIService(apiServiceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected APIService") - - newLabels := map[string]string{"olm.owner": "hat-serverfd4r5", "olm.owner.kind": "ClusterServiceVersion", "olm.owner.namespace": "nonexistent-namespace"} - orphanedAPISvc.SetLabels(newLabels) - _, err = c.UpdateAPIService(orphanedAPISvc) - Expect(err).ShouldNot(HaveOccurred(), "error updating APIService") - <-deleted - - _, err = c.CreateAPIService(apiService) - Expect(err).ShouldNot(HaveOccurred(), "error creating expected APIService") - orphanedAPISvc, err = c.GetAPIService(apiServiceName) - Expect(err).ShouldNot(HaveOccurred(), "error getting expected APIService") - - newLabels = map[string]string{"olm.owner": "hat-serverfd4r5", "olm.owner.kind": "ClusterServiceVersion", "olm.owner.namespace": testNamespace} - orphanedAPISvc.SetLabels(newLabels) - _, err = c.UpdateAPIService(orphanedAPISvc) - Expect(err).ShouldNot(HaveOccurred(), "error updating APIService") - <-deleted - }) - It("CSV annotations overwrite pod template annotations defined in a StrategyDetailsDeployment", func() { - // Create a StrategyDetailsDeployment that defines the `foo1` and `foo2` annotations on a pod template - nginxName := genName("nginx-") - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(nginxName), - }, - }, - } - strategy.DeploymentSpecs[0].Spec.Template.Annotations = map[string]string{ - "foo1": "notBar1", - "foo2": "bar2", - } + // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system + roleBindingName := fmt.Sprintf("%s-auth-reader", serviceName) + Eventually(func() error { + return resourceManager.GetByKey("kube-system", roleBindingName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - // Create a CSV that defines the `foo1` and `foo3` annotations - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - Annotations: map[string]string{ - "foo1": "bar1", - "foo3": "bar3", + // Create a new CSV that owns the same API Service and replace the old CSV + csv2 := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-2", + Namespace: ns.GetName(), }, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: csv.Name, + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - }, - } + } - // Create the CSV and make sure to clean it up - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + // Create the APIService CSV + Expect(resourceManager.Create(&csv2)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv2, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should create Deployment + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), depName, &appsv1.Deployment{}) + }).Should(Succeed()) - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) + // Should create APIService + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, &apiregistrationv1.APIService{}) + }).Should(Succeed()) - // Make sure that the pods annotations are correct - annotations := dep.Spec.Template.Annotations - Expect(annotations["foo1"]).Should(Equal("bar1")) - Expect(annotations["foo2"]).Should(Equal("bar2")) - Expect(annotations["foo3"]).Should(Equal("bar3")) - }) - It("Set labels for the Deployment created via the ClusterServiceVersion", func() { - // Create a StrategyDetailsDeployment that defines labels for Deployment inside - nginxName := genName("nginx-") - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(nginxName), - Label: k8slabels.Set{ - "application": "nginx", - "application.type": "proxy", - }, - }, - }, - } + // Should create Service + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), serviceName, &corev1.Service{}) + }).Should(Succeed()) - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - }, - } + // Should create certificate Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &corev1.Secret{}) + }).Should(Succeed()) - // Create the CSV and make sure to clean it up - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + // Should create a Role for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.Role{}) + }).Should(Succeed()) - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should create a RoleBinding for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) - - // Make sure that the deployment labels are correct - labels := dep.GetLabels() - Expect(labels["olm.owner"]).Should(Equal(csv.GetName())) - Expect(labels["olm.owner.namespace"]).Should(Equal(testNamespace)) - Expect(labels["application"]).Should(Equal("nginx")) - Expect(labels["application.type"]).Should(Equal("proxy")) - }) - It("update same deployment name", func() { + // Should create a system:auth-delegator Cluster RoleBinding + Eventually(func() error { + return resourceManager.GetByKey("", clusterRoleBindingName, &rbacv1.ClusterRoleBinding{}) + }).Should(Succeed()) - // Create dependency first (CRD) - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, - }, - }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) + // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system + Eventually(func() error { + return resourceManager.GetByKey("kube-system", roleBindingName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - // Create "current" CSV - nginxName := genName("nginx-") - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(nginxName), - }, - }, - } + // Should eventually GC the CSV + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csv.Name, &operatorsv1alpha1.ClusterServiceVersion{}) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - Expect(err).ShouldNot(HaveOccurred()) + // Rename the initial CSV + csv.SetName("csv-hat-3") - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + // Recreate the old CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions( + &csv, + util.CSVConditionReason(operatorsv1alpha1.CSVReasonOwnerConflict), + util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseFailed))).To(Succeed()) + }) + + It("create same CSV with owned API service multi namespace", func() { + // Create new namespace in a new operator group + secondNamespaceName := genName(ns.GetName() + "-") + matchingLabel := map[string]string{"inGroup": secondNamespaceName} + + Expect(resourceManager.Create(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: secondNamespaceName, + Labels: matchingLabel, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + })).To(Succeed()) + + // Create a new operator group for the new namespace + operatorGroup := operatorsv1.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("e2e-operator-group-"), + Namespace: secondNamespaceName, }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ - { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster", - }, + Spec: operatorsv1.OperatorGroupSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: matchingLabel, }, }, - }, - } - - // Don't need to cleanup this CSV, it will be deleted by the upgrade process - _, err = createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) + } + Expect(resourceManager.Create(&operatorGroup)).To(Succeed()) - // Create "updated" CSV - strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - // Same name - Name: strategy.DeploymentSpecs[0].Name, - // Different spec - Spec: newNginxDeployment(nginxName), - }, - }, - } + ctx.Ctx().Logf("Waiting on new operator group to have correct status") + Expect(resourceManager.WaitForConditions(&operatorGroup, util.OperatorGroupNamespaces(secondNamespaceName))) - Expect(err).ShouldNot(HaveOccurred()) + depName := genName("hat-server") + mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) + version := "v1alpha1" + mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") + mockKinds := []string{"fedora"} + depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) + apiServiceName := strings.Join([]string{version, mockGroup}, ".") - csvNew := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: csv.Name, - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, + // Create CSVs for the hat-server + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: depName, + Spec: depSpec, }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategyNew, + } + + owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) + for i, kind := range mockKinds { + owned[i] = operatorsv1alpha1.APIServiceDescription{ + Name: apiServiceName, + Group: mockGroup, + Version: version, + Kind: kind, + DeploymentName: depName, + ContainerPort: int32(5443), + DisplayName: kind, + Description: fmt.Sprintf("A %s", kind), + } + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-1", + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster", + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, + }, }, - }, - } + } - cleanupNewCSV, err := createCSV(c, crc, csvNew, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupNewCSV() + // Create the APIService CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Wait for updated CSV to succeed - fetchedCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should create Deployment + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), depName, &appsv1.Deployment{}) + }).Should(Succeed()) - // Should have updated existing deployment - depUpdated, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depUpdated).ShouldNot(BeNil()) - Expect(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name).Should(Equal(depUpdated.Spec.Template.Spec.Containers[0].Name)) + // Should create APIService + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, &apiregistrationv1.APIService{}) + }).Should(Succeed()) - // Should eventually GC the CSV - Eventually(func() bool { - return csvExists(crc, csv.Name) - }).Should(BeFalse()) + // Should create Service + serviceName := fmt.Sprintf("%s-service", depName) + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), serviceName, &corev1.Service{}) + }).Should(Succeed()) - // Fetch cluster service version again to check for unnecessary control loops - sameCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - Expect(equality.Semantic.DeepEqual(fetchedCSV, sameCSV)).Should(BeTrue(), diff.ObjectDiff(fetchedCSV, sameCSV)) - }) - It("update different deployment name", func() { + // Should create certificate Secret + secretName := fmt.Sprintf("%s-cert", serviceName) + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &corev1.Secret{}) + }).Should(Succeed()) - // Create dependency first (CRD) - crdPlural := genName("ins2") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, - }, - }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() + // Should create a Role for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.Role{}) + }).Should(Succeed()) - // create "current" CSV - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(genName("nginx-")), - }, - }, - } + // Should create a RoleBinding for the Secret + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), secretName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) - Expect(err).ShouldNot(HaveOccurred()) + // Should create a system:auth-delegator Cluster RoleBinding + clusterRoleBindingName := fmt.Sprintf("%s-system:auth-delegator", serviceName) + Eventually(func() error { + return resourceManager.GetByKey("", clusterRoleBindingName, &rbacv1.ClusterRoleBinding{}) + }).Should(Succeed()) - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + // Should create an extension-apiserver-authentication-reader RoleBinding in kube-system + roleBindingName := fmt.Sprintf("%s-auth-reader", serviceName) + Eventually(func() error { + return resourceManager.GetByKey("kube-system", roleBindingName, &rbacv1.RoleBinding{}) + }).Should(Succeed()) + + // Create a new CSV that owns the same API Service but in a different namespace + csv2 := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-2", + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster2", + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, + }, }, - }, - } + } - // don't need to clean up this CSV, it will be deleted by the upgrade process - _, err = createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&csv2)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv2, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseFailed))) + }) - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + It("orphaned API service clean up", func() { - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) + mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) + version := "v1alpha1" + apiServiceName := strings.Join([]string{version, mockGroup}, ".") - // Create "updated" CSV - strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep2"), - Spec: newNginxDeployment(genName("nginx-")), + apiService := &apiregistrationv1.APIService{ + ObjectMeta: metav1.ObjectMeta{ + Name: apiServiceName, }, - }, - } + Spec: apiregistrationv1.APIServiceSpec{ + Group: mockGroup, + Version: version, + GroupPriorityMinimum: 100, + VersionPriority: 100, + }, + } + Expect(resourceManager.Create(apiService)).To(Succeed()) - Expect(err).ShouldNot(HaveOccurred()) + // Update orphan api with olm.owner.namespace + orphanedAPISvc := &apiregistrationv1.APIService{} + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, orphanedAPISvc) + }).Should(Succeed()) - csvNew := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv2"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: csv.Name, - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategyNew, - }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ - { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster2", - }, - }, - }, - }, - } + newLabels := map[string]string{"olm.owner": "hat-serverfd4r5", "olm.owner.kind": "ClusterServiceVersion", "olm.owner.namespace": "nonexistent-namespace"} + orphanedAPISvc.SetLabels(newLabels) + Expect(resourceManager.Update(orphanedAPISvc)).To(Succeed()) - cleanupNewCSV, err := createCSV(c, crc, csvNew, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupNewCSV() + // Ensure orphan api is deleted + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, orphanedAPISvc) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - // Wait for updated CSV to succeed - fetchedCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Recreate the orphan api service + Expect(resourceManager.Create(apiService)).To(Succeed()) + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, orphanedAPISvc) + }).Should(Succeed()) - // Fetch cluster service version again to check for unnecessary control loops - sameCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - Expect(equality.Semantic.DeepEqual(fetchedCSV, sameCSV)).Should(BeTrue(), diff.ObjectDiff(fetchedCSV, sameCSV)) + // Add olm labels with good olm.owner.namespace + newLabels = map[string]string{"olm.owner": "hat-serverfd4r5", "olm.owner.kind": "ClusterServiceVersion", "olm.owner.namespace": ns.GetName()} + orphanedAPISvc.SetLabels(newLabels) - // Should have created new deployment and deleted old - depNew, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depNew).ShouldNot(BeNil()) - err = waitForDeploymentToDelete(c, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Update(orphanedAPISvc)).To(Succeed()) - // Should eventually GC the CSV - Eventually(func() bool { - return csvExists(crc, csv.Name) - }).Should(BeFalse()) - }) - It("update multiple intermediates", func() { + // Ensure it is deleted + Eventually(func() error { + return resourceManager.GetByKey("", apiServiceName, orphanedAPISvc) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) + }) - // Create dependency first (CRD) - crdPlural := genName("ins3") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ + It("CSV annotations overwrite pod template annotations defined in a StrategyDetailsDeployment", func() { + // Create a StrategyDetailsDeployment that defines the `foo1` and `foo2` annotations on a pod template + nginxName := genName("nginx-") + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, + Name: genName("dep-"), + Spec: newNginxDeployment(nginxName), }, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() + } + strategy.DeploymentSpecs[0].Spec.Template.Annotations = map[string]string{ + "foo1": "notBar1", + "foo2": "bar2", + } - // create "current" CSV - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(genName("nginx-")), + // Create a CSV that defines the `foo1` and `foo3` annotations + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - }, - } - - Expect(err).ShouldNot(HaveOccurred()) - - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + Annotations: map[string]string{ + "foo1": "bar1", + "foo3": "bar3", }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster3", + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, }, - }, - } + } - // don't need to clean up this CSV, it will be deleted by the upgrade process - _, err = createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) + // Make sure that the pods annotations are correct + annotations := dep.Spec.Template.Annotations + Expect(annotations["foo1"]).Should(Equal("bar1")) + Expect(annotations["foo2"]).Should(Equal("bar2")) + Expect(annotations["foo3"]).Should(Equal("bar3")) + }) - // Create "updated" CSV - strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep2"), - Spec: newNginxDeployment(genName("nginx-")), + It("Set labels for the Deployment created via the ClusterServiceVersion", func() { + // Create a StrategyDetailsDeployment that defines labels for Deployment inside + nginxName := genName("nginx-") + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: genName("dep-"), + Spec: newNginxDeployment(nginxName), + Label: k8slabels.Set{ + "application": "nginx", + "application.type": "proxy", + }, + }, }, - }, - } - - Expect(err).ShouldNot(HaveOccurred()) + } - csvNew := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv2"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: csv.Name, - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategyNew, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster3", + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, }, - }, - } - - cleanupNewCSV, err := createCSV(c, crc, csvNew, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupNewCSV() - - // Wait for updated CSV to succeed - fetchedCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Fetch cluster service version again to check for unnecessary control loops - sameCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - Expect(equality.Semantic.DeepEqual(fetchedCSV, sameCSV)).Should(BeTrue(), diff.ObjectDiff(fetchedCSV, sameCSV)) + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - // Should have created new deployment and deleted old - depNew, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depNew).ShouldNot(BeNil()) - err = waitForDeploymentToDelete(c, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) + // Make sure that the deployment labels are correct + labels := dep.GetLabels() + Expect(labels["olm.owner"]).Should(Equal(csv.GetName())) + Expect(labels["olm.owner.namespace"]).Should(Equal(ns.GetName())) + Expect(labels["application"]).Should(Equal("nginx")) + Expect(labels["application.type"]).Should(Equal("proxy")) + }) - // Should eventually GC the CSV - Eventually(func() bool { - return csvExists(crc, csv.Name) - }).Should(BeFalse()) - }) - It("update in place", func() { + It("update same deployment name", func() { - // Create dependency first (CRD) - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", + // Create dependency first (CRD) + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, }, }, }, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, + }, + Scope: apiextensionsv1.NamespaceScoped, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - - // Create "current" CSV - nginxName := genName("nginx-") - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(nginxName), - }, - }, - } - - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&crd)).To(Succeed()) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, + // Create "current" CSV + nginxName := genName("nginx-") + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep-"), + Spec: newNginxDeployment(nginxName), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster", + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster", + }, }, }, }, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, true) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Wait for current CSV to succeed - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) - - // Create "updated" CSV - strategyNew := strategy - strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers = []corev1.Container{ - { - Name: genName("nginx-"), - Image: *dummyImage, - Ports: []corev1.ContainerPort{ + // Create "updated" CSV + strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - ContainerPort: 80, + // Same name + Name: strategy.DeploymentSpecs[0].Name, + // Different spec + Spec: newNginxDeployment(nginxName), }, }, - ImagePullPolicy: corev1.PullIfNotPresent, - }, - } - - // Also set something outside the spec template - this should be ignored - var five int32 = 5 - strategyNew.DeploymentSpecs[0].Spec.Replicas = &five - - Expect(err).ShouldNot(HaveOccurred()) - - fetchedCSV.Spec.InstallStrategy.StrategySpec = strategyNew - - // Update CSV directly - _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Update(context.TODO(), fetchedCSV, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - // wait for deployment spec to be updated - Eventually(func() (string, error) { - fetched, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - if err != nil { - return "", err } - ctx.Ctx().Logf("waiting for deployment to update...") - return fetched.Spec.Template.Spec.Containers[0].Name, nil - }).Should(Equal(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name)) - // Wait for updated CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - - depUpdated, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depUpdated).ShouldNot(BeNil()) + csvNew := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: csv.Name, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategyNew, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster", + }, + }, + }, + }, + } + Expect(resourceManager.Create(&csvNew)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csvNew, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Deployment should have changed even though the CSV is otherwise the same - Expect(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name).Should(Equal(depUpdated.Spec.Template.Spec.Containers[0].Name)) - Expect(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Image).Should(Equal(depUpdated.Spec.Template.Spec.Containers[0].Image)) + // Should have updated existing deployment + depUpdated := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depUpdated) + }).Should(Succeed()) + Expect(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name).Should(Equal(depUpdated.Spec.Template.Spec.Containers[0].Name)) - // Field updated even though template spec didn't change, because it was part of a template spec change as well - Expect(*strategyNew.DeploymentSpecs[0].Spec.Replicas).Should(Equal(*depUpdated.Spec.Replicas)) - }) - It("update multiple version CRD", func() { + // Should eventually GC the CSV + Eventually(func() error { + return resourceManager.Get(&csv) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - // Create initial CRD which has 2 versions: v1alpha1 & v1alpha2 - crdPlural := genName("ins4") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", + // Fetch cluster service version again to check for unnecessary control loops + sameCsv := operatorsv1alpha1.ClusterServiceVersion{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csvNew.Name, &sameCsv) + }).Should(Succeed()) + Expect(equality.Semantic.DeepEqual(csvNew, sameCsv)).Should(BeTrue(), cmp.Diff(csvNew, sameCsv)) + }) + It("update different deployment name", func() { + // Create dependency first (CRD) + crdPlural := genName("ins2") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, }, }, }, - { - Name: "v1alpha2", - Served: true, - Storage: false, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, + Scope: apiextensionsv1.NamespaceScoped, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() + } + Expect(resourceManager.Create(&crd)).To(Succeed()) - // create initial deployment strategy - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep1-"), - Spec: newNginxDeployment(genName("nginx-")), + // create "current" CSV + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: genName("dep-"), + Spec: newNginxDeployment(genName("nginx-")), + }, }, - }, - } - - Expect(err).ShouldNot(HaveOccurred()) + } - // First CSV with owning CRD v1alpha1 - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster2", + }, + }, }, + }, + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) + + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) + + // Create "updated" CSV + strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep2"), + Spec: newNginxDeployment(genName("nginx-")), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + } + + csvNew := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv2"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: csv.Name, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster4", + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategyNew, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster2", + }, }, }, }, - }, - } + } + Expect(resourceManager.Create(&csvNew)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csvNew, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) + Eventually(func() error { + return resourceManager.Get(&csvNew) + }).Should(Succeed()) - // CSV will be deleted by the upgrade process later - _, err = createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) + // Fetch cluster service version again to check for unnecessary control loops + sameCsv := operatorsv1alpha1.ClusterServiceVersion{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csvNew.Name, &sameCsv) + }).Should(Succeed()) + Expect(resourceManager.WaitForConditions(&sameCsv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + Expect(equality.Semantic.DeepEqual(csvNew, sameCsv)).Should(BeTrue(), cmp.Diff(csvNew, sameCsv)) - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) + // Should have created new deployment and deleted old + depNew := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depNew) + }).Should(Succeed()) - // Create updated deployment strategy - strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep2-"), - Spec: newNginxDeployment(genName("nginx-")), - }, - }, - } + // Wait for old deployment to be deleted + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, &appsv1.Deployment{}) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - Expect(err).ShouldNot(HaveOccurred()) + // Should eventually GC the CSV + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csv.Name, &operatorsv1alpha1.ClusterServiceVersion{}) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) + }) - // Second CSV with owning CRD v1alpha1 and v1alpha2 - csvNew := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv2"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: csv.Name, - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + It("update multiple intermediates", func() { + // Create dependency first (CRD) + crdPlural := genName("ins3") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + Expect(resourceManager.Create(&crd)).To(Succeed()) + + // create "current" CSV + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep-"), + Spec: newNginxDeployment(genName("nginx-")), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategyNew, + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster4", + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, }, { - Name: crdName, - Version: "v1alpha2", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster4", + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster3", + }, }, }, }, - }, - } - - // Create newly updated CSV - _, err = createCSV(c, crc, csvNew, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - - // Wait for updated CSV to succeed - fetchedCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Fetch cluster service version again to check for unnecessary control loops - sameCSV, err := fetchCSV(crc, csvNew.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - Expect(equality.Semantic.DeepEqual(fetchedCSV, sameCSV)).Should(BeTrue(), diff.ObjectDiff(fetchedCSV, sameCSV)) - - // Should have created new deployment and deleted old one - depNew, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depNew).ShouldNot(BeNil()) - err = waitForDeploymentToDelete(c, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&csv)) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Create updated deployment strategy - strategyNew2 := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep3-"), - Spec: newNginxDeployment(genName("nginx-")), - }, - }, - } - Expect(err).ShouldNot(HaveOccurred()) + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - // Third CSV with owning CRD v1alpha2 - csvNew2 := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv3"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: csvNew.Name, - InstallModes: []operatorsv1alpha1.InstallMode{ + // Create "updated" CSV + strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep2"), + Spec: newNginxDeployment(genName("nginx-")), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategyNew2, + } + + csvNew := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv2"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: csv.Name, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha2", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster4", + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategyNew, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster3", + }, }, }, }, - }, - } + } + Expect(resourceManager.Create(&csvNew)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csvNew, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) + Eventually(func() error { + return resourceManager.Get(&csvNew) + }).Should(Succeed()) - // Create newly updated CSV - cleanupNewCSV, err := createCSV(c, crc, csvNew2, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupNewCSV() + // Fetch cluster service version again to check for unnecessary control loops + sameCsv := operatorsv1alpha1.ClusterServiceVersion{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csvNew.Name, &sameCsv) + }).Should(Succeed()) + Expect(resourceManager.WaitForConditions(&sameCsv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // Wait for updated CSV to succeed - fetchedCSV, err = fetchCSV(crc, csvNew2.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + Expect(equality.Semantic.DeepEqual(csvNew, sameCsv)).Should(BeTrue(), cmp.Diff(csvNew, sameCsv)) - // Fetch cluster service version again to check for unnecessary control loops - sameCSV, err = fetchCSV(crc, csvNew2.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - Expect(equality.Semantic.DeepEqual(fetchedCSV, sameCSV)).Should(BeTrue(), diff.ObjectDiff(fetchedCSV, sameCSV)) + // Should have created new deployment and deleted old + depNew := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depNew) + }).Should(Succeed()) - // Should have created new deployment and deleted old one - depNew, err = c.GetDeployment(testNamespace, strategyNew2.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depNew).ShouldNot(BeNil()) - err = waitForDeploymentToDelete(c, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) + // Wait for deployment to delete + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, &appsv1.Deployment{}) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - // Should clean up the CSV - Eventually(func() bool { - return csvExists(crc, csvNew.Name) - }).Should(BeFalse()) - }) - It("update modify deployment name", func() { + // Should eventually GC the CSV + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csv.Name, &operatorsv1alpha1.ClusterServiceVersion{}) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) + }) - // Create dependency first (CRD) - crdPlural := genName("ins2") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", + It("update in place", func() { + + // Create dependency first (CRD) + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, }, }, }, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, + }, + Scope: apiextensionsv1.NamespaceScoped, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() - - // create "current" CSV - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(genName("nginx-")), - }, - { - Name: "dep2-test", - Spec: newNginxDeployment("nginx2"), - }, - }, - } - - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&crd)).To(Succeed()) - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, + // Create "current" CSV + nginxName := genName("nginx-") + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep-"), + Spec: newNginxDeployment(nginxName), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster2", + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster", + }, }, }, }, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // Should have created deployments - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) - dep2, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[1].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep2).ShouldNot(BeNil()) + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - // Create "updated" CSV - strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + // Create "updated" CSV + strategyNew := strategy + strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers = []corev1.Container{ { - Name: genName("dep3-"), - Spec: newNginxDeployment(genName("nginx3-")), - }, - { - Name: "dep2-test", - Spec: newNginxDeployment("nginx2"), + Name: genName("nginx-"), + Image: *dummyImage, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 80, + }, + }, + ImagePullPolicy: corev1.PullIfNotPresent, }, - }, - } - - Expect(err).ShouldNot(HaveOccurred()) - - // Fetch the current csv - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + } - // Update csv with same strategy with different deployment's name - fetchedCSV.Spec.InstallStrategy.StrategySpec = strategyNew + fetchedCSV := operatorsv1alpha1.ClusterServiceVersion{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csv.Name, &fetchedCSV) + }).Should(Succeed()) - // Update the current csv with the new csv - _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Update(context.TODO(), fetchedCSV, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) + // Also set something outside the spec template - this should be ignored + var five int32 = 5 + strategyNew.DeploymentSpecs[0].Spec.Replicas = &five + fetchedCSV.Spec.InstallStrategy.StrategySpec = strategyNew - // Wait for new deployment to exist - err = waitForDeployment(c, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) + // Update CSV directly + Expect(resourceManager.Update(&fetchedCSV)).To(Succeed()) - // Wait for updated CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // wait for deployment spec to be updated + depUpdated := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depUpdated) + }).Should(Succeed()) - // Should have created new deployment and deleted old - depNew, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depNew).ShouldNot(BeNil()) + Expect(resourceManager.WaitForConditions(depUpdated, util.DeploymentCondition(func(deployment *appsv1.Deployment) error { + actualName := deployment.Spec.Template.Spec.Containers[0].Name + expectedName := strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name + if actualName != expectedName { + return fmt.Errorf("deployment contaner name differs from expected: %s, %s", actualName, expectedName) + } - // Make sure the unchanged deployment still exists - depNew2, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[1].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depNew2).ShouldNot(BeNil()) + actualImage := deployment.Spec.Template.Spec.Containers[0].Image + expectedImage := strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Image + if actualImage != expectedImage { + return fmt.Errorf("deployment contaner image differs from expected: %s, %s", actualImage, expectedImage) + } + return nil + }))).To(Succeed()) - err = waitForDeploymentToDelete(c, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("update deployment spec in an existing CSV for a hotfix", func() { + // Wait for updated CSV to succeed + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - c := newKubeClient() - crc := newCRClient() + // Field updated even though template spec didn't change, because it was part of a template spec change as well + Expect(*strategyNew.DeploymentSpecs[0].Spec.Replicas).Should(Equal(*depUpdated.Spec.Replicas)) + }) - // Create dependency first (CRD) - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", + It("update multiple version CRD", func() { + // Create initial CRD which has 2 versions: v1alpha1 & v1alpha2 + crdPlural := genName("ins4") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, + { + Name: "v1alpha2", + Served: true, + Storage: false, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, }, }, }, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, + }, + Scope: apiextensionsv1.NamespaceScoped, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - defer cleanupCRD() - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(&crd)).To(Succeed()) - // Create "current" CSV - nginxName := genName("nginx-") - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(nginxName), + // create initial deployment strategy + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: genName("dep1-"), + Spec: newNginxDeployment(genName("nginx-")), + }, }, - }, - } + } - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + // First CSV with owning CRD v1alpha1 + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster4", + }, + }, }, + }, + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) + + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) + + // Create updated deployment strategy + strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep2-"), + Spec: newNginxDeployment(genName("nginx-")), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + } + + // Second CSV with owning CRD v1alpha1 and v1alpha2 + csvNew := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv2"), + Namespace: ns.GetName(), }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: csv.Name, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, { - Name: crdName, - Version: "v1alpha1", - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster", + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategyNew, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster4", + }, + { + Name: crdName, + Version: "v1alpha2", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster4", + }, }, }, }, - }, - } + } + Expect(resourceManager.Create(&csvNew)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csvNew, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + // Fetch cluster service version again to check for unnecessary control loops + sameCSV := operatorsv1alpha1.ClusterServiceVersion{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csvNew.Name, &sameCSV) + }).Should(Succeed()) + Expect(equality.Semantic.DeepEqual(csvNew, sameCSV)).Should(BeTrue(), cmp.Diff(csvNew, sameCSV)) - // Wait for current CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Should have created new deployment and deleted old one + depNew := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depNew) + }).Should(Succeed()) - // Should have created deployment - dep, err := c.GetDeployment(testNamespace, strategy.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(dep).ShouldNot(BeNil()) + Eventually(func() error { + return resourceManager.Get(dep) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - // Create "updated" CSV - strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - // Same name - Name: strategy.DeploymentSpecs[0].Name, - // Different spec - Spec: newNginxDeployment(nginxName), + // Create updated deployment strategy + strategyNew2 := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: genName("dep3-"), + Spec: newNginxDeployment(genName("nginx-")), + }, }, - }, - } - - // Fetch the current csv - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + } - // Update csv with modified deployment spec - fetchedCSV.Spec.InstallStrategy.StrategySpec = strategyNew + // Third CSV with owning CRD v1alpha2 + csvNew2 := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv3"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: csvNew.Name, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategyNew2, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha2", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster4", + }, + }, + }, + }, + } + Expect(resourceManager.Create(&csvNew2)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csvNew2, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - Eventually(func() error { - // Update the current csv - _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Update(context.TODO(), fetchedCSV, metav1.UpdateOptions{}) - return err - }).Should(Succeed()) + // Fetch cluster service version again to check for unnecessary control loops + sameCSV = operatorsv1alpha1.ClusterServiceVersion{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), csvNew2.Name, &sameCSV) + }).Should(Succeed()) + Expect(equality.Semantic.DeepEqual(csvNew2, sameCSV)).Should(BeTrue(), cmp.Diff(csvNew2, sameCSV)) - // Wait for updated CSV to succeed - _, err = fetchCSV(crc, csv.Name, testNamespace, func(csv *operatorsv1alpha1.ClusterServiceVersion) bool { + // Should have created new deployment and deleted old one + depNew2 := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew2.DeploymentSpecs[0].Name, depNew2) + }).Should(Succeed()) - // Should have updated existing deployment - depUpdated, err := c.GetDeployment(testNamespace, strategyNew.DeploymentSpecs[0].Name) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depUpdated).ShouldNot(BeNil()) - // container name has been updated and differs from initial CSV spec and updated CSV spec - Expect(depUpdated.Spec.Template.Spec.Containers[0].Name).ShouldNot(Equal(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name)) + Eventually(func() error { + return resourceManager.Get(depNew) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - // Check for success - return csvSucceededChecker(csv) + // Should clean up the CSV + Eventually(func() error { + return resourceManager.Get(&csvNew) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) }) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("emits CSV requirement events", func() { + It("update modify deployment name", func() { + // Create dependency first (CRD) + crdPlural := genName("ins2") + crdName := crdPlural + ".cluster.com" - csv := &operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + Expect(resourceManager.Create(&crd)).To(Succeed()) + + // create "current" CSV + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Name: genName("dep-"), + Spec: newNginxDeployment(genName("nginx-")), }, { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: "dep2-test", + Spec: newNginxDeployment("nginx2"), }, }, - InstallStrategy: newNginxInstallStrategy(genName("dep-"), nil, nil), - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - // Require an API that we know won't exist under our domain - Required: []operatorsv1alpha1.APIServiceDescription{ + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, { - Group: "bad.packages.operators.coreos.com", - Version: "v1", - Kind: "PackageManifest", + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, }, }, - }, - }, - } - csv.SetNamespace(testNamespace) - csv.SetName(genName("csv-")) - - clientCtx := context.Background() - listOpts := metav1.ListOptions{ - FieldSelector: "involvedObject.kind=ClusterServiceVersion", - } - events, err := c.KubernetesInterface().CoreV1().Events(csv.GetNamespace()).List(clientCtx, listOpts) - Expect(err).ToNot(HaveOccurred()) - - // Watch latest events from test namespace for CSV - listOpts.ResourceVersion = events.ResourceVersion - w, err := c.KubernetesInterface().CoreV1().Events(testNamespace).Watch(context.Background(), listOpts) - Expect(err).ToNot(HaveOccurred()) - defer w.Stop() - - cleanupCSV, err := createCSV(c, crc, *csv, csv.GetNamespace(), false, false) - Expect(err).ToNot(HaveOccurred()) - defer cleanupCSV() - - By("emitting when requirements are not met") - nextReason := func() string { - if e := <-w.ResultChan(); e.Object != nil { - return e.Object.(*corev1.Event).Reason - } - return "" - } - Eventually(nextReason).Should(Equal("RequirementsNotMet")) - - // Patch the CSV to require an API that we know exists - Eventually(ctx.Ctx().SSAClient().Apply(clientCtx, csv, func(c *operatorsv1alpha1.ClusterServiceVersion) error { - c.Spec.APIServiceDefinitions.Required[0].Group = "packages.operators.coreos.com" - return nil - })).Should(Succeed()) - - By("emitting when requirements are met") - Eventually(nextReason).Should(Equal("AllRequirementsMet")) - }) - - // TODO: test behavior when replaces field doesn't point to existing CSV - It("status invalid CSV", func() { - - // Create CRD - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - cleanupCRD, err := createCRD(c, apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster2", }, }, }, }, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensions.NamespaceScoped, - }, - }) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCRD() + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - // create CSV - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(genName("nginx-")), - }, - }, - } + // Should have created deployments + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - Expect(err).ShouldNot(HaveOccurred()) + dep2 := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[1].Name, dep2) + }).Should(Succeed()) - csv := operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv"), - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, + // Create "updated" CSV + strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Name: genName("dep3-"), + Spec: newNginxDeployment(genName("nginx3-")), }, { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - }, - CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ - Owned: []operatorsv1alpha1.CRDDescription{ - { - Name: crdName, - Version: "apiextensions.k8s.io/v1alpha1", // purposely invalid, should be just v1alpha1 to match CRD - Kind: crdPlural, - DisplayName: crdName, - Description: "In the cluster2", - }, + Name: "dep2-test", + Spec: newNginxDeployment("nginx2"), }, }, - }, - } - - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, true, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - notServedStatus := operatorsv1alpha1.RequirementStatus{ - Group: "apiextensions.k8s.io", - Version: "v1", - Kind: "CustomResourceDefinition", - Name: crdName, - Status: operatorsv1alpha1.RequirementStatusReasonNotPresent, - Message: "CRD version not served", - } - csvCheckPhaseAndRequirementStatus := func(csv *operatorsv1alpha1.ClusterServiceVersion) bool { - if csv.Status.Phase == operatorsv1alpha1.CSVPhasePending { - for _, status := range csv.Status.RequirementStatus { - if status.Message == notServedStatus.Message { - return true - } - } } - return false - } + // Fetch the current csv + Eventually(func() error { + return resourceManager.Get(&csv) + }).Should(Succeed()) - fetchedCSV, err := fetchCSV(crc, csv.Name, testNamespace, csvCheckPhaseAndRequirementStatus) - Expect(err).ShouldNot(HaveOccurred()) + // Update csv with same strategy with different deployment's name + csv.Spec.InstallStrategy.StrategySpec = strategyNew - Expect(fetchedCSV.Status.RequirementStatus).Should(ContainElement(notServedStatus)) - }) + // Update the current csv with the new csv + Expect(resourceManager.Update(&csv)).To(Succeed()) - It("api service resource migrated if adoptable", func() { + // Wait for new deployment to exist + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, &appsv1.Deployment{}) + }).Should(Succeed()) - depName := genName("hat-server") - mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) - version := "v1alpha1" - mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") - mockKinds := []string{"fedora"} - depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) - apiServiceName := strings.Join([]string{version, mockGroup}, ".") + // Wait for updated CSV to succeed + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Create CSVs for the hat-server - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: depName, - Spec: depSpec, - }, - }, - } + // Should have created new deployment and deleted old + depNew := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depNew) + }).Should(Succeed()) - owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) - for i, kind := range mockKinds { - owned[i] = operatorsv1alpha1.APIServiceDescription{ - Name: apiServiceName, - Group: mockGroup, - Version: version, - Kind: kind, - DeploymentName: depName, - ContainerPort: int32(5443), - DisplayName: kind, - Description: fmt.Sprintf("A %s", kind), - } - } + // Make sure the unchanged deployment still exists + depNew2 := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[1].Name, depNew2) + }).Should(Succeed()) - csv := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, &appsv1.Deployment{}) + }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) + }) + + It("update deployment spec in an existing CSV for a hotfix", func() { + // Create dependency first (CRD) + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + Expect(resourceManager.Create(&crd)).To(Succeed()) + + // Create "current" CSV + nginxName := genName("nginx-") + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep-"), + Spec: newNginxDeployment(nginxName), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - }, - } - csv.SetName("csv-hat-1") - csv.SetNamespace(testNamespace) + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "v1alpha1", + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster", + }, + }, + }, + }, + } + Expect(resourceManager.Create(&csv)).To(Succeed()) - createLegacyAPIResources(&csv, owned[0]) + // Wait for current CSV to succeed + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - // Create the APIService CSV - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() + // Should have created deployment + dep := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategy.DeploymentSpecs[0].Name, dep) + }).Should(Succeed()) - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + // Create "updated" CSV + strategyNew := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + // Same name + Name: strategy.DeploymentSpecs[0].Name, + // Different spec + Spec: newNginxDeployment(nginxName), + }, + }, + } - checkLegacyAPIResources(owned[0], true) - }) + // Fetch the current csv + Eventually(func() error { + return resourceManager.Get(&csv) + }).Should(Succeed()) - It("API service resource not migrated if not adoptable", func() { + // Update csv with modified deployment spec + csv.Spec.InstallStrategy.StrategySpec = strategyNew - depName := genName("hat-server") - mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) - version := "v1alpha1" - mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") - mockKinds := []string{"fedora"} - depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) - apiServiceName := strings.Join([]string{version, mockGroup}, ".") + Eventually(func() error { + return resourceManager.Update(&csv) + }).Should(Succeed()) - // Create CSVs for the hat-server - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: depName, - Spec: depSpec, - }, - }, - } + // Wait for updated CSV to succeed + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) - for i, kind := range mockKinds { - owned[i] = operatorsv1alpha1.APIServiceDescription{ - Name: apiServiceName, - Group: mockGroup, - Version: version, - Kind: kind, - DeploymentName: depName, - ContainerPort: int32(5443), - DisplayName: kind, - Description: fmt.Sprintf("A %s", kind), - } - } + depUpdated := &appsv1.Deployment{} + Eventually(func() error { + return resourceManager.GetByKey(ns.GetName(), strategyNew.DeploymentSpecs[0].Name, depUpdated) + }).Should(Succeed()) + Expect(depUpdated.Spec.Template.Spec.Containers[0].Name).ShouldNot(Equal(strategyNew.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Name)) + }) - csv := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, + It("emits CSV requirement events", func() { + csv := &operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv-"), + Namespace: ns.GetName(), }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: newNginxInstallStrategy(genName("dep-"), nil, nil), + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + // Require an API that we know won't exist under our domain + Required: []operatorsv1alpha1.APIServiceDescription{ + { + Group: "bad.packages.operators.coreos.com", + Version: "v1", + Kind: "PackageManifest", + }, + }, + }, }, - }, - } - csv.SetName("csv-hat-1") - csv.SetNamespace(testNamespace) - - createLegacyAPIResources(nil, owned[0]) - - // Create the APIService CSV - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) + } + Expect(resourceManager.Create(csv)).To(Succeed()) - checkLegacyAPIResources(owned[0], false) + Expect(resourceManager.WaitForEventWithReason(csv, "RequirementsNotMet")).To(Succeed()) - // Cleanup the resources created for this test that were not cleaned up. - deleteLegacyAPIResources(owned[0]) - }) + // Patch the CSV to require an API that we know exists + Expect(resourceManager.Apply(csv, func(c *operatorsv1alpha1.ClusterServiceVersion) error { + c.Spec.APIServiceDefinitions.Required[0].Group = "packages.operators.coreos.com" + return nil + })).To(Succeed()) - It("multiple API services on a single pod", func() { - - // Create the deployment that both APIServices will be deployed to. - depName := genName("hat-server") - - // Define the expected mock APIService settings. - version := "v1alpha1" - apiService1Group := fmt.Sprintf("hats.%s.redhat.com", genName("")) - apiService1GroupVersion := strings.Join([]string{apiService1Group, version}, "/") - apiService1Kinds := []string{"fedora"} - apiService1Name := strings.Join([]string{version, apiService1Group}, ".") - - apiService2Group := fmt.Sprintf("hats.%s.redhat.com", genName("")) - apiService2GroupVersion := strings.Join([]string{apiService2Group, version}, "/") - apiService2Kinds := []string{"fez"} - apiService2Name := strings.Join([]string{version, apiService2Group}, ".") - - // Create the deployment spec with the two APIServices. - mockGroupVersionKinds := []mockGroupVersionKind{ - { - depName, - apiService1GroupVersion, - apiService1Kinds, - 5443, - }, - { - depName, - apiService2GroupVersion, - apiService2Kinds, - 5444, - }, - } - depSpec := newMockExtServerDeployment(depName, mockGroupVersionKinds) + By("emitting when requirements are met") + Expect(resourceManager.WaitForEventWithReason(csv, "AllRequirementsMet")).To(Succeed()) + }) - // Create the CSV. - strategy := operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: depName, - Spec: depSpec, + // TODO: test behavior when replaces field doesn't point to existing CSV + It("status invalid CSV", func() { + // Create CRD + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, }, - }, - } - - // Update the owned APIServices two include the two APIServices. - owned := []operatorsv1alpha1.APIServiceDescription{ - { - Name: apiService1Name, - Group: apiService1Group, - Version: version, - Kind: apiService1Kinds[0], - DeploymentName: depName, - ContainerPort: int32(5443), - DisplayName: apiService1Kinds[0], - Description: fmt.Sprintf("A %s", apiService1Kinds[0]), - }, - { - Name: apiService2Name, - Group: apiService2Group, - Version: version, - Kind: apiService2Kinds[0], - DeploymentName: depName, - ContainerPort: int32(5444), - DisplayName: apiService2Kinds[0], - Description: fmt.Sprintf("A %s", apiService2Kinds[0]), - }, - } - - csv := operatorsv1alpha1.ClusterServiceVersion{ - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, }, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + Expect(resourceManager.Create(&crd)).To(Succeed()) + + // create CSV + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: genName("dep-"), + Spec: newNginxDeployment(genName("nginx-")), }, }, - InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, + } + + csv := operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, }, - APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ - Owned: owned, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + Namespace: ns.GetName(), }, - }, - } - csv.SetName("csv-hat-1") - csv.SetNamespace(testNamespace) - - // Create the APIService CSV - cleanupCSV, err := createCSV(c, crc, csv, testNamespace, false, false) - Expect(err).ShouldNot(HaveOccurred()) - defer cleanupCSV() - - _, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker) - Expect(err).ShouldNot(HaveOccurred()) - - // Check that the APIService caBundles are equal - apiService1, err := c.GetAPIService(apiService1Name) - Expect(err).ShouldNot(HaveOccurred()) - - apiService2, err := c.GetAPIService(apiService2Name) - Expect(err).ShouldNot(HaveOccurred()) - - Expect(apiService2.Spec.CABundle).Should(Equal(apiService1.Spec.CABundle)) - }) -}) - -var _ = Describe("Disabling copied CSVs", func() { - var ( - ns corev1.Namespace - csv operatorsv1alpha1.ClusterServiceVersion - ) + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ + Owned: []operatorsv1alpha1.CRDDescription{ + { + Name: crdName, + Version: "apiextensions.k8s.io/v1alpha1", // purposely invalid, should be just v1alpha1 to match CRD + Kind: crdPlural, + DisplayName: crdName, + Description: "In the cluster2", + }, + }, + }, + }, + } + Expect(resourceManager.Create(&csv)).To(Succeed()) + + notServedStatus := &operatorsv1alpha1.RequirementStatus{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + Name: crdName, + Status: operatorsv1alpha1.RequirementStatusReasonNotPresent, + Message: "CRD version not served", + } - BeforeEach(func() { - nsname := genName("csv-toggle-test-") - og := operatorsv1.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-operatorgroup", nsname), - Namespace: nsname, - }, - } - ns = SetupGeneratedTestNamespaceWithOperatorGroup(nsname, og) + Expect(resourceManager.WaitForConditions( + &csv, + util.CSVPhaseCondition(operatorsv1alpha1.CSVPhasePending), + util.CSVHasRequirementStatus(notServedStatus), + )).To(Succeed()) + }) - csv = operatorsv1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: genName("csv-toggle-test-"), - Namespace: nsname, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - InstallStrategy: newNginxInstallStrategy(genName("csv-toggle-test-"), nil, nil), - InstallModes: []operatorsv1alpha1.InstallMode{ + It("api service resource migrated if adoptable", func() { + depName := genName("hat-server") + mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) + version := "v1alpha1" + mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") + mockKinds := []string{"fedora"} + depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) + apiServiceName := strings.Join([]string{version, mockGroup}, ".") + + // Create CSVs for the hat-server + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, + Name: depName, + Spec: depSpec, }, }, - }, - } - err := ctx.Ctx().Client().Create(context.Background(), &csv) - Expect(err).ShouldNot(HaveOccurred()) - }) - AfterEach(func() { - Eventually(func() error { - err := ctx.Ctx().Client().Delete(context.Background(), &csv) - if err != nil && k8serrors.IsNotFound(err) { - return err } - return nil - }).Should(Succeed()) - TeardownNamespace(ns.GetName()) - Eventually(func() error { - var namespace corev1.Namespace - return ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&ns), &namespace) - }).Should(WithTransform(k8serrors.IsNotFound, BeTrue())) - }) - - When("an operator is installed in AllNamespace mode", func() { - // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2643 - It("[FLAKE] should have Copied CSVs in all other namespaces", func() { - Eventually(func() error { - requirement, err := k8slabels.NewRequirement(operatorsv1alpha1.CopiedLabelKey, selection.Equals, []string{csv.GetNamespace()}) - if err != nil { - return err - } - - var copiedCSVs operatorsv1alpha1.ClusterServiceVersionList - err = ctx.Ctx().Client().List(context.TODO(), &copiedCSVs, &client.ListOptions{ - LabelSelector: k8slabels.NewSelector().Add(*requirement), - }) - if err != nil { - return err + owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) + for i, kind := range mockKinds { + owned[i] = operatorsv1alpha1.APIServiceDescription{ + Name: apiServiceName, + Group: mockGroup, + Version: version, + Kind: kind, + DeploymentName: depName, + ContainerPort: int32(5443), + DisplayName: kind, + Description: fmt.Sprintf("A %s", kind), } + } - var namespaces corev1.NamespaceList - if err := ctx.Ctx().Client().List(context.TODO(), &namespaces, &client.ListOptions{}); err != nil { - return err - } + csv := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-1", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, + }, + }, + } + createLegacyAPIResources(resourceManager, ns.GetName(), &csv, owned[0]) - if len(namespaces.Items)-1 != len(copiedCSVs.Items) { - return fmt.Errorf("%d copied CSVs found, expected %d", len(copiedCSVs.Items), len(namespaces.Items)-1) - } + // Create the APIService CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))) - return nil - }).Should(Succeed()) + checkLegacyAPIResources(resourceManager, ns.GetName(), owned[0], true) }) - }) - When("Copied CSVs are disabled", func() { - BeforeEach(func() { - Eventually(func() error { - var olmConfig operatorsv1.OLMConfig - if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { - ctx.Ctx().Logf("Error getting olmConfig %v", err) - return err - } + It("API service resource not migrated if not adoptable", func() { - // Exit early if copied CSVs are disabled. - if !olmConfig.CopiedCSVsAreEnabled() { - return nil - } + depName := genName("hat-server") + mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName("")) + version := "v1alpha1" + mockGroupVersion := strings.Join([]string{mockGroup, version}, "/") + mockKinds := []string{"fedora"} + depSpec := newMockExtServerDeployment(depName, []mockGroupVersionKind{{depName, mockGroupVersion, mockKinds, 5443}}) + apiServiceName := strings.Join([]string{version, mockGroup}, ".") - olmConfig.Spec = operatorsv1.OLMConfigSpec{ - Features: &operatorsv1.Features{ - DisableCopiedCSVs: getPointer(true), + // Create CSVs for the hat-server + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: depName, + Spec: depSpec, }, - } - - if err := ctx.Ctx().Client().Update(context.TODO(), &olmConfig); err != nil { - ctx.Ctx().Logf("Error setting olmConfig %v", err) - return err - } - - return nil - }).Should(Succeed()) - }) - - It("should not have any copied CSVs", func() { - Eventually(func() error { - requirement, err := k8slabels.NewRequirement(operatorsv1alpha1.CopiedLabelKey, selection.Equals, []string{csv.GetNamespace()}) - if err != nil { - return err - } - - var copiedCSVs operatorsv1alpha1.ClusterServiceVersionList - err = ctx.Ctx().Client().List(context.TODO(), &copiedCSVs, &client.ListOptions{ - LabelSelector: k8slabels.NewSelector().Add(*requirement), - }) - if err != nil { - return err - } + }, + } - if numCSVs := len(copiedCSVs.Items); numCSVs != 0 { - return fmt.Errorf("Found %d copied CSVs, should be 0", numCSVs) + owned := make([]operatorsv1alpha1.APIServiceDescription, len(mockKinds)) + for i, kind := range mockKinds { + owned[i] = operatorsv1alpha1.APIServiceDescription{ + Name: apiServiceName, + Group: mockGroup, + Version: version, + Kind: kind, + DeploymentName: depName, + ContainerPort: int32(5443), + DisplayName: kind, + Description: fmt.Sprintf("A %s", kind), } - return nil - }).Should(Succeed()) - }) + } - // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2634 - It("[FLAKE] should be reflected in the olmConfig.Status.Condition array that the expected number of copied CSVs exist", func() { - Eventually(func() error { - var olmConfig operatorsv1.OLMConfig - if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { - return err - } + csv := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-1", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, + }, + }, + } - foundCondition := meta.FindStatusCondition(olmConfig.Status.Conditions, operatorsv1.DisabledCopiedCSVsConditionType) - if foundCondition == nil { - return fmt.Errorf("%s condition not found", operatorsv1.DisabledCopiedCSVsConditionType) - } + createLegacyAPIResources(resourceManager, ns.GetName(), nil, owned[0]) - expectedCondition := metav1.Condition{ - Reason: "NoCopiedCSVsFound", - Message: "Copied CSVs are disabled and none were found for operators installed in AllNamespace mode", - Status: metav1.ConditionTrue, - } + // Create the APIService CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - if foundCondition.Reason != expectedCondition.Reason || - foundCondition.Message != expectedCondition.Message || - foundCondition.Status != expectedCondition.Status { - return fmt.Errorf("condition does not have expected reason, message, and status. Expected %v, got %v", expectedCondition, foundCondition) - } + checkLegacyAPIResources(resourceManager, ns.GetName(), owned[0], false) - return nil - }).Should(Succeed()) + // Cleanup the resources created for this test that were not cleaned up. + deleteLegacyAPIResources(ns.GetName(), owned[0]) }) - }) - - When("Copied CSVs are toggled back on", func() { - BeforeEach(func() { - Eventually(func() error { - var olmConfig operatorsv1.OLMConfig - if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { - return err - } - - // Exit early if copied CSVs are enabled. - if olmConfig.CopiedCSVsAreEnabled() { - return nil - } - olmConfig.Spec = operatorsv1.OLMConfigSpec{ - Features: &operatorsv1.Features{ - DisableCopiedCSVs: getPointer(false), - }, - } - - if err := ctx.Ctx().Client().Update(context.TODO(), &olmConfig); err != nil { - return err - } + It("multiple API services on a single pod", func() { + // Create the deployment that both APIServices will be deployed to. + depName := genName("hat-server") - return nil - }).Should(Succeed()) - }) + // Define the expected mock APIService settings. + version := "v1alpha1" + apiService1Group := fmt.Sprintf("hats.%s.redhat.com", genName("")) + apiService1GroupVersion := strings.Join([]string{apiService1Group, version}, "/") + apiService1Kinds := []string{"fedora"} + apiService1Name := strings.Join([]string{version, apiService1Group}, ".") - // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2634 - It("[FLAKE] should have copied CSVs in all other Namespaces", func() { - Eventually(func() error { - // find copied csvs... - requirement, err := k8slabels.NewRequirement(operatorsv1alpha1.CopiedLabelKey, selection.Equals, []string{csv.GetNamespace()}) - if err != nil { - return err - } + apiService2Group := fmt.Sprintf("hats.%s.redhat.com", genName("")) + apiService2GroupVersion := strings.Join([]string{apiService2Group, version}, "/") + apiService2Kinds := []string{"fez"} + apiService2Name := strings.Join([]string{version, apiService2Group}, ".") - var copiedCSVs operatorsv1alpha1.ClusterServiceVersionList - err = ctx.Ctx().Client().List(context.TODO(), &copiedCSVs, &client.ListOptions{ - LabelSelector: k8slabels.NewSelector().Add(*requirement), - }) - if err != nil { - return err - } + // Create the deployment spec with the two APIServices. + mockGroupVersionKinds := []mockGroupVersionKind{ + { + depName, + apiService1GroupVersion, + apiService1Kinds, + 5443, + }, + { + depName, + apiService2GroupVersion, + apiService2Kinds, + 5444, + }, + } + depSpec := newMockExtServerDeployment(depName, mockGroupVersionKinds) - var namespaces corev1.NamespaceList - if err := ctx.Ctx().Client().List(context.TODO(), &namespaces, &client.ListOptions{FieldSelector: fields.SelectorFromSet(map[string]string{"status.phase": "Active"})}); err != nil { - return err - } + // Create the CSV. + strategy := operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: depName, + Spec: depSpec, + }, + }, + } - if len(namespaces.Items)-1 != len(copiedCSVs.Items) { - return fmt.Errorf("%d copied CSVs found, expected %d", len(copiedCSVs.Items), len(namespaces.Items)-1) - } + // Update the owned APIServices two include the two APIServices. + owned := []operatorsv1alpha1.APIServiceDescription{ + { + Name: apiService1Name, + Group: apiService1Group, + Version: version, + Kind: apiService1Kinds[0], + DeploymentName: depName, + ContainerPort: int32(5443), + DisplayName: apiService1Kinds[0], + Description: fmt.Sprintf("A %s", apiService1Kinds[0]), + }, + { + Name: apiService2Name, + Group: apiService2Group, + Version: version, + Kind: apiService2Kinds[0], + DeploymentName: depName, + ContainerPort: int32(5444), + DisplayName: apiService2Kinds[0], + Description: fmt.Sprintf("A %s", apiService2Kinds[0]), + }, + } - return nil - }).Should(Succeed()) - }) + csv := operatorsv1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-hat-1", + Namespace: ns.GetName(), + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + }, + APIServiceDefinitions: operatorsv1alpha1.APIServiceDefinitions{ + Owned: owned, + }, + }, + } - // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2641 - It("[FLAKE] should be reflected in the olmConfig.Status.Condition array that the expected number of copied CSVs exist", func() { - Eventually(func() error { - var olmConfig operatorsv1.OLMConfig - if err := ctx.Ctx().Client().Get(context.TODO(), apitypes.NamespacedName{Name: "cluster"}, &olmConfig); err != nil { - return err - } - foundCondition := meta.FindStatusCondition(olmConfig.Status.Conditions, operatorsv1.DisabledCopiedCSVsConditionType) - if foundCondition == nil { - return fmt.Errorf("%s condition not found", operatorsv1.DisabledCopiedCSVsConditionType) - } + // Create the APIService CSV + Expect(resourceManager.Create(&csv)).To(Succeed()) + Expect(resourceManager.WaitForConditions(&csv, util.CSVPhaseCondition(operatorsv1alpha1.CSVPhaseSucceeded))).To(Succeed()) - expectedCondition := metav1.Condition{ - Reason: "CopiedCSVsEnabled", - Message: "Copied CSVs are enabled and present across the cluster", - Status: metav1.ConditionFalse, - } + // Check that the APIService caBundles are equal + var apiService1 = apiregistrationv1.APIService{} + Expect(resourceManager.GetByKey("", apiService1Name, &apiService1)).To(Succeed()) - if foundCondition.Reason != expectedCondition.Reason || - foundCondition.Message != expectedCondition.Message || - foundCondition.Status != expectedCondition.Status { - return fmt.Errorf("condition does not have expected reason, message, and status. Expected %v, got %v", expectedCondition, foundCondition) - } + var apiService2 = apiregistrationv1.APIService{} + Expect(resourceManager.GetByKey("", apiService2Name, &apiService2)).To(Succeed()) - return nil - }).Should(Succeed()) + Expect(apiService2.Spec.CABundle).Should(Equal(apiService1.Spec.CABundle)) }) }) }) @@ -4459,22 +3867,6 @@ var singleInstance = int32(1) var immediateDeleteGracePeriod int64 = 0 -func findLastEvent(events *corev1.EventList) (event corev1.Event) { - var latestTime metav1.Time - var latestInd int - for i, item := range events.Items { - if i != 0 { - if latestTime.Before(&item.LastTimestamp) { - latestTime = item.LastTimestamp - latestInd = i - } - } else { - latestTime = item.LastTimestamp - } - } - return events.Items[latestInd] -} - func buildCSVCleanupFunc(c operatorclient.ClientInterface, crc versioned.Interface, csv operatorsv1alpha1.ClusterServiceVersion, namespace string, deleteCRDs, deleteAPIServices bool) cleanupFunc { return func() { err := crc.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(context.TODO(), csv.GetName(), metav1.DeleteOptions{}) @@ -4690,7 +4082,6 @@ func buildCSVReasonChecker(reasons ...operatorsv1alpha1.ConditionReason) csvCond } } -var csvPendingChecker = buildCSVConditionChecker(operatorsv1alpha1.CSVPhasePending) var csvSucceededChecker = buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded) var csvReplacingChecker = buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseReplacing, operatorsv1alpha1.CSVPhaseDeleting) var csvFailedChecker = buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseFailed) @@ -4738,25 +4129,12 @@ func awaitCSV(c versioned.Interface, namespace, name string, checker csvConditio return fetched, err } -func waitForDeployment(c operatorclient.ClientInterface, name string) error { - return wait.Poll(pollInterval, pollDuration, func() (bool, error) { - _, err := c.GetDeployment(testNamespace, name) - if err != nil { - if k8serrors.IsNotFound(err) { - return false, nil - } - return false, err - } - return true, nil - }) -} - -func waitForDeploymentToDelete(c operatorclient.ClientInterface, name string) error { +func waitForDeploymentToDelete(namespace string, c operatorclient.ClientInterface, name string) error { var err error Eventually(func() (bool, error) { ctx.Ctx().Logf("waiting for deployment %s to delete", name) - _, err := c.GetDeployment(testNamespace, name) + _, err := c.GetDeployment(namespace, name) if k8serrors.IsNotFound(err) { ctx.Ctx().Logf("deleted %s", name) return true, nil @@ -4770,101 +4148,55 @@ func waitForDeploymentToDelete(c operatorclient.ClientInterface, name string) er return err } -func csvExists(c versioned.Interface, name string) bool { - fetched, err := c.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Get(context.TODO(), name, metav1.GetOptions{}) - if k8serrors.IsNotFound(err) { - return false - } - ctx.Ctx().Logf("%s (%s): %s", fetched.Status.Phase, fetched.Status.Reason, fetched.Status.Message) - if err != nil { - return true - } - return true -} - -func deleteLegacyAPIResources(desc operatorsv1alpha1.APIServiceDescription) { - c := newKubeClient() - - apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group) - - err := c.DeleteService(testNamespace, strings.Replace(apiServiceName, ".", "-", -1), &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - err = c.DeleteSecret(testNamespace, apiServiceName+"-cert", &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - err = c.DeleteRole(testNamespace, apiServiceName+"-cert", &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - err = c.DeleteRoleBinding(testNamespace, apiServiceName+"-cert", &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - err = c.DeleteClusterRoleBinding(apiServiceName+"-system:auth-delegator", &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - err = c.DeleteRoleBinding("kube-system", apiServiceName+"-auth-reader", &metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) -} - -func createLegacyAPIResources(csv *operatorsv1alpha1.ClusterServiceVersion, desc operatorsv1alpha1.APIServiceDescription) { - c := newKubeClient() - +func createLegacyAPIResources(resourceManager util.K8sResourceManager, namespace string, csv *operatorsv1alpha1.ClusterServiceVersion, desc operatorsv1alpha1.APIServiceDescription) { apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group) // Attempt to create the legacy service service := corev1.Service{} service.SetName(strings.Replace(apiServiceName, ".", "-", -1)) - service.SetNamespace(testNamespace) + service.SetNamespace(namespace) if csv != nil { err := ownerutil.AddOwnerLabels(&service, csv) Expect(err).ShouldNot(HaveOccurred()) } service.Spec.Ports = []corev1.ServicePort{{Port: 433, TargetPort: intstr.FromInt(443)}} - _, err := c.CreateService(&service) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&service)).To(Succeed()) // Attempt to create the legacy secret secret := corev1.Secret{} secret.SetName(apiServiceName + "-cert") - secret.SetNamespace(testNamespace) + secret.SetNamespace(namespace) if csv != nil { - err = ownerutil.AddOwnerLabels(&secret, csv) - Expect(err).ShouldNot(HaveOccurred()) - } - - _, err = c.CreateSecret(&secret) - if err != nil && !k8serrors.IsAlreadyExists(err) { + err := ownerutil.AddOwnerLabels(&secret, csv) Expect(err).ShouldNot(HaveOccurred()) } + Expect(resourceManager.Create(&secret)).To(Succeed()) // Attempt to create the legacy secret role role := rbacv1.Role{} role.SetName(apiServiceName + "-cert") - role.SetNamespace(testNamespace) + role.SetNamespace(namespace) if csv != nil { - err = ownerutil.AddOwnerLabels(&role, csv) + err := ownerutil.AddOwnerLabels(&role, csv) Expect(err).ShouldNot(HaveOccurred()) } - _, err = c.CreateRole(&role) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&role)).To(Succeed()) // Attempt to create the legacy secret role binding roleBinding := rbacv1.RoleBinding{} roleBinding.SetName(apiServiceName + "-cert") - roleBinding.SetNamespace(testNamespace) + roleBinding.SetNamespace(namespace) roleBinding.RoleRef = rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "Role", Name: role.GetName(), } if csv != nil { - err = ownerutil.AddOwnerLabels(&roleBinding, csv) + err := ownerutil.AddOwnerLabels(&roleBinding, csv) Expect(err).ShouldNot(HaveOccurred()) } - - _, err = c.CreateRoleBinding(&roleBinding) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&roleBinding)).To(Succeed()) // Attempt to create the legacy authDelegatorClusterRoleBinding clusterRoleBinding := rbacv1.ClusterRoleBinding{} @@ -4875,11 +4207,10 @@ func createLegacyAPIResources(csv *operatorsv1alpha1.ClusterServiceVersion, desc Name: "system:auth-delegator", } if csv != nil { - err = ownerutil.AddOwnerLabels(&clusterRoleBinding, csv) + err := ownerutil.AddOwnerLabels(&clusterRoleBinding, csv) Expect(err).ShouldNot(HaveOccurred()) } - _, err = c.CreateClusterRoleBinding(&clusterRoleBinding) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&clusterRoleBinding)).To(Succeed()) // Attempt to create the legacy authReadingRoleBinding roleBinding.SetName(apiServiceName + "-auth-reader") @@ -4889,35 +4220,69 @@ func createLegacyAPIResources(csv *operatorsv1alpha1.ClusterServiceVersion, desc Kind: "Role", Name: "extension-apiserver-authentication-reader", } - _, err = c.CreateRoleBinding(&roleBinding) - Expect(err).ShouldNot(HaveOccurred()) + Expect(resourceManager.Create(&roleBinding)).To(Succeed()) +} + +func csvExists(namespace string, c versioned.Interface, name string) bool { + fetched, err := c.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if k8serrors.IsNotFound(err) { + return false + } + ctx.Ctx().Logf("%s (%s): %s", fetched.Status.Phase, fetched.Status.Reason, fetched.Status.Message) + if err != nil { + return true + } + return true } -func checkLegacyAPIResources(desc operatorsv1alpha1.APIServiceDescription, expectedIsNotFound bool) { +func deleteLegacyAPIResources(namespace string, desc operatorsv1alpha1.APIServiceDescription) { c := newKubeClient() + + apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group) + + err := c.DeleteService(namespace, strings.Replace(apiServiceName, ".", "-", -1), &metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + err = c.DeleteSecret(namespace, apiServiceName+"-cert", &metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + err = c.DeleteRole(namespace, apiServiceName+"-cert", &metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + err = c.DeleteRoleBinding(namespace, apiServiceName+"-cert", &metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + err = c.DeleteClusterRoleBinding(apiServiceName+"-system:auth-delegator", &metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + err = c.DeleteRoleBinding("kube-system", apiServiceName+"-auth-reader", &metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) +} + +func checkLegacyAPIResources(resourceManager util.K8sResourceManager, namespace string, desc operatorsv1alpha1.APIServiceDescription, expectedIsNotFound bool) { apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group) // Attempt to create the legacy service - _, err := c.GetService(testNamespace, strings.Replace(apiServiceName, ".", "-", -1)) + err := resourceManager.GetByKey(namespace, strings.Replace(apiServiceName, ".", "-", -1), &corev1.Service{}) Expect(k8serrors.IsNotFound(err)).Should(Equal(expectedIsNotFound)) // Attempt to create the legacy secret - _, err = c.GetSecret(testNamespace, apiServiceName+"-cert") + err = resourceManager.GetByKey(namespace, apiServiceName+"-cert", &corev1.Secret{}) Expect(k8serrors.IsNotFound(err)).Should(Equal(expectedIsNotFound)) // Attempt to create the legacy secret role - _, err = c.GetRole(testNamespace, apiServiceName+"-cert") + err = resourceManager.GetByKey(namespace, apiServiceName+"-cert", &rbacv1.Role{}) Expect(k8serrors.IsNotFound(err)).Should(Equal(expectedIsNotFound)) // Attempt to create the legacy secret role binding - _, err = c.GetRoleBinding(testNamespace, apiServiceName+"-cert") + err = resourceManager.GetByKey(namespace, apiServiceName+"-cert", &rbacv1.RoleBinding{}) Expect(k8serrors.IsNotFound(err)).Should(Equal(expectedIsNotFound)) // Attempt to create the legacy authDelegatorClusterRoleBinding - _, err = c.GetClusterRoleBinding(apiServiceName + "-system:auth-delegator") + err = resourceManager.GetByKey("", apiServiceName+"-system:auth-delegator", &rbacv1.ClusterRoleBinding{}) Expect(k8serrors.IsNotFound(err)).Should(Equal(expectedIsNotFound)) // Attempt to create the legacy authReadingRoleBinding - _, err = c.GetRoleBinding("kube-system", apiServiceName+"-auth-reader") + err = resourceManager.GetByKey("kube-system", apiServiceName+"-auth-reader", &rbacv1.RoleBinding{}) Expect(k8serrors.IsNotFound(err)).Should(Equal(expectedIsNotFound)) } diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 8954dacf0c3..b38133dd652 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -1084,7 +1084,7 @@ var _ = Describe("Operator Group", func() { require.NoError(GinkgoT(), awaitAnnotations(GinkgoT(), q, map[string]string{v1.OperatorGroupProvidedAPIsAnnotationKey: ""})) // Ensure csvA's deployment is deleted - require.NoError(GinkgoT(), waitForDeploymentToDelete(c, pkgAStable)) + require.NoError(GinkgoT(), waitForDeploymentToDelete(testNamespace, c, pkgAStable)) // Await csvB's success _, err = awaitCSV(crc, nsB, csvB.GetName(), csvSucceededChecker) diff --git a/test/e2e/subscription_e2e_test.go b/test/e2e/subscription_e2e_test.go index 4b63fc1e6e4..7e89acd374b 100644 --- a/test/e2e/subscription_e2e_test.go +++ b/test/e2e/subscription_e2e_test.go @@ -586,11 +586,11 @@ var _ = Describe("Subscription", func() { // Should eventually GC the CSVs Eventually(func() bool { - return csvExists(crc, csvA.Name) + return csvExists(testNamespace, crc, csvA.Name) }).Should(BeFalse()) Eventually(func() bool { - return csvExists(crc, csvB.Name) + return csvExists(testNamespace, crc, csvB.Name) }).Should(BeFalse()) // TODO: check installplans, subscription status, etc diff --git a/test/e2e/util.go b/test/e2e/util.go index 79a7441b717..2db992c56f0 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -959,6 +959,12 @@ func HavePhase(goal operatorsv1alpha1.InstallPlanPhase) gtypes.GomegaMatcher { }, Equal(goal)) } +func CSVHasPhase(goal operatorsv1alpha1.ClusterServiceVersionPhase) gtypes.GomegaMatcher { + return WithTransform(func(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.ClusterServiceVersionPhase { + return csv.Status.Phase + }, Equal(goal)) +} + func HaveMessage(goal string) gtypes.GomegaMatcher { return WithTransform(func(plan *operatorsv1alpha1.InstallPlan) string { return plan.Status.Message diff --git a/test/e2e/util/resource_manager.go b/test/e2e/util/resource_manager.go index 07db36f34b6..04177ce8adf 100644 --- a/test/e2e/util/resource_manager.go +++ b/test/e2e/util/resource_manager.go @@ -3,6 +3,7 @@ package util import ( "context" "fmt" + . "github.com/onsi/ginkgo" "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -24,7 +25,7 @@ type K8sResourceManager interface { WaitForConditions(obj k8scontrollerclient.Object, conditions ...ObjectCondition) error List(list k8scontrollerclient.ObjectList, options ...k8scontrollerclient.ListOption) error WaitForEventWithReason(involvedObj k8scontrollerclient.Object, reason string) error - Reset() error + Reset(namespacesToDump ...string) error } func NewK8sResourceManager(ctx *ctx.TestContext) K8sResourceManager { @@ -100,6 +101,7 @@ func (m *k8sResourceManager) Create(obj k8scontrollerclient.Object, opts ...Crea } func (m *k8sResourceManager) SetupTestNamespace(name string, options ...TestNamespaceOption) (*corev1.Namespace, error) { + // TODO: should the test namespace be a member of the resource manager? namespace := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -118,7 +120,12 @@ func (m *k8sResourceManager) SetupTestNamespace(name string, options ...TestName return namespace, nil } -func (m *k8sResourceManager) Reset() error { +func (m *k8sResourceManager) Reset(namespacesToDump ...string) error { + // TODO: make this an option + for _, ns := range namespacesToDump { + m.dumpNamespace(ns) + } + for { obj, ok := m.createdResources.DequeueTail() @@ -294,3 +301,16 @@ func (m *k8sResourceManager) WaitForEventWithReason(involvedObj k8scontrollercli }, ) } + +func (m *k8sResourceManager) dumpNamespace(ns string) { + // TODO: this needs to be refactored. It is a duplicates code from e2e/util.go:TeardownNamespace + log := ctx.Ctx().Logf + + currentTest := CurrentGinkgoTestDescription() + if currentTest.Failed { + log("collecting the %s namespace artifacts as the '%s' test case failed", ns, currentTest.TestText) + if err := ctx.Ctx().DumpNamespaceArtifacts(ns); err != nil { + log("failed to collect namespace artifacts: %v", err) + } + } +} diff --git a/test/e2e/webhook_e2e_test.go b/test/e2e/webhook_e2e_test.go index 125dd1a79aa..e6526954aa7 100644 --- a/test/e2e/webhook_e2e_test.go +++ b/test/e2e/webhook_e2e_test.go @@ -419,7 +419,7 @@ var _ = Describe("CSVs with a Webhook", func() { // Make sure old resources are cleaned up. Eventually(func() bool { - return csvExists(crc, csv.Spec.Replaces) + return csvExists(testNamespace, crc, csv.Spec.Replaces) }).Should(BeFalse()) // Wait until previous webhook is cleaned up