github.com/operator-framework/operator-lifecycle-manager@v0.30.0/test/e2e/installplan_e2e_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/blang/semver/v4"
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  	. "github.com/onsi/gomega/gstruct"
    18  	"github.com/stretchr/testify/require"
    19  	appsv1 "k8s.io/api/apps/v1"
    20  	authorizationv1 "k8s.io/api/authorization/v1"
    21  	corev1 "k8s.io/api/core/v1"
    22  	rbacv1 "k8s.io/api/rbac/v1"
    23  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    24  	"k8s.io/apimachinery/pkg/api/equality"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
    30  	"k8s.io/apimachinery/pkg/util/diff"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	"k8s.io/client-go/discovery"
    33  	"k8s.io/client-go/util/retry"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  
    36  	opver "github.com/operator-framework/api/pkg/lib/version"
    37  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    38  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    39  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    40  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog"
    41  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
    42  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/apis/rbac"
    43  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    44  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    45  	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
    46  	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/util"
    47  )
    48  
    49  const (
    50  	deprecatedCRDDir = "deprecated-crd"
    51  )
    52  
    53  var _ = Describe("Install Plan", func() {
    54  	var (
    55  		c                  operatorclient.ClientInterface
    56  		crc                versioned.Interface
    57  		generatedNamespace corev1.Namespace
    58  	)
    59  
    60  	BeforeEach(func() {
    61  		namespaceName := genName("install-plan-e2e-")
    62  		og := operatorsv1.OperatorGroup{
    63  			ObjectMeta: metav1.ObjectMeta{
    64  				Name:      fmt.Sprintf("%s-operatorgroup", namespaceName),
    65  				Namespace: namespaceName,
    66  			},
    67  		}
    68  		generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(namespaceName, og)
    69  		c = ctx.Ctx().KubeClient()
    70  		crc = ctx.Ctx().OperatorClient()
    71  	})
    72  
    73  	AfterEach(func() {
    74  		TeardownNamespace(generatedNamespace.GetName())
    75  	})
    76  
    77  	When("an InstallPlan step contains a deprecated resource version", func() {
    78  		var (
    79  			csv        operatorsv1alpha1.ClusterServiceVersion
    80  			plan       operatorsv1alpha1.InstallPlan
    81  			deprecated client.Object
    82  			manifest   string
    83  			counter    float64
    84  		)
    85  
    86  		BeforeEach(func() {
    87  			dc, err := discovery.NewDiscoveryClientForConfig(ctx.Ctx().RESTConfig())
    88  			Expect(err).ToNot(HaveOccurred())
    89  
    90  			v, err := dc.ServerVersion()
    91  			Expect(err).ToNot(HaveOccurred())
    92  
    93  			if minor, err := strconv.Atoi(v.Minor); err == nil && minor < 16 {
    94  				Skip("test is dependent on CRD v1 introduced at 1.16")
    95  			}
    96  		})
    97  
    98  		BeforeEach(func() {
    99  			counter = 0
   100  			for _, metric := range getMetricsFromPod(ctx.Ctx().KubeClient(), getPodWithLabel(ctx.Ctx().KubeClient(), "app=catalog-operator")) {
   101  				if metric.Family == "installplan_warnings_total" {
   102  					counter = metric.Value
   103  				}
   104  			}
   105  			deprecatedCRD, err := util.DecodeFile(filepath.Join(testdataDir, deprecatedCRDDir, "deprecated.crd.yaml"), &apiextensionsv1.CustomResourceDefinition{})
   106  			Expect(err).NotTo(HaveOccurred())
   107  
   108  			Expect(ctx.Ctx().Client().Create(context.Background(), deprecatedCRD)).To(Succeed())
   109  
   110  			csv = newCSV(genName("test-csv-"), generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil)
   111  			Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed())
   112  
   113  			deprecated, err = util.DecodeFile(filepath.Join(testdataDir, deprecatedCRDDir, "deprecated.cr.yaml"), &unstructured.Unstructured{}, util.WithNamespace(generatedNamespace.GetName()))
   114  			Expect(err).NotTo(HaveOccurred())
   115  
   116  			scheme := runtime.NewScheme()
   117  			{
   118  				var b bytes.Buffer
   119  				Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(deprecated, &b)).To(Succeed())
   120  				manifest = b.String()
   121  			}
   122  
   123  			plan = operatorsv1alpha1.InstallPlan{
   124  				ObjectMeta: metav1.ObjectMeta{
   125  					Namespace: generatedNamespace.GetName(),
   126  					Name:      genName("test-plan-"),
   127  				},
   128  				Spec: operatorsv1alpha1.InstallPlanSpec{
   129  					Approval:                   operatorsv1alpha1.ApprovalAutomatic,
   130  					Approved:                   true,
   131  					ClusterServiceVersionNames: []string{},
   132  				},
   133  			}
   134  			Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed())
   135  			plan.Status = operatorsv1alpha1.InstallPlanStatus{
   136  				Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
   137  				CatalogSources: []string{},
   138  				Plan: []*operatorsv1alpha1.Step{
   139  					{
   140  						Resolving: csv.GetName(),
   141  						Status:    operatorsv1alpha1.StepStatusUnknown,
   142  						Resource: operatorsv1alpha1.StepResource{
   143  							Name:     deprecated.GetName(),
   144  							Version:  "v1",
   145  							Kind:     "Deprecated",
   146  							Manifest: manifest,
   147  						},
   148  					},
   149  				},
   150  			}
   151  			Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed())
   152  			Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
   153  				return &plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&plan), &plan)
   154  			}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
   155  		})
   156  
   157  		AfterEach(func() {
   158  			Eventually(func() error {
   159  				return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv))
   160  			}).Should(Succeed())
   161  			Eventually(func() error {
   162  				deprecatedCRD := &apiextensionsv1.CustomResourceDefinition{
   163  					ObjectMeta: metav1.ObjectMeta{
   164  						Name: "deprecateds.operators.io.operator-framework",
   165  					},
   166  				}
   167  				return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), deprecatedCRD))
   168  			}).Should(Succeed())
   169  		})
   170  
   171  		It("creates an Event surfacing the deprecation warning", func() {
   172  			Eventually(func() ([]corev1.Event, error) {
   173  				var events corev1.EventList
   174  				if err := ctx.Ctx().Client().List(context.Background(), &events, client.InNamespace(generatedNamespace.GetName())); err != nil {
   175  					return nil, err
   176  				}
   177  				var result []corev1.Event
   178  				for _, item := range events.Items {
   179  					result = append(result, corev1.Event{
   180  						InvolvedObject: corev1.ObjectReference{
   181  							APIVersion: item.InvolvedObject.APIVersion,
   182  							Kind:       item.InvolvedObject.Kind,
   183  							Namespace:  item.InvolvedObject.Namespace,
   184  							Name:       item.InvolvedObject.Name,
   185  							FieldPath:  item.InvolvedObject.FieldPath,
   186  						},
   187  						Reason:  item.Reason,
   188  						Message: item.Message,
   189  					})
   190  				}
   191  				return result, nil
   192  			}).Should(ContainElement(corev1.Event{
   193  				InvolvedObject: corev1.ObjectReference{
   194  					APIVersion: operatorsv1alpha1.InstallPlanAPIVersion,
   195  					Kind:       operatorsv1alpha1.InstallPlanKind,
   196  					Namespace:  generatedNamespace.GetName(),
   197  					Name:       plan.GetName(),
   198  					FieldPath:  "status.plan[0]",
   199  				},
   200  				Reason:  "AppliedWithWarnings",
   201  				Message: fmt.Sprintf("1 warning(s) generated during installation of operator \"%s\" (Deprecated \"%s\"): operators.io.operator-framework/v1 Deprecated is deprecated", csv.GetName(), deprecated.GetName()),
   202  			}))
   203  		})
   204  
   205  		It("increments a metric counting the warning", func() {
   206  			Eventually(func() []Metric {
   207  				return getMetricsFromPod(ctx.Ctx().KubeClient(), getPodWithLabel(ctx.Ctx().KubeClient(), "app=catalog-operator"))
   208  			}).Should(ContainElement(LikeMetric(
   209  				WithFamily("installplan_warnings_total"),
   210  				WithValueGreaterThan(counter),
   211  			)))
   212  		})
   213  	})
   214  
   215  	When("a CustomResourceDefinition step resolved from a bundle is applied", func() {
   216  		var (
   217  			crd      apiextensionsv1.CustomResourceDefinition
   218  			manifest string
   219  		)
   220  
   221  		BeforeEach(func() {
   222  			csv := newCSV("test-csv", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil)
   223  			Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed())
   224  
   225  			crd = apiextensionsv1.CustomResourceDefinition{
   226  				ObjectMeta: metav1.ObjectMeta{
   227  					Name: "tests.example.com",
   228  				},
   229  				TypeMeta: metav1.TypeMeta{
   230  					Kind:       "CustomResourceDefinition",
   231  					APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
   232  				},
   233  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   234  					Group: "example.com",
   235  					Scope: apiextensionsv1.ClusterScoped,
   236  					Names: apiextensionsv1.CustomResourceDefinitionNames{
   237  						Plural:   "tests",
   238  						Singular: "test",
   239  						Kind:     "Test",
   240  						ListKind: "TestList",
   241  					},
   242  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
   243  						Name:    "v1",
   244  						Served:  true,
   245  						Storage: true,
   246  						Schema: &apiextensionsv1.CustomResourceValidation{
   247  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   248  								Type: "object",
   249  							},
   250  						},
   251  					}},
   252  				},
   253  			}
   254  
   255  			scheme := runtime.NewScheme()
   256  			Expect(corev1.AddToScheme(scheme)).To(Succeed())
   257  			{
   258  				var b bytes.Buffer
   259  				Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&crd, &b)).To(Succeed())
   260  				manifest = b.String()
   261  			}
   262  
   263  			plan := operatorsv1alpha1.InstallPlan{
   264  				ObjectMeta: metav1.ObjectMeta{
   265  					Namespace: generatedNamespace.GetName(),
   266  					Name:      "test-plan",
   267  				},
   268  				Spec: operatorsv1alpha1.InstallPlanSpec{
   269  					Approval:                   operatorsv1alpha1.ApprovalAutomatic,
   270  					Approved:                   true,
   271  					ClusterServiceVersionNames: []string{},
   272  				},
   273  			}
   274  			Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed())
   275  			plan.Status = operatorsv1alpha1.InstallPlanStatus{
   276  				Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
   277  				CatalogSources: []string{},
   278  				Plan: []*operatorsv1alpha1.Step{
   279  					{
   280  						Resolving: "test-csv",
   281  						Status:    operatorsv1alpha1.StepStatusUnknown,
   282  						Resource: operatorsv1alpha1.StepResource{
   283  							Name:     crd.GetName(),
   284  							Version:  apiextensionsv1.SchemeGroupVersion.String(),
   285  							Kind:     "CustomResourceDefinition",
   286  							Manifest: manifest,
   287  						},
   288  					},
   289  				},
   290  			}
   291  			Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed())
   292  			Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
   293  				return &plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&plan), &plan)
   294  			}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
   295  		})
   296  
   297  		AfterEach(func() {
   298  			Eventually(func() error {
   299  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}))
   300  			}).Should(Succeed())
   301  		})
   302  
   303  		It("is annotated with a reference to its associated ClusterServiceVersion", func() {
   304  			Eventually(func() (map[string]string, error) {
   305  				if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&crd), &crd); err != nil {
   306  					return nil, err
   307  				}
   308  				return crd.GetAnnotations(), nil
   309  			}).Should(HaveKeyWithValue(
   310  				HavePrefix("operatorframework.io/installed-alongside-"),
   311  				fmt.Sprintf("%s/test-csv", generatedNamespace.GetName()),
   312  			))
   313  		})
   314  
   315  		When("a second plan includes the same CustomResourceDefinition", func() {
   316  			var (
   317  				csv  operatorsv1alpha1.ClusterServiceVersion
   318  				plan operatorsv1alpha1.InstallPlan
   319  			)
   320  
   321  			BeforeEach(func() {
   322  				csv = newCSV("test-csv-two", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil)
   323  				Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed())
   324  
   325  				plan = operatorsv1alpha1.InstallPlan{
   326  					ObjectMeta: metav1.ObjectMeta{
   327  						Namespace: generatedNamespace.GetName(),
   328  						Name:      "test-plan-two",
   329  					},
   330  					Spec: operatorsv1alpha1.InstallPlanSpec{
   331  						Approval:                   operatorsv1alpha1.ApprovalAutomatic,
   332  						Approved:                   true,
   333  						ClusterServiceVersionNames: []string{},
   334  					},
   335  				}
   336  				Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed())
   337  				plan.Status = operatorsv1alpha1.InstallPlanStatus{
   338  					Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
   339  					CatalogSources: []string{},
   340  					Plan: []*operatorsv1alpha1.Step{
   341  						{
   342  							Resolving: "test-csv-two",
   343  							Status:    operatorsv1alpha1.StepStatusUnknown,
   344  							Resource: operatorsv1alpha1.StepResource{
   345  								Name:     crd.GetName(),
   346  								Version:  apiextensionsv1.SchemeGroupVersion.String(),
   347  								Kind:     "CustomResourceDefinition",
   348  								Manifest: manifest,
   349  							},
   350  						},
   351  					},
   352  				}
   353  				Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed())
   354  				Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
   355  					return &plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&plan), &plan)
   356  				}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
   357  			})
   358  
   359  			AfterEach(func() {
   360  				Eventually(func() error {
   361  					return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv))
   362  				}).Should(Succeed())
   363  				Eventually(func() error {
   364  					return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &plan))
   365  				}).Should(Succeed())
   366  			})
   367  
   368  			It("has one annotation for each ClusterServiceVersion", func() {
   369  				Eventually(func() ([]struct{ Key, Value string }, error) {
   370  					if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&crd), &crd); err != nil {
   371  						return nil, err
   372  					}
   373  					var pairs []struct{ Key, Value string }
   374  					for k, v := range crd.GetAnnotations() {
   375  						pairs = append(pairs, struct{ Key, Value string }{Key: k, Value: v})
   376  					}
   377  					return pairs, nil
   378  				}).Should(ConsistOf(
   379  					MatchFields(IgnoreExtras, Fields{
   380  						"Key":   HavePrefix("operatorframework.io/installed-alongside-"),
   381  						"Value": Equal(fmt.Sprintf("%s/test-csv", generatedNamespace.GetName())),
   382  					}),
   383  					MatchFields(IgnoreExtras, Fields{
   384  						"Key":   HavePrefix("operatorframework.io/installed-alongside-"),
   385  						"Value": Equal(fmt.Sprintf("%s/test-csv-two", generatedNamespace.GetName())),
   386  					}),
   387  				))
   388  			})
   389  		})
   390  	})
   391  
   392  	When("an error is encountered during InstallPlan step execution", func() {
   393  		var (
   394  			plan  *operatorsv1alpha1.InstallPlan
   395  			owned *corev1.ConfigMap
   396  		)
   397  
   398  		BeforeEach(func() {
   399  			By(`It's hard to reliably generate transient`)
   400  			By(`errors in an uninstrumented end-to-end`)
   401  			By(`test, so simulate it by constructing an`)
   402  			By(`error state that can be easily corrected`)
   403  			By(`during a test.`)
   404  			owned = &corev1.ConfigMap{
   405  				ObjectMeta: metav1.ObjectMeta{
   406  					Namespace: generatedNamespace.GetName(),
   407  					Name:      "test-owned",
   408  					OwnerReferences: []metav1.OwnerReference{{
   409  						APIVersion: "operators.coreos.com/v1alpha1",
   410  						Kind:       "ClusterServiceVersion",
   411  						Name:       "test-owner", // Does not exist!
   412  						UID:        "",           // catalog-operator populates this if the named CSV exists.
   413  					}},
   414  				},
   415  			}
   416  
   417  			scheme := runtime.NewScheme()
   418  			Expect(corev1.AddToScheme(scheme)).To(Succeed())
   419  			var manifest bytes.Buffer
   420  			Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(owned, &manifest)).To(Succeed())
   421  
   422  			plan = &operatorsv1alpha1.InstallPlan{
   423  				ObjectMeta: metav1.ObjectMeta{
   424  					Namespace: generatedNamespace.GetName(),
   425  					Name:      "test-plan",
   426  				},
   427  				Spec: operatorsv1alpha1.InstallPlanSpec{
   428  					Approval:                   operatorsv1alpha1.ApprovalAutomatic,
   429  					Approved:                   true,
   430  					ClusterServiceVersionNames: []string{},
   431  				},
   432  			}
   433  			Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed())
   434  			plan.Status = operatorsv1alpha1.InstallPlanStatus{
   435  				Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
   436  				CatalogSources: []string{},
   437  				Plan: []*operatorsv1alpha1.Step{
   438  					{
   439  						Status: operatorsv1alpha1.StepStatusUnknown,
   440  						Resource: operatorsv1alpha1.StepResource{
   441  							Name:     owned.GetName(),
   442  							Version:  "v1",
   443  							Kind:     "ConfigMap",
   444  							Manifest: manifest.String(),
   445  						},
   446  					},
   447  				},
   448  			}
   449  			Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed())
   450  		})
   451  
   452  		AfterEach(func() {
   453  			Expect(ctx.Ctx().Client().Delete(context.Background(), owned)).To(Or(
   454  				Succeed(),
   455  				WithTransform(apierrors.IsNotFound, BeTrue()),
   456  			))
   457  			Expect(ctx.Ctx().Client().Delete(context.Background(), plan)).To(Or(
   458  				Succeed(),
   459  				WithTransform(apierrors.IsNotFound, BeTrue()),
   460  			))
   461  		})
   462  
   463  		It("times out if the error persists", func() {
   464  			Eventually(
   465  				func() (*operatorsv1alpha1.InstallPlan, error) {
   466  					return plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(plan), plan)
   467  				},
   468  				90*time.Second,
   469  			).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed))
   470  		})
   471  
   472  		It("succeeds if there is no error on a later attempt", func() {
   473  			owner := newCSV("test-owner", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil)
   474  			Expect(ctx.Ctx().Client().Create(context.Background(), &owner)).To(Succeed())
   475  			Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
   476  				return plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(plan), plan)
   477  			}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
   478  		})
   479  	})
   480  
   481  	When("an InstallPlan transfers ownership of a ServiceAccount to a new ClusterServiceVersion", func() {
   482  		var (
   483  			csv1, csv2 operatorsv1alpha1.ClusterServiceVersion
   484  			sa         corev1.ServiceAccount
   485  			plan       operatorsv1alpha1.InstallPlan
   486  		)
   487  
   488  		BeforeEach(func() {
   489  			csv1 = newCSV("test-csv-old", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil)
   490  			Expect(ctx.Ctx().Client().Create(context.Background(), &csv1)).To(Succeed())
   491  			csv2 = newCSV("test-csv-new", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil)
   492  			Expect(ctx.Ctx().Client().Create(context.Background(), &csv2)).To(Succeed())
   493  
   494  			sa = corev1.ServiceAccount{
   495  				ObjectMeta: metav1.ObjectMeta{
   496  					Namespace: generatedNamespace.GetName(),
   497  					Name:      "test-serviceaccount",
   498  					OwnerReferences: []metav1.OwnerReference{
   499  						{
   500  							APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(),
   501  							Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
   502  							Name:       csv1.GetName(),
   503  							UID:        csv1.GetUID(),
   504  						},
   505  					},
   506  				},
   507  			}
   508  			Expect(ctx.Ctx().Client().Create(context.Background(), &sa)).To(Succeed())
   509  
   510  			scheme := runtime.NewScheme()
   511  			Expect(corev1.AddToScheme(scheme)).To(Succeed())
   512  			var manifest bytes.Buffer
   513  			Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&corev1.ServiceAccount{
   514  				ObjectMeta: metav1.ObjectMeta{
   515  					Namespace: generatedNamespace.GetName(),
   516  					Name:      "test-serviceaccount",
   517  					OwnerReferences: []metav1.OwnerReference{
   518  						{
   519  							APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(),
   520  							Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
   521  							Name:       csv2.GetName(),
   522  						},
   523  					},
   524  				},
   525  			}, &manifest)).To(Succeed())
   526  
   527  			plan = operatorsv1alpha1.InstallPlan{
   528  				ObjectMeta: metav1.ObjectMeta{
   529  					Namespace: generatedNamespace.GetName(),
   530  					Name:      "test-plan",
   531  				},
   532  				Spec: operatorsv1alpha1.InstallPlanSpec{
   533  					Approval:                   operatorsv1alpha1.ApprovalAutomatic,
   534  					Approved:                   true,
   535  					ClusterServiceVersionNames: []string{},
   536  				},
   537  			}
   538  			Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed())
   539  			plan.Status = operatorsv1alpha1.InstallPlanStatus{
   540  				Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
   541  				CatalogSources: []string{},
   542  				Plan: []*operatorsv1alpha1.Step{
   543  					{
   544  						Status: operatorsv1alpha1.StepStatusUnknown,
   545  						Resource: operatorsv1alpha1.StepResource{
   546  							Name:     sa.GetName(),
   547  							Version:  "v1",
   548  							Kind:     "ServiceAccount",
   549  							Manifest: manifest.String(),
   550  						},
   551  					},
   552  				},
   553  			}
   554  			Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed())
   555  		})
   556  
   557  		AfterEach(func() {
   558  			Expect(ctx.Ctx().Client().Delete(context.Background(), &sa)).To(Or(
   559  				Succeed(),
   560  				WithTransform(apierrors.IsNotFound, BeTrue()),
   561  			))
   562  			Expect(ctx.Ctx().Client().Delete(context.Background(), &csv1)).To(Or(
   563  				Succeed(),
   564  				WithTransform(apierrors.IsNotFound, BeTrue()),
   565  			))
   566  			Expect(ctx.Ctx().Client().Delete(context.Background(), &csv2)).To(Or(
   567  				Succeed(),
   568  				WithTransform(apierrors.IsNotFound, BeTrue()),
   569  			))
   570  			Expect(ctx.Ctx().Client().Delete(context.Background(), &plan)).To(Or(
   571  				Succeed(),
   572  				WithTransform(apierrors.IsNotFound, BeTrue()),
   573  			))
   574  		})
   575  
   576  		It("preserves owner references to both the old and the new ClusterServiceVersion", func() {
   577  			Eventually(func() ([]metav1.OwnerReference, error) {
   578  				if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&sa), &sa); err != nil {
   579  					return nil, err
   580  				}
   581  				return sa.GetOwnerReferences(), nil
   582  			}).Should(ContainElements([]metav1.OwnerReference{
   583  				{
   584  					APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(),
   585  					Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
   586  					Name:       csv1.GetName(),
   587  					UID:        csv1.GetUID(),
   588  				},
   589  				{
   590  					APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(),
   591  					Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
   592  					Name:       csv2.GetName(),
   593  					UID:        csv2.GetUID(),
   594  				},
   595  			}))
   596  		})
   597  	})
   598  
   599  	When("a ClusterIP service exists", func() {
   600  		var (
   601  			service *corev1.Service
   602  		)
   603  
   604  		BeforeEach(func() {
   605  			service = &corev1.Service{
   606  				TypeMeta: metav1.TypeMeta{
   607  					Kind:       "Service",
   608  					APIVersion: "v1",
   609  				},
   610  				ObjectMeta: metav1.ObjectMeta{
   611  					Namespace: generatedNamespace.GetName(),
   612  					Name:      "test-service",
   613  				},
   614  				Spec: corev1.ServiceSpec{
   615  					Type: corev1.ServiceTypeClusterIP,
   616  					Ports: []corev1.ServicePort{
   617  						{
   618  							Port: 12345,
   619  						},
   620  					},
   621  				},
   622  			}
   623  
   624  			Expect(ctx.Ctx().Client().Create(context.Background(), service.DeepCopy())).To(Succeed())
   625  		})
   626  
   627  		AfterEach(func() {
   628  			Expect(ctx.Ctx().Client().Delete(context.Background(), service)).To(Succeed())
   629  		})
   630  
   631  		It("it can be updated by an InstallPlan step", func() {
   632  			scheme := runtime.NewScheme()
   633  			Expect(corev1.AddToScheme(scheme)).To(Succeed())
   634  			var manifest bytes.Buffer
   635  			Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(service, &manifest)).To(Succeed())
   636  
   637  			plan := &operatorsv1alpha1.InstallPlan{
   638  				ObjectMeta: metav1.ObjectMeta{
   639  					Namespace: generatedNamespace.GetName(),
   640  					Name:      "test-plan",
   641  				},
   642  				Spec: operatorsv1alpha1.InstallPlanSpec{
   643  					Approval:                   operatorsv1alpha1.ApprovalAutomatic,
   644  					Approved:                   true,
   645  					ClusterServiceVersionNames: []string{},
   646  				},
   647  			}
   648  
   649  			Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed())
   650  			plan.Status = operatorsv1alpha1.InstallPlanStatus{
   651  				Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
   652  				CatalogSources: []string{},
   653  				Plan: []*operatorsv1alpha1.Step{
   654  					{
   655  						Status: operatorsv1alpha1.StepStatusUnknown,
   656  						Resource: operatorsv1alpha1.StepResource{
   657  							Name:     service.Name,
   658  							Version:  "v1",
   659  							Kind:     "Service",
   660  							Manifest: manifest.String(),
   661  						},
   662  					},
   663  				},
   664  			}
   665  			Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed())
   666  
   667  			key := client.ObjectKeyFromObject(plan)
   668  
   669  			Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
   670  				return plan, ctx.Ctx().Client().Get(context.Background(), key, plan)
   671  			}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
   672  			Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), plan))).To(Succeed())
   673  		})
   674  	})
   675  
   676  	It("with CSVs across multiple catalog sources", func() {
   677  
   678  		log := func(s string) {
   679  			GinkgoT().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s)
   680  		}
   681  
   682  		mainPackageName := genName("nginx-")
   683  		dependentPackageName := genName("nginxdep-")
   684  
   685  		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
   686  		dependentPackageStable := fmt.Sprintf("%s-stable", dependentPackageName)
   687  
   688  		stableChannel := "stable"
   689  
   690  		dependentCRD := newCRD(genName("ins-"))
   691  		mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil)
   692  		dependentCSV := newCSV(dependentPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil)
   693  
   694  		defer func() {
   695  			require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
   696  		}()
   697  
   698  		dependentCatalogName := genName("mock-ocs-dependent-")
   699  		mainCatalogName := genName("mock-ocs-main-")
   700  
   701  		By(`Create separate manifests for each CatalogSource`)
   702  		mainManifests := []registry.PackageManifest{
   703  			{
   704  				PackageName: mainPackageName,
   705  				Channels: []registry.PackageChannel{
   706  					{Name: stableChannel, CurrentCSVName: mainPackageStable},
   707  				},
   708  				DefaultChannelName: stableChannel,
   709  			},
   710  		}
   711  
   712  		dependentManifests := []registry.PackageManifest{
   713  			{
   714  				PackageName: dependentPackageName,
   715  				Channels: []registry.PackageChannel{
   716  					{Name: stableChannel, CurrentCSVName: dependentPackageStable},
   717  				},
   718  				DefaultChannelName: stableChannel,
   719  			},
   720  		}
   721  
   722  		By(`Defer CRD clean up`)
   723  		defer func() {
   724  			Eventually(func() error {
   725  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{}))
   726  			}).Should(Succeed())
   727  		}()
   728  
   729  		By(`Create the catalog sources`)
   730  		require.NotEqual(GinkgoT(), "", generatedNamespace.GetName())
   731  		_, cleanupDependentCatalogSource := createInternalCatalogSource(c, crc, dependentCatalogName, generatedNamespace.GetName(), dependentManifests, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentCSV})
   732  		defer cleanupDependentCatalogSource()
   733  
   734  		By(`Attempt to get the catalog source before creating install plan`)
   735  		_, err := fetchCatalogSourceOnStatus(crc, dependentCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
   736  		require.NoError(GinkgoT(), err)
   737  
   738  		_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, nil, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
   739  		defer cleanupMainCatalogSource()
   740  
   741  		By(`Attempt to get the catalog source before creating install plan`)
   742  		_, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
   743  		require.NoError(GinkgoT(), err)
   744  
   745  		By(`Create expected install plan step sources`)
   746  		expectedStepSources := map[registry.ResourceKey]registry.ResourceKey{
   747  			{Name: dependentCRD.Name, Kind: "CustomResourceDefinition"}:                                                                                               {Name: dependentCatalogName, Namespace: generatedNamespace.GetName()},
   748  			{Name: dependentPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}:                                                                         {Name: dependentCatalogName, Namespace: generatedNamespace.GetName()},
   749  			{Name: mainPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}:                                                                              {Name: mainCatalogName, Namespace: generatedNamespace.GetName()},
   750  			{Name: strings.Join([]string{dependentPackageStable, dependentCatalogName, generatedNamespace.GetName()}, "-"), Kind: operatorsv1alpha1.SubscriptionKind}: {Name: dependentCatalogName, Namespace: generatedNamespace.GetName()},
   751  		}
   752  
   753  		subscriptionName := genName("sub-nginx-")
   754  		subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
   755  		defer subscriptionCleanup()
   756  
   757  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
   758  		require.NoError(GinkgoT(), err)
   759  		require.NotNil(GinkgoT(), subscription)
   760  
   761  		installPlanName := subscription.Status.InstallPlanRef.Name
   762  
   763  		By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
   764  		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   765  		require.NoError(GinkgoT(), err)
   766  		log(fmt.Sprintf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase))
   767  
   768  		require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
   769  
   770  		By(`Fetch installplan again to check for unnecessary control loops`)
   771  		fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool {
   772  			By(`Don't compare object meta as labels can be applied by the operator controller.`)
   773  			Expect(equality.Semantic.DeepEqual(fetchedInstallPlan.Spec, fip.Spec)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip))
   774  			Expect(equality.Semantic.DeepEqual(fetchedInstallPlan.Status, fip.Status)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip))
   775  			return true
   776  		})
   777  		require.NoError(GinkgoT(), err)
   778  		require.Equal(GinkgoT(), len(expectedStepSources), len(fetchedInstallPlan.Status.Plan), "Number of resolved steps matches the number of expected steps")
   779  
   780  		By(`Ensure resolved step resources originate from the correct catalog sources`)
   781  		log(fmt.Sprintf("%#v", expectedStepSources))
   782  		for _, step := range fetchedInstallPlan.Status.Plan {
   783  			log(fmt.Sprintf("checking %s", step.Resource))
   784  			key := registry.ResourceKey{Name: step.Resource.Name, Kind: step.Resource.Kind}
   785  			expectedSource, ok := expectedStepSources[key]
   786  			require.True(GinkgoT(), ok, "didn't find %v", key)
   787  			require.Equal(GinkgoT(), expectedSource.Name, step.Resource.CatalogSource)
   788  			require.Equal(GinkgoT(), expectedSource.Namespace, step.Resource.CatalogSourceNamespace)
   789  
   790  			By(`delete`)
   791  		}
   792  	EXPECTED:
   793  		for key := range expectedStepSources {
   794  			for _, step := range fetchedInstallPlan.Status.Plan {
   795  				if step.Resource.Name == key.Name && step.Resource.Kind == key.Kind {
   796  					continue EXPECTED
   797  				}
   798  			}
   799  			GinkgoT().Fatalf("expected step %s not found in %#v", key, fetchedInstallPlan.Status.Plan)
   800  		}
   801  
   802  		log("All expected resources resolved")
   803  
   804  		By(`Verify that the dependent subscription is in a good state`)
   805  		dependentSubscription, err := fetchSubscription(crc, generatedNamespace.GetName(), strings.Join([]string{dependentPackageStable, dependentCatalogName, generatedNamespace.GetName()}, "-"), subscriptionStateAtLatestChecker())
   806  		require.NoError(GinkgoT(), err)
   807  		require.NotNil(GinkgoT(), dependentSubscription)
   808  		require.NotNil(GinkgoT(), dependentSubscription.Status.InstallPlanRef)
   809  		require.Equal(GinkgoT(), dependentCSV.GetName(), dependentSubscription.Status.CurrentCSV)
   810  
   811  		By(`Verify CSV is created`)
   812  		_, err = fetchCSV(crc, generatedNamespace.GetName(), dependentCSV.GetName(), csvAnyChecker)
   813  		require.NoError(GinkgoT(), err)
   814  
   815  		By(`Update dependent subscription in catalog and wait for csv to update`)
   816  		updatedDependentCSV := newCSV(dependentPackageStable+"-v2", generatedNamespace.GetName(), dependentPackageStable, semver.MustParse("0.1.1"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil)
   817  		dependentManifests = []registry.PackageManifest{
   818  			{
   819  				PackageName: dependentPackageName,
   820  				Channels: []registry.PackageChannel{
   821  					{Name: stableChannel, CurrentCSVName: updatedDependentCSV.GetName()},
   822  				},
   823  				DefaultChannelName: stableChannel,
   824  			},
   825  		}
   826  
   827  		updateInternalCatalog(GinkgoT(), c, crc, dependentCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentCSV, updatedDependentCSV}, dependentManifests)
   828  
   829  		By(`Wait for subscription to update`)
   830  		updatedDepSubscription, err := fetchSubscription(crc, generatedNamespace.GetName(), strings.Join([]string{dependentPackageStable, dependentCatalogName, generatedNamespace.GetName()}, "-"), subscriptionHasCurrentCSV(updatedDependentCSV.GetName()))
   831  		require.NoError(GinkgoT(), err)
   832  
   833  		By(`Verify installplan created and installed`)
   834  		fetchedUpdatedDepInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedDepSubscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   835  		require.NoError(GinkgoT(), err)
   836  		log(fmt.Sprintf("Install plan %s fetched with status %s", fetchedUpdatedDepInstallPlan.GetName(), fetchedUpdatedDepInstallPlan.Status.Phase))
   837  		require.NotEqual(GinkgoT(), fetchedInstallPlan.GetName(), fetchedUpdatedDepInstallPlan.GetName())
   838  
   839  		By(`Wait for csv to update`)
   840  		_, err = fetchCSV(crc, generatedNamespace.GetName(), updatedDependentCSV.GetName(), csvAnyChecker)
   841  		require.NoError(GinkgoT(), err)
   842  	})
   843  
   844  	Context("creation with pre existing CRD owners", func() {
   845  
   846  		It("OnePreExistingCRDOwner", func() {
   847  
   848  			mainPackageName := genName("nginx-")
   849  			dependentPackageName := genName("nginx-dep-")
   850  
   851  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
   852  			mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName)
   853  			dependentPackageStable := fmt.Sprintf("%s-stable", dependentPackageName)
   854  			dependentPackageBeta := fmt.Sprintf("%s-beta", dependentPackageName)
   855  
   856  			stableChannel := "stable"
   857  			betaChannel := "beta"
   858  
   859  			By(`Create manifests`)
   860  			mainManifests := []registry.PackageManifest{
   861  				{
   862  					PackageName: mainPackageName,
   863  					Channels: []registry.PackageChannel{
   864  						{Name: stableChannel, CurrentCSVName: mainPackageStable},
   865  					},
   866  					DefaultChannelName: stableChannel,
   867  				},
   868  				{
   869  					PackageName: dependentPackageName,
   870  					Channels: []registry.PackageChannel{
   871  						{Name: stableChannel, CurrentCSVName: dependentPackageStable},
   872  						{Name: betaChannel, CurrentCSVName: dependentPackageBeta},
   873  					},
   874  					DefaultChannelName: stableChannel,
   875  				},
   876  			}
   877  
   878  			By(`Create new CRDs`)
   879  			mainCRD := newCRD(genName("ins-"))
   880  			dependentCRD := newCRD(genName("ins-"))
   881  
   882  			By(`Create new CSVs`)
   883  			mainStableCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil)
   884  			mainBetaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil)
   885  			dependentStableCSV := newCSV(dependentPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil)
   886  			dependentBetaCSV := newCSV(dependentPackageBeta, generatedNamespace.GetName(), dependentPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil)
   887  
   888  			By(`Defer CRD clean up`)
   889  			defer func() {
   890  				Eventually(func() error {
   891  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{}))
   892  				}).Should(Succeed())
   893  				Eventually(func() error {
   894  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{}))
   895  				}).Should(Succeed())
   896  			}()
   897  
   898  			defer func() {
   899  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
   900  			}()
   901  
   902  			By(`Create the catalog source`)
   903  			mainCatalogSourceName := genName("mock-ocs-main-" + strings.ToLower(K8sSafeCurrentTestDescription()) + "-")
   904  			_, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{dependentCRD, mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentBetaCSV, dependentStableCSV, mainStableCSV, mainBetaCSV})
   905  			defer cleanupCatalogSource()
   906  
   907  			By(`Attempt to get the catalog source before creating install plan(s)`)
   908  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
   909  			require.NoError(GinkgoT(), err)
   910  
   911  			expectedSteps := map[registry.ResourceKey]struct{}{
   912  				{Name: mainCRD.Name, Kind: "CustomResourceDefinition"}:                       {},
   913  				{Name: mainPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}: {},
   914  			}
   915  
   916  			By(`Create the preexisting CRD and CSV`)
   917  			cleanupCRD, err := createCRD(c, dependentCRD)
   918  			require.NoError(GinkgoT(), err)
   919  			defer cleanupCRD()
   920  			cleanupCSV, err := createCSV(c, crc, dependentBetaCSV, generatedNamespace.GetName(), true, false)
   921  			require.NoError(GinkgoT(), err)
   922  			defer cleanupCSV()
   923  			GinkgoT().Log("Dependent CRD and preexisting CSV created")
   924  
   925  			subscriptionName := genName("sub-nginx-")
   926  			subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
   927  			defer subscriptionCleanup()
   928  
   929  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
   930  			require.NoError(GinkgoT(), err)
   931  			require.NotNil(GinkgoT(), subscription)
   932  
   933  			installPlanName := subscription.Status.InstallPlanRef.Name
   934  
   935  			By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`)
   936  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed))
   937  			require.NoError(GinkgoT(), err)
   938  			GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
   939  
   940  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
   941  
   942  			By(`Fetch installplan again to check for unnecessary control loops`)
   943  			fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool {
   944  				Expect(equality.Semantic.DeepEqual(fetchedInstallPlan, fip)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip))
   945  				return true
   946  			})
   947  			require.NoError(GinkgoT(), err)
   948  
   949  			for _, step := range fetchedInstallPlan.Status.Plan {
   950  				GinkgoT().Logf("%#v", step)
   951  			}
   952  			require.Equal(GinkgoT(), len(fetchedInstallPlan.Status.Plan), len(expectedSteps), "number of expected steps does not match installed")
   953  			GinkgoT().Logf("Number of resolved steps matches the number of expected steps")
   954  
   955  			for _, step := range fetchedInstallPlan.Status.Plan {
   956  				key := registry.ResourceKey{
   957  					Name: step.Resource.Name,
   958  					Kind: step.Resource.Kind,
   959  				}
   960  				_, ok := expectedSteps[key]
   961  				require.True(GinkgoT(), ok)
   962  
   963  				By(`Remove the entry from the expected steps set (to ensure no duplicates in resolved plan)`)
   964  				delete(expectedSteps, key)
   965  			}
   966  
   967  			By(`Should have removed every matching step`)
   968  			require.Equal(GinkgoT(), 0, len(expectedSteps), "Actual resource steps do not match expected")
   969  
   970  			By(`Delete CRDs`)
   971  			Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &mainCRD))).To(Succeed())
   972  			Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &dependentCRD))).To(Succeed())
   973  		})
   974  	})
   975  
   976  	Describe("with CRD schema change", func() {
   977  		type schemaPayload struct {
   978  			name            string
   979  			expectedPhase   operatorsv1alpha1.InstallPlanPhase
   980  			oldCRD          *apiextensionsv1.CustomResourceDefinition
   981  			intermediateCRD *apiextensionsv1.CustomResourceDefinition
   982  			newCRD          *apiextensionsv1.CustomResourceDefinition
   983  		}
   984  
   985  		var min float64 = 2
   986  		var max float64 = 256
   987  		var newMax float64 = 50
   988  		// generated outside of the test table so that the same naming can be used for both old and new CSVs
   989  		mainCRDPlural := genName("testcrd-")
   990  
   991  		// excluded: new CRD, same version, same schema - won't trigger a CRD update
   992  		tableEntries := []TableEntry{
   993  			Entry("all existing versions are present, different (backwards compatible) schema", schemaPayload{
   994  				name:          "all existing versions are present, different (backwards compatible) schema",
   995  				expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete,
   996  				oldCRD: func() *apiextensionsv1.CustomResourceDefinition {
   997  					oldCRD := newCRD(mainCRDPlural + "a")
   998  					oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
   999  						{
  1000  							Name:    "v1alpha1",
  1001  							Served:  true,
  1002  							Storage: true,
  1003  							Schema: &apiextensionsv1.CustomResourceValidation{
  1004  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1005  									Type: "object",
  1006  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1007  										"spec": {
  1008  											Type:        "object",
  1009  											Description: "Spec of a test object.",
  1010  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1011  												"scalar": {
  1012  													Type:        "number",
  1013  													Description: "Scalar value that should have a min and max.",
  1014  													Minimum:     &min,
  1015  													Maximum:     &max,
  1016  												},
  1017  											},
  1018  										},
  1019  									},
  1020  								},
  1021  							},
  1022  						},
  1023  					}
  1024  					return &oldCRD
  1025  				}(),
  1026  				newCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1027  					newCRD := newCRD(mainCRDPlural + "a")
  1028  					newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1029  						{
  1030  							Name:    "v1alpha1",
  1031  							Served:  true,
  1032  							Storage: false,
  1033  							Schema: &apiextensionsv1.CustomResourceValidation{
  1034  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1035  									Type: "object",
  1036  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1037  										"spec": {
  1038  											Type:        "object",
  1039  											Description: "Spec of a test object.",
  1040  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1041  												"scalar": {
  1042  													Type:        "number",
  1043  													Description: "Scalar value that should have a min and max.",
  1044  													Minimum:     &min,
  1045  													Maximum:     &max,
  1046  												},
  1047  											},
  1048  										},
  1049  									},
  1050  								},
  1051  							},
  1052  						},
  1053  						{
  1054  							Name:    "v1alpha2",
  1055  							Served:  true,
  1056  							Storage: true,
  1057  							Schema: &apiextensionsv1.CustomResourceValidation{
  1058  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1059  									Type: "object",
  1060  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1061  										"spec": {
  1062  											Type:        "object",
  1063  											Description: "Spec of a test object.",
  1064  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1065  												"scalar": {
  1066  													Type:        "number",
  1067  													Description: "Scalar value that should have a min and max.",
  1068  													Minimum:     &min,
  1069  													Maximum:     &max,
  1070  												},
  1071  											},
  1072  										},
  1073  									},
  1074  								},
  1075  							},
  1076  						},
  1077  					}
  1078  					return &newCRD
  1079  				}(),
  1080  			}),
  1081  			Entry("all existing versions are present, different (backwards incompatible) schema", schemaPayload{name: "all existing versions are present, different (backwards incompatible) schema",
  1082  				expectedPhase: operatorsv1alpha1.InstallPlanPhaseFailed,
  1083  				oldCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1084  					oldCRD := newCRD(mainCRDPlural + "b")
  1085  					oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1086  						{
  1087  							Name:    "v1alpha1",
  1088  							Served:  true,
  1089  							Storage: true,
  1090  							Schema: &apiextensionsv1.CustomResourceValidation{
  1091  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1092  									Type: "object",
  1093  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1094  										"spec": {
  1095  											Type:        "object",
  1096  											Description: "Spec of a test object.",
  1097  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1098  												"scalar": {
  1099  													Type:        "number",
  1100  													Description: "Scalar value that should have a min and max.",
  1101  												},
  1102  											},
  1103  										},
  1104  									},
  1105  								},
  1106  							},
  1107  						},
  1108  					}
  1109  					return &oldCRD
  1110  				}(),
  1111  				newCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1112  					newCRD := newCRD(mainCRDPlural + "b")
  1113  					newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1114  						{
  1115  							Name:    "v1alpha1",
  1116  							Served:  true,
  1117  							Storage: true,
  1118  							Schema: &apiextensionsv1.CustomResourceValidation{
  1119  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1120  									Type: "object",
  1121  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1122  										"spec": {
  1123  											Type:        "object",
  1124  											Description: "Spec of a test object.",
  1125  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1126  												"scalar": {
  1127  													Type:        "number",
  1128  													Description: "Scalar value that should have a min and max.",
  1129  													Minimum:     &min,
  1130  													Maximum:     &newMax,
  1131  												},
  1132  											},
  1133  										},
  1134  									},
  1135  								},
  1136  							},
  1137  						},
  1138  					}
  1139  					return &newCRD
  1140  				}(),
  1141  			}),
  1142  			Entry("missing existing versions in new CRD", schemaPayload{name: "missing existing versions in new CRD",
  1143  				expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete,
  1144  				oldCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1145  					oldCRD := newCRD(mainCRDPlural + "c")
  1146  					oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1147  						{
  1148  							Name:    "v1alpha1",
  1149  							Served:  true,
  1150  							Storage: true,
  1151  							Schema: &apiextensionsv1.CustomResourceValidation{
  1152  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1153  									Type:        "object",
  1154  									Description: "my crd schema",
  1155  								},
  1156  							},
  1157  						},
  1158  						{
  1159  							Name:    "v1alpha2",
  1160  							Served:  true,
  1161  							Storage: false,
  1162  							Schema: &apiextensionsv1.CustomResourceValidation{
  1163  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1164  									Type:        "object",
  1165  									Description: "my crd schema",
  1166  								},
  1167  							},
  1168  						},
  1169  					}
  1170  					return &oldCRD
  1171  				}(),
  1172  				newCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1173  					newCRD := newCRD(mainCRDPlural + "c")
  1174  					newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1175  						{
  1176  							Name:    "v1alpha1",
  1177  							Served:  true,
  1178  							Storage: true,
  1179  							Schema: &apiextensionsv1.CustomResourceValidation{
  1180  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1181  									Type: "object",
  1182  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1183  										"spec": {
  1184  											Type:        "object",
  1185  											Description: "Spec of a test object.",
  1186  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1187  												"scalar": {
  1188  													Type:        "number",
  1189  													Description: "Scalar value that should have a min and max.",
  1190  													Minimum:     &min,
  1191  													Maximum:     &max,
  1192  												},
  1193  											},
  1194  										},
  1195  									},
  1196  								},
  1197  							},
  1198  						},
  1199  						{
  1200  							Name:    "v1",
  1201  							Served:  true,
  1202  							Storage: false,
  1203  							Schema: &apiextensionsv1.CustomResourceValidation{
  1204  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1205  									Type: "object",
  1206  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1207  										"spec": {
  1208  											Type:        "object",
  1209  											Description: "Spec of a test object.",
  1210  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1211  												"scalar": {
  1212  													Type:        "number",
  1213  													Description: "Scalar value that should have a min and max.",
  1214  													Minimum:     &min,
  1215  													Maximum:     &max,
  1216  												},
  1217  											},
  1218  										},
  1219  									},
  1220  								},
  1221  							},
  1222  						},
  1223  					}
  1224  					return &newCRD
  1225  				}()}),
  1226  			Entry("existing version is present in new CRD (deprecated field)", schemaPayload{name: "existing version is present in new CRD (deprecated field)",
  1227  				expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete,
  1228  				oldCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1229  					oldCRD := newCRD(mainCRDPlural + "d")
  1230  					return &oldCRD
  1231  				}(),
  1232  				newCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1233  					newCRD := newCRD(mainCRDPlural + "d")
  1234  					newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1235  						{
  1236  							Name:    "v1alpha1",
  1237  							Served:  true,
  1238  							Storage: true,
  1239  							Schema: &apiextensionsv1.CustomResourceValidation{
  1240  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1241  									Type: "object",
  1242  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1243  										"spec": {
  1244  											Type:        "object",
  1245  											Description: "Spec of a test object.",
  1246  											Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1247  												"scalar": {
  1248  													Type:        "number",
  1249  													Description: "Scalar value that should have a min and max.",
  1250  													Minimum:     &min,
  1251  													Maximum:     &max,
  1252  												},
  1253  											},
  1254  										},
  1255  									},
  1256  								},
  1257  							},
  1258  						},
  1259  						{
  1260  							Name:    "v1alpha3",
  1261  							Served:  false,
  1262  							Storage: false,
  1263  							Schema: &apiextensionsv1.CustomResourceValidation{
  1264  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{Type: "object"},
  1265  							},
  1266  						},
  1267  					}
  1268  					return &newCRD
  1269  				}()}),
  1270  		}
  1271  
  1272  		DescribeTable("Test", func(tt schemaPayload) {
  1273  
  1274  			mainPackageName := genName("nginx-")
  1275  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  1276  			mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName)
  1277  
  1278  			stableChannel := "stable"
  1279  			betaChannel := "beta"
  1280  
  1281  			By(`Create manifests`)
  1282  			mainManifests := []registry.PackageManifest{
  1283  				{
  1284  					PackageName: mainPackageName,
  1285  					Channels: []registry.PackageChannel{
  1286  						{Name: stableChannel, CurrentCSVName: mainPackageStable},
  1287  						{Name: betaChannel, CurrentCSVName: mainPackageBeta},
  1288  					},
  1289  					DefaultChannelName: stableChannel,
  1290  				},
  1291  			}
  1292  
  1293  			By(`Create new CSVs`)
  1294  			mainStableCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, nil, nil)
  1295  			mainBetaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, nil, nil)
  1296  
  1297  			By(`Defer CRD clean up`)
  1298  			defer func() {
  1299  				Eventually(func() error {
  1300  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.oldCRD.GetName(), metav1.DeleteOptions{}))
  1301  				}).Should(Succeed())
  1302  				Eventually(func() error {
  1303  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.newCRD.GetName(), metav1.DeleteOptions{}))
  1304  				}).Should(Succeed())
  1305  				if tt.intermediateCRD != nil {
  1306  					Eventually(func() error {
  1307  						return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.intermediateCRD.GetName(), metav1.DeleteOptions{}))
  1308  					}).Should(Succeed())
  1309  				}
  1310  			}()
  1311  
  1312  			By(`Existing custom resource`)
  1313  			existingCR := &unstructured.Unstructured{
  1314  				Object: map[string]interface{}{
  1315  					"apiVersion": "cluster.com/v1alpha1",
  1316  					"kind":       tt.oldCRD.Spec.Names.Kind,
  1317  					"metadata": map[string]interface{}{
  1318  						"namespace": generatedNamespace.GetName(),
  1319  						"name":      "my-cr-1",
  1320  					},
  1321  					"spec": map[string]interface{}{
  1322  						"scalar": 100,
  1323  					},
  1324  				},
  1325  			}
  1326  
  1327  			By(`Create the catalog source`)
  1328  			mainCatalogSourceName := genName("mock-ocs-main-")
  1329  			_, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV})
  1330  			defer cleanupCatalogSource()
  1331  
  1332  			By(`Attempt to get the catalog source before creating install plan(s)`)
  1333  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1334  			require.NoError(GinkgoT(), err)
  1335  
  1336  			subscriptionName := genName("sub-nginx-alpha-")
  1337  			cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  1338  			defer cleanupSubscription()
  1339  
  1340  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  1341  			require.NoError(GinkgoT(), err)
  1342  			require.NotNil(GinkgoT(), subscription)
  1343  
  1344  			installPlanName := subscription.Status.InstallPlanRef.Name
  1345  
  1346  			By(`Wait for InstallPlan to be status: Complete or failed before checking resource presence`)
  1347  			completeOrFailedFunc := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed)
  1348  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), completeOrFailedFunc)
  1349  			require.NoError(GinkgoT(), err)
  1350  			GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
  1351  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  1352  
  1353  			By(`Ensure that the desired resources have been created`)
  1354  			expectedSteps := map[registry.ResourceKey]struct{}{
  1355  				{Name: tt.oldCRD.Name, Kind: "CustomResourceDefinition"}:                     {},
  1356  				{Name: mainPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}: {},
  1357  			}
  1358  
  1359  			require.Equal(GinkgoT(), len(expectedSteps), len(fetchedInstallPlan.Status.Plan), "number of expected steps does not match installed")
  1360  
  1361  			for _, step := range fetchedInstallPlan.Status.Plan {
  1362  				key := registry.ResourceKey{
  1363  					Name: step.Resource.Name,
  1364  					Kind: step.Resource.Kind,
  1365  				}
  1366  				_, ok := expectedSteps[key]
  1367  				require.True(GinkgoT(), ok, "couldn't find %v in expected steps: %#v", key, expectedSteps)
  1368  
  1369  				By(`Remove the entry from the expected steps set (to ensure no duplicates in resolved plan)`)
  1370  				delete(expectedSteps, key)
  1371  			}
  1372  
  1373  			By(`Should have removed every matching step`)
  1374  			require.Equal(GinkgoT(), 0, len(expectedSteps), "Actual resource steps do not match expected")
  1375  
  1376  			By(`Create initial CR`)
  1377  			cleanupCR, err := createCR(c, existingCR, "cluster.com", "v1alpha1", generatedNamespace.GetName(), tt.oldCRD.Spec.Names.Plural, "my-cr-1")
  1378  			require.NoError(GinkgoT(), err)
  1379  			defer cleanupCR()
  1380  
  1381  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{*tt.newCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV}, mainManifests)
  1382  
  1383  			By(`Attempt to get the catalog source before creating install plan(s)`)
  1384  			_, err = fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1385  			require.NoError(GinkgoT(), err)
  1386  
  1387  			By(`Update the subscription resource to point to the beta CSV`)
  1388  			err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
  1389  				subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  1390  				require.NoError(GinkgoT(), err)
  1391  				require.NotNil(GinkgoT(), subscription)
  1392  
  1393  				subscription.Spec.Channel = betaChannel
  1394  				subscription, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Update(context.Background(), subscription, metav1.UpdateOptions{})
  1395  
  1396  				return err
  1397  			})
  1398  
  1399  			By(`Wait for subscription to have a new installplan`)
  1400  			subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName()))
  1401  			require.NoError(GinkgoT(), err)
  1402  			require.NotNil(GinkgoT(), subscription)
  1403  
  1404  			installPlanName = subscription.Status.InstallPlanRef.Name
  1405  
  1406  			By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`)
  1407  			fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(tt.expectedPhase))
  1408  			require.NoError(GinkgoT(), err)
  1409  			GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
  1410  
  1411  			require.Equal(GinkgoT(), tt.expectedPhase, fetchedInstallPlan.Status.Phase)
  1412  
  1413  			By(`Ensure correct in-cluster resource(s)`)
  1414  			fetchedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainBetaCSV.GetName(), csvAnyChecker)
  1415  			require.NoError(GinkgoT(), err)
  1416  
  1417  			GinkgoT().Logf("All expected resources resolved %s", fetchedCSV.Status.Phase)
  1418  		}, tableEntries)
  1419  
  1420  	})
  1421  
  1422  	Describe("with deprecated version CRD", func() {
  1423  
  1424  		// generated outside of the test table so that the same naming can be used for both old and new CSVs
  1425  		mainCRDPlural := genName("ins")
  1426  
  1427  		type schemaPayload struct {
  1428  			name            string
  1429  			expectedPhase   operatorsv1alpha1.InstallPlanPhase
  1430  			oldCRD          *apiextensionsv1.CustomResourceDefinition
  1431  			intermediateCRD *apiextensionsv1.CustomResourceDefinition
  1432  			newCRD          *apiextensionsv1.CustomResourceDefinition
  1433  		}
  1434  
  1435  		// excluded: new CRD, same version, same schema - won't trigger a CRD update
  1436  
  1437  		tableEntries := []TableEntry{
  1438  			Entry("upgrade CRD with deprecated version", schemaPayload{
  1439  				name:          "upgrade CRD with deprecated version",
  1440  				expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete,
  1441  				oldCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1442  					oldCRD := newCRD(mainCRDPlural)
  1443  					oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1444  						{
  1445  							Name:    "v1alpha1",
  1446  							Served:  true,
  1447  							Storage: true,
  1448  							Schema: &apiextensionsv1.CustomResourceValidation{
  1449  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1450  									Type:        "object",
  1451  									Description: "my crd schema",
  1452  								},
  1453  							},
  1454  						},
  1455  					}
  1456  					return &oldCRD
  1457  				}(),
  1458  				intermediateCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1459  					intermediateCRD := newCRD(mainCRDPlural)
  1460  					intermediateCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1461  						{
  1462  							Name:    "v1alpha2",
  1463  							Served:  true,
  1464  							Storage: true,
  1465  							Schema: &apiextensionsv1.CustomResourceValidation{
  1466  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1467  									Type:        "object",
  1468  									Description: "my crd schema",
  1469  								},
  1470  							},
  1471  						},
  1472  						{
  1473  							Name:    "v1alpha1",
  1474  							Served:  true,
  1475  							Storage: false,
  1476  							Schema: &apiextensionsv1.CustomResourceValidation{
  1477  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1478  									Type:        "object",
  1479  									Description: "my crd schema",
  1480  								},
  1481  							},
  1482  						},
  1483  					}
  1484  					return &intermediateCRD
  1485  				}(),
  1486  				newCRD: func() *apiextensionsv1.CustomResourceDefinition {
  1487  					newCRD := newCRD(mainCRDPlural)
  1488  					newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
  1489  						{
  1490  							Name:    "v1alpha2",
  1491  							Served:  true,
  1492  							Storage: true,
  1493  							Schema: &apiextensionsv1.CustomResourceValidation{
  1494  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1495  									Type:        "object",
  1496  									Description: "my crd schema",
  1497  								},
  1498  							},
  1499  						},
  1500  						{
  1501  							Name:    "v1beta1",
  1502  							Served:  true,
  1503  							Storage: false,
  1504  							Schema: &apiextensionsv1.CustomResourceValidation{
  1505  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1506  									Type:        "object",
  1507  									Description: "my crd schema",
  1508  								},
  1509  							},
  1510  						},
  1511  						{
  1512  							Name:    "v1alpha1",
  1513  							Served:  false,
  1514  							Storage: false,
  1515  							Schema: &apiextensionsv1.CustomResourceValidation{
  1516  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1517  									Type:        "object",
  1518  									Description: "my crd schema",
  1519  								},
  1520  							},
  1521  						},
  1522  					}
  1523  					return &newCRD
  1524  				}(),
  1525  			}),
  1526  		}
  1527  
  1528  		DescribeTable("Test", func(tt schemaPayload) {
  1529  
  1530  			mainPackageName := genName("nginx-")
  1531  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  1532  			mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName)
  1533  			mainPackageDelta := fmt.Sprintf("%s-delta", mainPackageName)
  1534  
  1535  			stableChannel := "stable"
  1536  
  1537  			By(`Create manifests`)
  1538  			mainManifests := []registry.PackageManifest{
  1539  				{
  1540  					PackageName: mainPackageName,
  1541  					Channels: []registry.PackageChannel{
  1542  						{Name: stableChannel, CurrentCSVName: mainPackageStable},
  1543  					},
  1544  					DefaultChannelName: stableChannel,
  1545  				},
  1546  			}
  1547  
  1548  			By(`Create new CSVs`)
  1549  			mainStableCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, nil, nil)
  1550  			mainBetaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{*tt.intermediateCRD}, nil, nil)
  1551  			mainDeltaCSV := newCSV(mainPackageDelta, generatedNamespace.GetName(), mainPackageBeta, semver.MustParse("0.3.0"), []apiextensionsv1.CustomResourceDefinition{*tt.newCRD}, nil, nil)
  1552  
  1553  			By(`Defer CRD clean up`)
  1554  			defer func() {
  1555  				Eventually(func() error {
  1556  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.oldCRD.GetName(), metav1.DeleteOptions{}))
  1557  				}).Should(Succeed())
  1558  				Eventually(func() error {
  1559  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.newCRD.GetName(), metav1.DeleteOptions{}))
  1560  				}).Should(Succeed())
  1561  				if tt.intermediateCRD != nil {
  1562  					Eventually(func() error {
  1563  						return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.intermediateCRD.GetName(), metav1.DeleteOptions{}))
  1564  					}).Should(Succeed())
  1565  				}
  1566  			}()
  1567  
  1568  			By(`Defer crd clean up`)
  1569  			defer func() {
  1570  				Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), tt.newCRD))).To(Succeed())
  1571  				Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), tt.oldCRD))).To(Succeed())
  1572  				Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), tt.intermediateCRD))).To(Succeed())
  1573  			}()
  1574  
  1575  			By(`Create the catalog source`)
  1576  			mainCatalogSourceName := genName("mock-ocs-main-")
  1577  			_, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV})
  1578  			defer cleanupCatalogSource()
  1579  
  1580  			By(`Attempt to get the catalog source before creating install plan(s)`)
  1581  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1582  			require.NoError(GinkgoT(), err)
  1583  
  1584  			subscriptionName := genName("sub-nginx-")
  1585  
  1586  			By(`this subscription will be cleaned up below without the clean up function`)
  1587  			createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  1588  
  1589  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  1590  			require.NoError(GinkgoT(), err)
  1591  			require.NotNil(GinkgoT(), subscription)
  1592  
  1593  			installPlanName := subscription.Status.InstallPlanRef.Name
  1594  
  1595  			By(`Wait for InstallPlan to be status: Complete or failed before checking resource presence`)
  1596  			completeOrFailedFunc := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed)
  1597  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), completeOrFailedFunc)
  1598  			require.NoError(GinkgoT(), err)
  1599  			GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
  1600  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  1601  
  1602  			By(`Ensure CRD versions are accurate`)
  1603  			expectedVersions := map[string]struct{}{
  1604  				"v1alpha1": {},
  1605  			}
  1606  
  1607  			validateCRDVersions(GinkgoT(), c, tt.oldCRD.GetName(), expectedVersions)
  1608  
  1609  			By(`Update the manifest`)
  1610  			mainManifests = []registry.PackageManifest{
  1611  				{
  1612  					PackageName: mainPackageName,
  1613  					Channels: []registry.PackageChannel{
  1614  						{Name: stableChannel, CurrentCSVName: mainPackageBeta},
  1615  					},
  1616  					DefaultChannelName: stableChannel,
  1617  				},
  1618  			}
  1619  
  1620  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{*tt.intermediateCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV}, mainManifests)
  1621  			By(`Attempt to get the catalog source before creating install plan(s)`)
  1622  			_, err = fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1623  			require.NoError(GinkgoT(), err)
  1624  			subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(installPlanName))
  1625  			require.NoError(GinkgoT(), err)
  1626  			require.NotNil(GinkgoT(), subscription)
  1627  
  1628  			installPlanName = subscription.Status.InstallPlanRef.Name
  1629  
  1630  			By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`)
  1631  			fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed))
  1632  			require.NoError(GinkgoT(), err)
  1633  			GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
  1634  			require.Equal(GinkgoT(), tt.expectedPhase, fetchedInstallPlan.Status.Phase)
  1635  
  1636  			By(`Ensure correct in-cluster resource(s)`)
  1637  			fetchedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainBetaCSV.GetName(), csvSucceededChecker)
  1638  			require.NoError(GinkgoT(), err)
  1639  
  1640  			By(`Ensure CRD versions are accurate`)
  1641  			expectedVersions = map[string]struct{}{
  1642  				"v1alpha1": {},
  1643  				"v1alpha2": {},
  1644  			}
  1645  
  1646  			validateCRDVersions(GinkgoT(), c, tt.oldCRD.GetName(), expectedVersions)
  1647  
  1648  			By(`Update the manifest`)
  1649  			mainManifests = []registry.PackageManifest{
  1650  				{
  1651  					PackageName: mainPackageName,
  1652  					Channels: []registry.PackageChannel{
  1653  						{Name: stableChannel, CurrentCSVName: mainPackageDelta},
  1654  					},
  1655  					DefaultChannelName: stableChannel,
  1656  				},
  1657  			}
  1658  
  1659  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{*tt.newCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV, mainDeltaCSV}, mainManifests)
  1660  			By(`Attempt to get the catalog source before creating install plan(s)`)
  1661  			_, err = fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1662  			require.NoError(GinkgoT(), err)
  1663  			subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(installPlanName))
  1664  			require.NoError(GinkgoT(), err)
  1665  			require.NotNil(GinkgoT(), subscription)
  1666  
  1667  			installPlanName = subscription.Status.InstallPlanRef.Name
  1668  
  1669  			By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`)
  1670  			fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed))
  1671  			require.NoError(GinkgoT(), err)
  1672  			GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
  1673  
  1674  			require.Equal(GinkgoT(), tt.expectedPhase, fetchedInstallPlan.Status.Phase)
  1675  
  1676  			By(`Ensure correct in-cluster resource(s)`)
  1677  			fetchedCSV, err = fetchCSV(crc, generatedNamespace.GetName(), mainDeltaCSV.GetName(), csvSucceededChecker)
  1678  			require.NoError(GinkgoT(), err)
  1679  
  1680  			By(`Ensure CRD versions are accurate`)
  1681  			expectedVersions = map[string]struct{}{
  1682  				"v1alpha2": {},
  1683  				"v1beta1":  {},
  1684  				"v1alpha1": {},
  1685  			}
  1686  
  1687  			validateCRDVersions(GinkgoT(), c, tt.oldCRD.GetName(), expectedVersions)
  1688  			GinkgoT().Logf("All expected resources resolved %s", fetchedCSV.Status.Phase)
  1689  		}, tableEntries)
  1690  	})
  1691  
  1692  	Describe("update catalog for subscription", func() {
  1693  
  1694  		// crdVersionKey uniquely identifies a version within a CRD
  1695  		type crdVersionKey struct {
  1696  			name    string
  1697  			served  bool
  1698  			storage bool
  1699  		}
  1700  		It("AmplifyPermissions", func() {
  1701  
  1702  			defer func() {
  1703  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  1704  			}()
  1705  
  1706  			By(`Build initial catalog`)
  1707  			mainPackageName := genName("nginx-amplify-")
  1708  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  1709  			stableChannel := "stable"
  1710  			crdPlural := genName("ins-amplify-")
  1711  			crdName := crdPlural + ".cluster.com"
  1712  			mainCRD := apiextensionsv1.CustomResourceDefinition{
  1713  				ObjectMeta: metav1.ObjectMeta{
  1714  					Name: crdName,
  1715  				},
  1716  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  1717  					Group: "cluster.com",
  1718  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  1719  						{
  1720  							Name:    "v1alpha1",
  1721  							Served:  true,
  1722  							Storage: true,
  1723  							Schema: &apiextensionsv1.CustomResourceValidation{
  1724  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1725  									Type:        "object",
  1726  									Description: "my crd schema",
  1727  								},
  1728  							},
  1729  						},
  1730  					},
  1731  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  1732  						Plural:   crdPlural,
  1733  						Singular: crdPlural,
  1734  						Kind:     crdPlural,
  1735  						ListKind: "list" + crdPlural,
  1736  					},
  1737  					Scope: apiextensionsv1.NamespaceScoped,
  1738  				},
  1739  			}
  1740  
  1741  			By(`Generate permissions`)
  1742  			serviceAccountName := genName("nginx-sa")
  1743  			permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  1744  				{
  1745  					ServiceAccountName: serviceAccountName,
  1746  					Rules: []rbacv1.PolicyRule{
  1747  						{
  1748  							Verbs:     []string{rbac.VerbAll},
  1749  							APIGroups: []string{"cluster.com"},
  1750  							Resources: []string{crdPlural},
  1751  						},
  1752  					},
  1753  				},
  1754  			}
  1755  			By(`Generate permissions`)
  1756  			clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  1757  				{
  1758  					ServiceAccountName: serviceAccountName,
  1759  					Rules: []rbacv1.PolicyRule{
  1760  						{
  1761  							Verbs:     []string{rbac.VerbAll},
  1762  							APIGroups: []string{"cluster.com"},
  1763  							Resources: []string{crdPlural},
  1764  						},
  1765  					},
  1766  				},
  1767  			}
  1768  
  1769  			By(`Create the catalog sources`)
  1770  			mainNamedStrategy := newNginxInstallStrategy(genName("dep-"), permissions, clusterPermissions)
  1771  			mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, &mainNamedStrategy)
  1772  			mainCatalogName := genName("mock-ocs-amplify-")
  1773  			mainManifests := []registry.PackageManifest{
  1774  				{
  1775  					PackageName: mainPackageName,
  1776  					Channels: []registry.PackageChannel{
  1777  						{Name: stableChannel, CurrentCSVName: mainCSV.GetName()},
  1778  					},
  1779  					DefaultChannelName: stableChannel,
  1780  				},
  1781  			}
  1782  
  1783  			By(`Defer CRD clean up`)
  1784  			defer func() {
  1785  				Eventually(func() error {
  1786  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{}))
  1787  				}).Should(Succeed())
  1788  			}()
  1789  
  1790  			_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
  1791  			defer cleanupMainCatalogSource()
  1792  
  1793  			By(`Attempt to get the catalog source before creating install plan`)
  1794  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1795  			require.NoError(GinkgoT(), err)
  1796  
  1797  			subscriptionName := genName("sub-nginx-update-perms1")
  1798  			subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  1799  			defer subscriptionCleanup()
  1800  
  1801  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  1802  			require.NoError(GinkgoT(), err)
  1803  			require.NotNil(GinkgoT(), subscription)
  1804  			require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef)
  1805  			require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV)
  1806  
  1807  			installPlanName := subscription.Status.InstallPlanRef.Name
  1808  
  1809  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  1810  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  1811  			require.NoError(GinkgoT(), err)
  1812  
  1813  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  1814  
  1815  			By(`Verify CSV is created`)
  1816  			_, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker)
  1817  			require.NoError(GinkgoT(), err)
  1818  
  1819  			By(`Update CatalogSource with a new CSV with more permissions`)
  1820  			updatedPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  1821  				{
  1822  					ServiceAccountName: serviceAccountName,
  1823  					Rules: []rbacv1.PolicyRule{
  1824  						{
  1825  							Verbs:     []string{rbac.VerbAll},
  1826  							APIGroups: []string{"cluster.com"},
  1827  							Resources: []string{crdPlural},
  1828  						},
  1829  						{
  1830  							Verbs:     []string{rbac.VerbAll},
  1831  							APIGroups: []string{"local.cluster.com"},
  1832  							Resources: []string{"locals"},
  1833  						},
  1834  					},
  1835  				},
  1836  			}
  1837  			updatedClusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  1838  				{
  1839  					ServiceAccountName: serviceAccountName,
  1840  					Rules: []rbacv1.PolicyRule{
  1841  						{
  1842  							Verbs:     []string{rbac.VerbAll},
  1843  							APIGroups: []string{"cluster.com"},
  1844  							Resources: []string{crdPlural},
  1845  						},
  1846  						{
  1847  							Verbs:     []string{rbac.VerbAll},
  1848  							APIGroups: []string{"two.cluster.com"},
  1849  							Resources: []string{"twos"},
  1850  						},
  1851  					},
  1852  				},
  1853  			}
  1854  
  1855  			By(`Create the catalog sources`)
  1856  			updatedNamedStrategy := newNginxInstallStrategy(genName("dep-"), updatedPermissions, updatedClusterPermissions)
  1857  			updatedCSV := newCSV(mainPackageStable+"-next", generatedNamespace.GetName(), mainCSV.GetName(), semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, &updatedNamedStrategy)
  1858  			updatedManifests := []registry.PackageManifest{
  1859  				{
  1860  					PackageName: mainPackageName,
  1861  					Channels: []registry.PackageChannel{
  1862  						{Name: stableChannel, CurrentCSVName: updatedCSV.GetName()},
  1863  					},
  1864  					DefaultChannelName: stableChannel,
  1865  				},
  1866  			}
  1867  
  1868  			By(`Update catalog with updated CSV with more permissions`)
  1869  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests)
  1870  
  1871  			_, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName()))
  1872  			require.NoError(GinkgoT(), err)
  1873  
  1874  			updatedInstallPlanName := subscription.Status.InstallPlanRef.Name
  1875  
  1876  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  1877  			fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedInstallPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  1878  			require.NoError(GinkgoT(), err)
  1879  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedUpdatedInstallPlan.Status.Phase)
  1880  
  1881  			By(`Wait for csv to update`)
  1882  			_, err = fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker)
  1883  			require.NoError(GinkgoT(), err)
  1884  
  1885  			By(`If the CSV is succeeded, we successfully rolled out the RBAC changes`)
  1886  		})
  1887  
  1888  		It("AttenuatePermissions", func() {
  1889  
  1890  			defer func() {
  1891  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  1892  			}()
  1893  
  1894  			By(`Build initial catalog`)
  1895  			mainPackageName := genName("nginx-attenuate-")
  1896  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  1897  			stableChannel := "stable"
  1898  			crdPlural := genName("ins-attenuate-")
  1899  			crdName := crdPlural + ".cluster.com"
  1900  			mainCRD := apiextensionsv1.CustomResourceDefinition{
  1901  				ObjectMeta: metav1.ObjectMeta{
  1902  					Name: crdName,
  1903  				},
  1904  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  1905  					Group: "cluster.com",
  1906  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  1907  						{
  1908  							Name:    "v1alpha1",
  1909  							Served:  true,
  1910  							Storage: true,
  1911  							Schema: &apiextensionsv1.CustomResourceValidation{
  1912  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1913  									Type:        "object",
  1914  									Description: "my crd schema",
  1915  								},
  1916  							},
  1917  						},
  1918  					},
  1919  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  1920  						Plural:   crdPlural,
  1921  						Singular: crdPlural,
  1922  						Kind:     crdPlural,
  1923  						ListKind: "list" + crdPlural,
  1924  					},
  1925  					Scope: apiextensionsv1.NamespaceScoped,
  1926  				},
  1927  			}
  1928  
  1929  			By(`Generate permissions`)
  1930  			serviceAccountName := genName("nginx-sa")
  1931  			permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  1932  				{
  1933  					ServiceAccountName: serviceAccountName,
  1934  					Rules: []rbacv1.PolicyRule{
  1935  						{
  1936  							Verbs:     []string{rbac.VerbAll},
  1937  							APIGroups: []string{"cluster.com"},
  1938  							Resources: []string{crdPlural},
  1939  						},
  1940  						{
  1941  							Verbs:     []string{rbac.VerbAll},
  1942  							APIGroups: []string{"local.cluster.com"},
  1943  							Resources: []string{"locals"},
  1944  						},
  1945  					},
  1946  				},
  1947  			}
  1948  
  1949  			By(`Generate permissions`)
  1950  			clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  1951  				{
  1952  					ServiceAccountName: serviceAccountName,
  1953  					Rules: []rbacv1.PolicyRule{
  1954  						{
  1955  							Verbs:     []string{rbac.VerbAll},
  1956  							APIGroups: []string{"cluster.com"},
  1957  							Resources: []string{crdPlural},
  1958  						},
  1959  						{
  1960  							Verbs:     []string{rbac.VerbAll},
  1961  							APIGroups: []string{"two.cluster.com"},
  1962  							Resources: []string{"twos"},
  1963  						},
  1964  					},
  1965  				},
  1966  			}
  1967  
  1968  			By(`Create the catalog sources`)
  1969  			mainNamedStrategy := newNginxInstallStrategy(genName("dep-"), permissions, clusterPermissions)
  1970  			mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, &mainNamedStrategy)
  1971  			mainCatalogName := genName("mock-ocs-main-update-perms1-")
  1972  			mainManifests := []registry.PackageManifest{
  1973  				{
  1974  					PackageName: mainPackageName,
  1975  					Channels: []registry.PackageChannel{
  1976  						{Name: stableChannel, CurrentCSVName: mainCSV.GetName()},
  1977  					},
  1978  					DefaultChannelName: stableChannel,
  1979  				},
  1980  			}
  1981  
  1982  			By(`Defer CRD clean up`)
  1983  			defer func() {
  1984  				Eventually(func() error {
  1985  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{}))
  1986  				}).Should(Succeed())
  1987  			}()
  1988  
  1989  			_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
  1990  			defer cleanupMainCatalogSource()
  1991  
  1992  			By(`Attempt to get the catalog source before creating install plan`)
  1993  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  1994  			require.NoError(GinkgoT(), err)
  1995  
  1996  			subscriptionName := genName("sub-nginx-update-perms1")
  1997  			subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  1998  			defer subscriptionCleanup()
  1999  
  2000  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  2001  			require.NoError(GinkgoT(), err)
  2002  			require.NotNil(GinkgoT(), subscription)
  2003  			require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef)
  2004  			require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV)
  2005  
  2006  			installPlanName := subscription.Status.InstallPlanRef.Name
  2007  
  2008  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  2009  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2010  			require.NoError(GinkgoT(), err)
  2011  
  2012  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  2013  
  2014  			By(`Verify CSV is created`)
  2015  			_, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker)
  2016  			require.NoError(GinkgoT(), err)
  2017  
  2018  			By("Wait for ServiceAccount to have access")
  2019  			err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  2020  				res, err := c.KubernetesInterface().AuthorizationV1().SubjectAccessReviews().Create(context.Background(), &authorizationv1.SubjectAccessReview{
  2021  					Spec: authorizationv1.SubjectAccessReviewSpec{
  2022  						User: "system:serviceaccount:" + generatedNamespace.GetName() + ":" + serviceAccountName,
  2023  						ResourceAttributes: &authorizationv1.ResourceAttributes{
  2024  							Group:    "cluster.com",
  2025  							Version:  "v1alpha1",
  2026  							Resource: crdPlural,
  2027  							Verb:     rbac.VerbAll,
  2028  						},
  2029  					},
  2030  				}, metav1.CreateOptions{})
  2031  				if err != nil {
  2032  					return false, err
  2033  				}
  2034  				if res == nil {
  2035  					return false, nil
  2036  				}
  2037  				GinkgoT().Log("checking serviceaccount for permission")
  2038  
  2039  				By("should be allowed")
  2040  				return res.Status.Allowed, nil
  2041  			})
  2042  			Expect(err).NotTo(HaveOccurred())
  2043  
  2044  			By(`Update CatalogSource with a new CSV with fewer permissions`)
  2045  			updatedPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  2046  				{
  2047  					ServiceAccountName: serviceAccountName,
  2048  					Rules: []rbacv1.PolicyRule{
  2049  						{
  2050  							Verbs:     []string{rbac.VerbAll},
  2051  							APIGroups: []string{"local.cluster.com"},
  2052  							Resources: []string{"locals"},
  2053  						},
  2054  					},
  2055  				},
  2056  			}
  2057  			updatedClusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  2058  				{
  2059  					ServiceAccountName: serviceAccountName,
  2060  					Rules: []rbacv1.PolicyRule{
  2061  						{
  2062  							Verbs:     []string{rbac.VerbAll},
  2063  							APIGroups: []string{"two.cluster.com"},
  2064  							Resources: []string{"twos"},
  2065  						},
  2066  					},
  2067  				},
  2068  			}
  2069  
  2070  			By(`Create the catalog sources`)
  2071  			updatedNamedStrategy := newNginxInstallStrategy(genName("dep-"), updatedPermissions, updatedClusterPermissions)
  2072  			updatedCSV := newCSV(mainPackageStable+"-next", generatedNamespace.GetName(), mainCSV.GetName(), semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, &updatedNamedStrategy)
  2073  			updatedManifests := []registry.PackageManifest{
  2074  				{
  2075  					PackageName: mainPackageName,
  2076  					Channels: []registry.PackageChannel{
  2077  						{Name: stableChannel, CurrentCSVName: updatedCSV.GetName()},
  2078  					},
  2079  					DefaultChannelName: stableChannel,
  2080  				},
  2081  			}
  2082  
  2083  			By(`Update catalog with updated CSV with more permissions`)
  2084  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests)
  2085  
  2086  			By(`Wait for subscription to update its status`)
  2087  			_, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName()))
  2088  			require.NoError(GinkgoT(), err)
  2089  
  2090  			updatedInstallPlanName := subscription.Status.InstallPlanRef.Name
  2091  
  2092  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  2093  			fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedInstallPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2094  			require.NoError(GinkgoT(), err)
  2095  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedUpdatedInstallPlan.Status.Phase)
  2096  
  2097  			By(`Wait for csv to update`)
  2098  			_, err = fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker)
  2099  			require.NoError(GinkgoT(), err)
  2100  
  2101  			By(`Wait for ServiceAccount to not have access anymore`)
  2102  			err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  2103  				res, err := c.KubernetesInterface().AuthorizationV1().SubjectAccessReviews().Create(context.Background(), &authorizationv1.SubjectAccessReview{
  2104  					Spec: authorizationv1.SubjectAccessReviewSpec{
  2105  						User: "system:serviceaccount:" + generatedNamespace.GetName() + ":" + serviceAccountName,
  2106  						ResourceAttributes: &authorizationv1.ResourceAttributes{
  2107  							Group:    "cluster.com",
  2108  							Version:  "v1alpha1",
  2109  							Resource: crdPlural,
  2110  							Verb:     rbac.VerbAll,
  2111  						},
  2112  					},
  2113  				}, metav1.CreateOptions{})
  2114  				if err != nil {
  2115  					return false, err
  2116  				}
  2117  				if res == nil {
  2118  					return false, nil
  2119  				}
  2120  				GinkgoT().Log("checking serviceaccount for permission")
  2121  
  2122  				By(`should not be allowed`)
  2123  				return !res.Status.Allowed, nil
  2124  			})
  2125  		})
  2126  
  2127  		It("StopOnCSVModifications", func() {
  2128  
  2129  			defer func() {
  2130  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  2131  			}()
  2132  
  2133  			By(`Build initial catalog`)
  2134  			mainPackageName := genName("nginx-amplify-")
  2135  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  2136  			stableChannel := "stable"
  2137  			crdPlural := genName("ins-amplify-")
  2138  			crdName := crdPlural + ".cluster.com"
  2139  			mainCRD := apiextensionsv1.CustomResourceDefinition{
  2140  				ObjectMeta: metav1.ObjectMeta{
  2141  					Name: crdName,
  2142  				},
  2143  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  2144  					Group: "cluster.com",
  2145  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  2146  						{
  2147  							Name:    "v1alpha1",
  2148  							Served:  true,
  2149  							Storage: true,
  2150  							Schema: &apiextensionsv1.CustomResourceValidation{
  2151  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2152  									Type:        "object",
  2153  									Description: "my crd schema",
  2154  								},
  2155  							},
  2156  						},
  2157  					},
  2158  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  2159  						Plural:   crdPlural,
  2160  						Singular: crdPlural,
  2161  						Kind:     crdPlural,
  2162  						ListKind: "list" + crdPlural,
  2163  					},
  2164  					Scope: apiextensionsv1.NamespaceScoped,
  2165  				},
  2166  			}
  2167  
  2168  			By(`Generate permissions`)
  2169  			serviceAccountName := genName("nginx-sa")
  2170  			permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  2171  				{
  2172  					ServiceAccountName: serviceAccountName,
  2173  					Rules: []rbacv1.PolicyRule{
  2174  						{
  2175  							Verbs:     []string{rbac.VerbAll},
  2176  							APIGroups: []string{"cluster.com"},
  2177  							Resources: []string{crdPlural},
  2178  						},
  2179  					},
  2180  				},
  2181  			}
  2182  
  2183  			By(`Generate permissions`)
  2184  			clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  2185  				{
  2186  					ServiceAccountName: serviceAccountName,
  2187  					Rules: []rbacv1.PolicyRule{
  2188  						{
  2189  							Verbs:     []string{rbac.VerbAll},
  2190  							APIGroups: []string{"cluster.com"},
  2191  							Resources: []string{crdPlural},
  2192  						},
  2193  					},
  2194  				},
  2195  			}
  2196  
  2197  			By(`Create the catalog sources`)
  2198  			deploymentName := genName("dep-")
  2199  			mainNamedStrategy := newNginxInstallStrategy(deploymentName, permissions, clusterPermissions)
  2200  			mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, &mainNamedStrategy)
  2201  			mainCatalogName := genName("mock-ocs-stomper-")
  2202  			mainManifests := []registry.PackageManifest{
  2203  				{
  2204  					PackageName: mainPackageName,
  2205  					Channels: []registry.PackageChannel{
  2206  						{Name: stableChannel, CurrentCSVName: mainCSV.GetName()},
  2207  					},
  2208  					DefaultChannelName: stableChannel,
  2209  				},
  2210  			}
  2211  
  2212  			By(`Defer CRD clean up`)
  2213  			defer func() {
  2214  				Eventually(func() error {
  2215  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{}))
  2216  				}).Should(Succeed())
  2217  			}()
  2218  
  2219  			_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
  2220  			defer cleanupMainCatalogSource()
  2221  
  2222  			By(`Attempt to get the catalog source before creating install plan`)
  2223  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  2224  			require.NoError(GinkgoT(), err)
  2225  
  2226  			subscriptionName := genName("sub-nginx-stompy-")
  2227  			subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  2228  			defer subscriptionCleanup()
  2229  
  2230  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  2231  			require.NoError(GinkgoT(), err)
  2232  			require.NotNil(GinkgoT(), subscription)
  2233  			require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef)
  2234  			require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV)
  2235  
  2236  			installPlanName := subscription.Status.InstallPlanRef.Name
  2237  
  2238  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  2239  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2240  			require.NoError(GinkgoT(), err)
  2241  
  2242  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  2243  
  2244  			By(`Verify CSV is created`)
  2245  			csv, err := fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker)
  2246  			require.NoError(GinkgoT(), err)
  2247  
  2248  			addedEnvVar := corev1.EnvVar{Name: "EXAMPLE", Value: "value"}
  2249  			modifiedDetails := operatorsv1alpha1.StrategyDetailsDeployment{
  2250  				DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{
  2251  					{
  2252  						Name: deploymentName,
  2253  						Spec: appsv1.DeploymentSpec{
  2254  							Selector: &metav1.LabelSelector{
  2255  								MatchLabels: map[string]string{"app": "nginx"},
  2256  							},
  2257  							Replicas: &singleInstance,
  2258  							Template: corev1.PodTemplateSpec{
  2259  								ObjectMeta: metav1.ObjectMeta{
  2260  									Labels: map[string]string{"app": "nginx"},
  2261  								},
  2262  								Spec: corev1.PodSpec{Containers: []corev1.Container{
  2263  									{
  2264  										Name:            genName("nginx"),
  2265  										Image:           *dummyImage,
  2266  										Ports:           []corev1.ContainerPort{{ContainerPort: 80}},
  2267  										ImagePullPolicy: corev1.PullIfNotPresent,
  2268  										Env:             []corev1.EnvVar{addedEnvVar},
  2269  									},
  2270  								}},
  2271  							},
  2272  						},
  2273  					},
  2274  				},
  2275  				Permissions:        permissions,
  2276  				ClusterPermissions: clusterPermissions,
  2277  			}
  2278  
  2279  			// wrapping the csv update in an eventually helps eliminate a flake in this test
  2280  			// it can happen that the csv changes in the meantime (e.g. reconciler adds a condition)
  2281  			// and the update fails with a conflict
  2282  			Eventually(func() error {
  2283  				csv, err := crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.Background(), csv.GetName(), metav1.GetOptions{})
  2284  				if err != nil {
  2285  					return nil
  2286  				}
  2287  
  2288  				// update spec
  2289  				csv.Spec.InstallStrategy = operatorsv1alpha1.NamedInstallStrategy{
  2290  					StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment,
  2291  					StrategySpec: modifiedDetails,
  2292  				}
  2293  
  2294  				// update csv
  2295  				_, err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Update(context.Background(), csv, metav1.UpdateOptions{})
  2296  				return err
  2297  			}).Should(Succeed())
  2298  
  2299  			By(`Wait for csv to update`)
  2300  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), csvSucceededChecker)
  2301  			require.NoError(GinkgoT(), err)
  2302  
  2303  			By(`Should have the updated env var`)
  2304  			err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  2305  				dep, err := c.GetDeployment(generatedNamespace.GetName(), deploymentName)
  2306  				if err != nil {
  2307  					return false, nil
  2308  				}
  2309  				if len(dep.Spec.Template.Spec.Containers[0].Env) == 0 {
  2310  					return false, nil
  2311  				}
  2312  				for _, envVar := range dep.Spec.Template.Spec.Containers[0].Env {
  2313  					if envVar == addedEnvVar {
  2314  						return true, nil
  2315  					}
  2316  				}
  2317  				return false, nil
  2318  			})
  2319  			require.NoError(GinkgoT(), err)
  2320  
  2321  			By(`Create the catalog sources`)
  2322  			By(`Updated csv has the same deployment strategy as main`)
  2323  			updatedCSV := newCSV(mainPackageStable+"-next", generatedNamespace.GetName(), mainCSV.GetName(), semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, &mainNamedStrategy)
  2324  			updatedManifests := []registry.PackageManifest{
  2325  				{
  2326  					PackageName: mainPackageName,
  2327  					Channels: []registry.PackageChannel{
  2328  						{Name: stableChannel, CurrentCSVName: updatedCSV.GetName()},
  2329  					},
  2330  					DefaultChannelName: stableChannel,
  2331  				},
  2332  			}
  2333  
  2334  			By(`Update catalog with updated CSV with more permissions`)
  2335  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests)
  2336  
  2337  			_, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName()))
  2338  			require.NoError(GinkgoT(), err)
  2339  
  2340  			updatedInstallPlanName := subscription.Status.InstallPlanRef.Name
  2341  
  2342  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  2343  			fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedInstallPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2344  			require.NoError(GinkgoT(), err)
  2345  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedUpdatedInstallPlan.Status.Phase)
  2346  
  2347  			By(`Wait for csv to update`)
  2348  			_, err = fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker)
  2349  			require.NoError(GinkgoT(), err)
  2350  
  2351  			By(`Should have created deployment and stomped on the env changes`)
  2352  			updatedDep, err := c.GetDeployment(generatedNamespace.GetName(), deploymentName)
  2353  			require.NoError(GinkgoT(), err)
  2354  			require.NotNil(GinkgoT(), updatedDep)
  2355  
  2356  			By(`Should have the updated env var`)
  2357  			for _, envVar := range updatedDep.Spec.Template.Spec.Containers[0].Env {
  2358  				require.False(GinkgoT(), envVar == addedEnvVar)
  2359  			}
  2360  		})
  2361  
  2362  		It("UpdateSingleExistingCRDOwner", func() {
  2363  
  2364  			mainPackageName := genName("nginx-update-")
  2365  
  2366  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  2367  			mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName)
  2368  
  2369  			stableChannel := "stable"
  2370  
  2371  			crdPlural := genName("ins-update-")
  2372  			crdName := crdPlural + ".cluster.com"
  2373  			mainCRD := apiextensionsv1.CustomResourceDefinition{
  2374  				ObjectMeta: metav1.ObjectMeta{
  2375  					Name: crdName,
  2376  				},
  2377  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  2378  					Group: "cluster.com",
  2379  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  2380  						{
  2381  							Name:    "v1alpha1",
  2382  							Served:  true,
  2383  							Storage: true,
  2384  							Schema: &apiextensionsv1.CustomResourceValidation{
  2385  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2386  									Type:        "object",
  2387  									Description: "my crd schema",
  2388  								},
  2389  							},
  2390  						},
  2391  					},
  2392  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  2393  						Plural:   crdPlural,
  2394  						Singular: crdPlural,
  2395  						Kind:     crdPlural,
  2396  						ListKind: "list" + crdPlural,
  2397  					},
  2398  					Scope: apiextensionsv1.NamespaceScoped,
  2399  				},
  2400  			}
  2401  
  2402  			updatedCRD := apiextensionsv1.CustomResourceDefinition{
  2403  				ObjectMeta: metav1.ObjectMeta{
  2404  					Name: crdName,
  2405  				},
  2406  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  2407  					Group: "cluster.com",
  2408  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  2409  						{
  2410  							Name:    "v1alpha1",
  2411  							Served:  true,
  2412  							Storage: true,
  2413  							Schema: &apiextensionsv1.CustomResourceValidation{
  2414  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2415  									Type:        "object",
  2416  									Description: "my crd schema",
  2417  								},
  2418  							},
  2419  						},
  2420  						{
  2421  							Name:    "v1alpha2",
  2422  							Served:  true,
  2423  							Storage: false,
  2424  							Schema: &apiextensionsv1.CustomResourceValidation{
  2425  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2426  									Type:        "object",
  2427  									Description: "my crd schema",
  2428  								},
  2429  							},
  2430  						},
  2431  					},
  2432  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  2433  						Plural:   crdPlural,
  2434  						Singular: crdPlural,
  2435  						Kind:     crdPlural,
  2436  						ListKind: "list" + crdPlural,
  2437  					},
  2438  					Scope: apiextensionsv1.NamespaceScoped,
  2439  				},
  2440  			}
  2441  
  2442  			mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, nil)
  2443  			betaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{updatedCRD}, nil, nil)
  2444  
  2445  			By(`Defer CRD clean up`)
  2446  			defer func() {
  2447  				Eventually(func() error {
  2448  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{}))
  2449  				}).Should(Succeed())
  2450  				Eventually(func() error {
  2451  					return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), updatedCRD.GetName(), metav1.DeleteOptions{}))
  2452  				}).Should(Succeed())
  2453  			}()
  2454  
  2455  			defer func() {
  2456  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  2457  			}()
  2458  
  2459  			mainCatalogName := genName("mock-ocs-main-update-")
  2460  
  2461  			By(`Create separate manifests for each CatalogSource`)
  2462  			mainManifests := []registry.PackageManifest{
  2463  				{
  2464  					PackageName: mainPackageName,
  2465  					Channels: []registry.PackageChannel{
  2466  						{Name: stableChannel, CurrentCSVName: mainPackageStable},
  2467  					},
  2468  					DefaultChannelName: stableChannel,
  2469  				},
  2470  			}
  2471  
  2472  			By(`Create the catalog sources`)
  2473  			_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
  2474  			defer cleanupMainCatalogSource()
  2475  
  2476  			By(`Attempt to get the catalog source before creating install plan`)
  2477  			_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  2478  			require.NoError(GinkgoT(), err)
  2479  
  2480  			subscriptionName := genName("sub-nginx-update-")
  2481  			createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  2482  
  2483  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  2484  			require.NoError(GinkgoT(), err)
  2485  			require.NotNil(GinkgoT(), subscription)
  2486  			require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef)
  2487  			require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV)
  2488  
  2489  			installPlanName := subscription.Status.InstallPlanRef.Name
  2490  
  2491  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  2492  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2493  			require.NoError(GinkgoT(), err)
  2494  
  2495  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  2496  
  2497  			By(`Fetch installplan again to check for unnecessary control loops`)
  2498  			fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool {
  2499  				Expect(equality.Semantic.DeepEqual(fetchedInstallPlan, fip)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip))
  2500  				return true
  2501  			})
  2502  			require.NoError(GinkgoT(), err)
  2503  
  2504  			By(`Verify CSV is created`)
  2505  			_, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvAnyChecker)
  2506  			require.NoError(GinkgoT(), err)
  2507  
  2508  			mainManifests = []registry.PackageManifest{
  2509  				{
  2510  					PackageName: mainPackageName,
  2511  					Channels: []registry.PackageChannel{
  2512  						{Name: stableChannel, CurrentCSVName: mainPackageBeta},
  2513  					},
  2514  					DefaultChannelName: stableChannel,
  2515  				},
  2516  			}
  2517  
  2518  			updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{updatedCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, betaCSV}, mainManifests)
  2519  			By(`Wait for subscription to update`)
  2520  			updatedSubscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName()))
  2521  			require.NoError(GinkgoT(), err)
  2522  
  2523  			By(`Verify installplan created and installed`)
  2524  			fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedSubscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2525  			require.NoError(GinkgoT(), err)
  2526  			require.NotEqual(GinkgoT(), fetchedInstallPlan.GetName(), fetchedUpdatedInstallPlan.GetName())
  2527  
  2528  			By(`Wait for csv to update`)
  2529  			_, err = fetchCSV(crc, generatedNamespace.GetName(), betaCSV.GetName(), csvAnyChecker)
  2530  			require.NoError(GinkgoT(), err)
  2531  
  2532  			By(`Get the CRD to see if it is updated`)
  2533  			fetchedCRD, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), crdName, metav1.GetOptions{})
  2534  			require.NoError(GinkgoT(), err)
  2535  			require.Equal(GinkgoT(), len(fetchedCRD.Spec.Versions), len(updatedCRD.Spec.Versions), "The CRD versions counts don't match")
  2536  
  2537  			fetchedCRDVersions := map[crdVersionKey]struct{}{}
  2538  			for _, version := range fetchedCRD.Spec.Versions {
  2539  				key := crdVersionKey{
  2540  					name:    version.Name,
  2541  					served:  version.Served,
  2542  					storage: version.Storage,
  2543  				}
  2544  				fetchedCRDVersions[key] = struct{}{}
  2545  			}
  2546  
  2547  			for _, version := range updatedCRD.Spec.Versions {
  2548  				key := crdVersionKey{
  2549  					name:    version.Name,
  2550  					served:  version.Served,
  2551  					storage: version.Storage,
  2552  				}
  2553  				_, ok := fetchedCRDVersions[key]
  2554  				require.True(GinkgoT(), ok, "couldn't find %v in fetched CRD versions: %#v", key, fetchedCRDVersions)
  2555  			}
  2556  		})
  2557  
  2558  		It("UpdatePreexistingCRDFailed", func() {
  2559  
  2560  			defer func() {
  2561  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  2562  			}()
  2563  
  2564  			mainPackageName := genName("nginx-update2-")
  2565  
  2566  			mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  2567  
  2568  			stableChannel := "stable"
  2569  
  2570  			crdPlural := genName("ins-update2-")
  2571  			crdName := crdPlural + ".cluster.com"
  2572  			mainCRD := apiextensionsv1.CustomResourceDefinition{
  2573  				ObjectMeta: metav1.ObjectMeta{
  2574  					Name: crdName,
  2575  				},
  2576  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  2577  					Group: "cluster.com",
  2578  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  2579  						{
  2580  							Name:    "v1alpha1",
  2581  							Served:  true,
  2582  							Storage: true,
  2583  							Schema: &apiextensionsv1.CustomResourceValidation{
  2584  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2585  									Type:        "object",
  2586  									Description: "my crd schema",
  2587  								},
  2588  							},
  2589  						},
  2590  					},
  2591  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  2592  						Plural:   crdPlural,
  2593  						Singular: crdPlural,
  2594  						Kind:     crdPlural,
  2595  						ListKind: "list" + crdPlural,
  2596  					},
  2597  					Scope: apiextensionsv1.NamespaceScoped,
  2598  				},
  2599  			}
  2600  
  2601  			updatedCRD := apiextensionsv1.CustomResourceDefinition{
  2602  				ObjectMeta: metav1.ObjectMeta{
  2603  					Name: crdName,
  2604  				},
  2605  				Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  2606  					Group: "cluster.com",
  2607  					Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  2608  						{
  2609  							Name:    "v1alpha1",
  2610  							Served:  true,
  2611  							Storage: true,
  2612  							Schema: &apiextensionsv1.CustomResourceValidation{
  2613  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2614  									Type:        "object",
  2615  									Description: "my crd schema",
  2616  								},
  2617  							},
  2618  						},
  2619  						{
  2620  							Name:    "v1alpha2",
  2621  							Served:  true,
  2622  							Storage: false,
  2623  							Schema: &apiextensionsv1.CustomResourceValidation{
  2624  								OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2625  									Type:        "object",
  2626  									Description: "my crd schema",
  2627  								},
  2628  							},
  2629  						},
  2630  					},
  2631  					Names: apiextensionsv1.CustomResourceDefinitionNames{
  2632  						Plural:   crdPlural,
  2633  						Singular: crdPlural,
  2634  						Kind:     crdPlural,
  2635  						ListKind: "list" + crdPlural,
  2636  					},
  2637  					Scope: apiextensionsv1.NamespaceScoped,
  2638  				},
  2639  			}
  2640  
  2641  			expectedCRDVersions := map[crdVersionKey]struct{}{}
  2642  			for _, version := range mainCRD.Spec.Versions {
  2643  				key := crdVersionKey{
  2644  					name:    version.Name,
  2645  					served:  version.Served,
  2646  					storage: version.Storage,
  2647  				}
  2648  				expectedCRDVersions[key] = struct{}{}
  2649  			}
  2650  
  2651  			By(`Create the initial CSV`)
  2652  			cleanupCRD, err := createCRD(c, mainCRD)
  2653  			require.NoError(GinkgoT(), err)
  2654  			defer cleanupCRD()
  2655  
  2656  			mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil)
  2657  
  2658  			mainCatalogName := genName("mock-ocs-main-update2-")
  2659  
  2660  			By(`Create separate manifests for each CatalogSource`)
  2661  			mainManifests := []registry.PackageManifest{
  2662  				{
  2663  					PackageName: mainPackageName,
  2664  					Channels: []registry.PackageChannel{
  2665  						{Name: stableChannel, CurrentCSVName: mainPackageStable},
  2666  					},
  2667  					DefaultChannelName: stableChannel,
  2668  				},
  2669  			}
  2670  
  2671  			By(`Create the catalog sources`)
  2672  			_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{updatedCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
  2673  			defer cleanupMainCatalogSource()
  2674  
  2675  			By(`Attempt to get the catalog source before creating install plan`)
  2676  			_, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  2677  			require.NoError(GinkgoT(), err)
  2678  
  2679  			subscriptionName := genName("sub-nginx-update2-")
  2680  			subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  2681  			defer subscriptionCleanup()
  2682  
  2683  			subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  2684  			require.NoError(GinkgoT(), err)
  2685  			require.NotNil(GinkgoT(), subscription)
  2686  			require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef)
  2687  			require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV)
  2688  
  2689  			installPlanName := subscription.Status.InstallPlanRef.Name
  2690  
  2691  			By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  2692  			fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  2693  			require.NoError(GinkgoT(), err)
  2694  
  2695  			require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  2696  
  2697  			By(`Fetch installplan again to check for unnecessary control loops`)
  2698  			fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool {
  2699  				Expect(equality.Semantic.DeepEqual(fetchedInstallPlan, fip)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip))
  2700  				return true
  2701  			})
  2702  			require.NoError(GinkgoT(), err)
  2703  
  2704  			By(`Verify CSV is created`)
  2705  			_, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvAnyChecker)
  2706  			require.NoError(GinkgoT(), err)
  2707  
  2708  			By(`Get the CRD to see if it is updated`)
  2709  			fetchedCRD, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), crdName, metav1.GetOptions{})
  2710  			require.NoError(GinkgoT(), err)
  2711  			require.Equal(GinkgoT(), len(fetchedCRD.Spec.Versions), len(mainCRD.Spec.Versions), "The CRD versions counts don't match")
  2712  
  2713  			fetchedCRDVersions := map[crdVersionKey]struct{}{}
  2714  			for _, version := range fetchedCRD.Spec.Versions {
  2715  				key := crdVersionKey{
  2716  					name:    version.Name,
  2717  					served:  version.Served,
  2718  					storage: version.Storage,
  2719  				}
  2720  				fetchedCRDVersions[key] = struct{}{}
  2721  			}
  2722  
  2723  			for _, version := range mainCRD.Spec.Versions {
  2724  				key := crdVersionKey{
  2725  					name:    version.Name,
  2726  					served:  version.Served,
  2727  					storage: version.Storage,
  2728  				}
  2729  				_, ok := fetchedCRDVersions[key]
  2730  				require.True(GinkgoT(), ok, "couldn't find %v in fetched CRD versions: %#v", key, fetchedCRDVersions)
  2731  			}
  2732  		})
  2733  	})
  2734  
  2735  	It("creation with permissions", func() {
  2736  		By(`This It spec creates an InstallPlan with a CSV containing a set of permissions to be resolved.`)
  2737  
  2738  		packageName := genName("nginx")
  2739  		stableChannel := "stable"
  2740  		stableCSVName := packageName + "-stable"
  2741  
  2742  		By("Create manifests")
  2743  		manifests := []registry.PackageManifest{
  2744  			{
  2745  				PackageName: packageName,
  2746  				Channels: []registry.PackageChannel{
  2747  					{
  2748  						Name:           stableChannel,
  2749  						CurrentCSVName: stableCSVName,
  2750  					},
  2751  				},
  2752  				DefaultChannelName: stableChannel,
  2753  			},
  2754  		}
  2755  
  2756  		By("Create new CRDs")
  2757  		crdPlural := genName("ins")
  2758  		crd := newCRD(crdPlural)
  2759  
  2760  		By("Defer CRD clean up")
  2761  		defer func() {
  2762  			Eventually(func() error {
  2763  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}))
  2764  			}).Should(Succeed())
  2765  		}()
  2766  
  2767  		By("Generate permissions")
  2768  		By(`Permissions must be different than ClusterPermissions defined below if OLM is going to lift role/rolebindings to cluster level.`)
  2769  		serviceAccountName := genName("nginx-sa")
  2770  		permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  2771  			{
  2772  				ServiceAccountName: serviceAccountName,
  2773  				Rules: []rbacv1.PolicyRule{
  2774  					{
  2775  						Verbs:     []string{rbac.VerbAll},
  2776  						APIGroups: []string{"cluster.com"},
  2777  						Resources: []string{crdPlural},
  2778  					},
  2779  					{
  2780  						Verbs:     []string{rbac.VerbAll},
  2781  						APIGroups: []string{corev1.GroupName},
  2782  						Resources: []string{corev1.ResourceConfigMaps.String()},
  2783  					},
  2784  				},
  2785  			},
  2786  		}
  2787  
  2788  		By("Generate permissions")
  2789  		clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{
  2790  			{
  2791  				ServiceAccountName: serviceAccountName,
  2792  				Rules: []rbacv1.PolicyRule{
  2793  					{
  2794  						Verbs:     []string{rbac.VerbAll},
  2795  						APIGroups: []string{"cluster.com"},
  2796  						Resources: []string{crdPlural},
  2797  					},
  2798  				},
  2799  			},
  2800  		}
  2801  
  2802  		By("Create a new NamedInstallStrategy")
  2803  		namedStrategy := newNginxInstallStrategy(genName("dep-"), permissions, clusterPermissions)
  2804  
  2805  		By("Create new CSVs")
  2806  		stableCSV := newCSV(stableCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, &namedStrategy)
  2807  
  2808  		defer func() {
  2809  			require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  2810  		}()
  2811  
  2812  		By("Create CatalogSource")
  2813  		mainCatalogSourceName := genName("nginx-catalog")
  2814  		_, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{stableCSV})
  2815  		defer cleanupCatalogSource()
  2816  
  2817  		By("Attempt to get CatalogSource")
  2818  		_, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  2819  		require.NoError(GinkgoT(), err)
  2820  
  2821  		By("Creating a Subscription")
  2822  		subscriptionName := genName("sub-nginx-")
  2823  		// Subscription is explitly deleted as part of the test to avoid CSV being recreated,
  2824  		// so ignore cleanup function
  2825  		_ = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, packageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  2826  
  2827  		By("Attempt to get Subscription")
  2828  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  2829  		require.NoError(GinkgoT(), err)
  2830  		require.NotNil(GinkgoT(), subscription)
  2831  
  2832  		installPlanName := subscription.Status.InstallPlanRef.Name
  2833  
  2834  		By("Attempt to get InstallPlan")
  2835  		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete))
  2836  		require.NoError(GinkgoT(), err)
  2837  		require.NotEqual(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseFailed, fetchedInstallPlan.Status.Phase, "InstallPlan failed")
  2838  
  2839  		By("Expect correct RBAC resources to be resolved and created")
  2840  		expectedSteps := map[registry.ResourceKey]struct{}{
  2841  			{Name: crd.Name, Kind: "CustomResourceDefinition"}:   {},
  2842  			{Name: stableCSVName, Kind: "ClusterServiceVersion"}: {},
  2843  			{Name: serviceAccountName, Kind: "ServiceAccount"}:   {},
  2844  			{Name: stableCSVName, Kind: "Role"}:                  {},
  2845  			{Name: stableCSVName, Kind: "RoleBinding"}:           {},
  2846  			{Name: stableCSVName, Kind: "ClusterRole"}:           {},
  2847  			{Name: stableCSVName, Kind: "ClusterRoleBinding"}:    {},
  2848  		}
  2849  
  2850  		require.Equal(GinkgoT(), len(expectedSteps), len(fetchedInstallPlan.Status.Plan), "number of expected steps does not match installed")
  2851  
  2852  		for _, step := range fetchedInstallPlan.Status.Plan {
  2853  			key := registry.ResourceKey{
  2854  				Name: step.Resource.Name,
  2855  				Kind: step.Resource.Kind,
  2856  			}
  2857  			for expected := range expectedSteps {
  2858  				if expected == key {
  2859  					delete(expectedSteps, expected)
  2860  				} else if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind {
  2861  					delete(expectedSteps, expected)
  2862  				} else {
  2863  					GinkgoT().Logf("Found unexpected step %#v, expected %#v: name has prefix: %v kinds match %v", key, expected, strings.HasPrefix(key.Name, expected.Name), key.Kind == expected.Kind)
  2864  				}
  2865  			}
  2866  
  2867  			By("This operator was installed into a global operator group, so the roles should have been lifted to clusterroles")
  2868  			if step.Resource.Kind == "Role" {
  2869  				err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  2870  					_, err = c.GetClusterRole(step.Resource.Name)
  2871  					if err != nil {
  2872  						if apierrors.IsNotFound(err) {
  2873  							return false, nil
  2874  						}
  2875  						return false, err
  2876  					}
  2877  					return true, nil
  2878  				})
  2879  				require.NoError(GinkgoT(), err)
  2880  			}
  2881  			if step.Resource.Kind == "RoleBinding" {
  2882  				err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  2883  					_, err = c.GetClusterRoleBinding(step.Resource.Name)
  2884  					if err != nil {
  2885  						if apierrors.IsNotFound(err) {
  2886  							return false, nil
  2887  						}
  2888  						return false, err
  2889  					}
  2890  					return true, nil
  2891  				})
  2892  				require.NoError(GinkgoT(), err)
  2893  			}
  2894  		}
  2895  
  2896  		By("Should have removed every matching step")
  2897  		require.Equal(GinkgoT(), 0, len(expectedSteps), "Actual resource steps do not match expected: %#v", expectedSteps)
  2898  
  2899  		By(fmt.Sprintf("Explicitly deleting subscription %s/%s", generatedNamespace.GetName(), subscriptionName))
  2900  		err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Delete(context.Background(), subscriptionName, metav1.DeleteOptions{})
  2901  		By("Looking for no error OR IsNotFound error")
  2902  		require.NoError(GinkgoT(), client.IgnoreNotFound(err))
  2903  
  2904  		By("Waiting for the Subscription to delete")
  2905  		err = waitForSubscriptionToDelete(generatedNamespace.GetName(), subscriptionName, crc)
  2906  		require.NoError(GinkgoT(), client.IgnoreNotFound(err))
  2907  
  2908  		By(fmt.Sprintf("Explicitly deleting csv %s/%s", generatedNamespace.GetName(), stableCSVName))
  2909  		err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.Background(), stableCSVName, metav1.DeleteOptions{})
  2910  		By("Looking for no error OR IsNotFound error")
  2911  		require.NoError(GinkgoT(), client.IgnoreNotFound(err))
  2912  		By("Waiting for the CSV to delete")
  2913  		err = waitForCsvToDelete(generatedNamespace.GetName(), stableCSVName, crc)
  2914  		require.NoError(GinkgoT(), client.IgnoreNotFound(err))
  2915  
  2916  		nCrs := 0
  2917  		nCrbs := 0
  2918  		By("Waiting for CRBs and CRs and SAs to delete")
  2919  		Eventually(func() bool {
  2920  
  2921  			crbs, err := c.KubernetesInterface().RbacV1().ClusterRoleBindings().List(context.Background(), metav1.ListOptions{LabelSelector: fmt.Sprintf("%v=%v", ownerutil.OwnerKey, stableCSVName)})
  2922  			if err != nil {
  2923  				GinkgoT().Logf("error getting crbs: %v", err)
  2924  				return false
  2925  			}
  2926  			if n := len(crbs.Items); n != 0 {
  2927  				if n != nCrbs {
  2928  					GinkgoT().Logf("CRBs remaining:  %v", n)
  2929  					nCrbs = n
  2930  				}
  2931  				return false
  2932  			}
  2933  
  2934  			crs, err := c.KubernetesInterface().RbacV1().ClusterRoles().List(context.Background(), metav1.ListOptions{LabelSelector: fmt.Sprintf("%v=%v", ownerutil.OwnerKey, stableCSVName)})
  2935  			if err != nil {
  2936  				GinkgoT().Logf("error getting crs: %v", err)
  2937  				return false
  2938  			}
  2939  			if n := len(crs.Items); n != 0 {
  2940  				if n != nCrs {
  2941  					GinkgoT().Logf("CRs remaining: %v", n)
  2942  					nCrs = n
  2943  				}
  2944  				return false
  2945  			}
  2946  
  2947  			_, err = c.KubernetesInterface().CoreV1().ServiceAccounts(generatedNamespace.GetName()).Get(context.Background(), serviceAccountName, metav1.GetOptions{})
  2948  			if client.IgnoreNotFound(err) != nil {
  2949  				GinkgoT().Logf("error getting sa %s/%s: %v", generatedNamespace.GetName(), serviceAccountName, err)
  2950  				return false
  2951  			}
  2952  
  2953  			return true
  2954  		}, pollDuration, pollInterval).Should(BeTrue())
  2955  		By("Cleaning up the test")
  2956  	})
  2957  
  2958  	It("CRD validation", func() {
  2959  		By(`Tests if CRD validation works with the "minimum" property after being`)
  2960  		By(`pulled from a CatalogSource's operator-registry.`)
  2961  
  2962  		crdPlural := genName("ins")
  2963  		crdName := crdPlural + ".cluster.com"
  2964  		var min float64 = 2
  2965  		var max float64 = 256
  2966  
  2967  		By(`Create CRD with offending property`)
  2968  		crd := apiextensionsv1.CustomResourceDefinition{
  2969  			ObjectMeta: metav1.ObjectMeta{
  2970  				Name: crdName,
  2971  			},
  2972  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  2973  				Group: "cluster.com",
  2974  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  2975  					{
  2976  						Name:    "v1alpha1",
  2977  						Served:  true,
  2978  						Storage: true,
  2979  						Schema: &apiextensionsv1.CustomResourceValidation{
  2980  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  2981  								Type: "object",
  2982  								Properties: map[string]apiextensionsv1.JSONSchemaProps{
  2983  									"spec": {
  2984  										Type:        "object",
  2985  										Description: "Spec of a test object.",
  2986  										Properties: map[string]apiextensionsv1.JSONSchemaProps{
  2987  											"scalar": {
  2988  												Type:        "number",
  2989  												Description: "Scalar value that should have a min and max.",
  2990  												Minimum:     &min,
  2991  												Maximum:     &max,
  2992  											},
  2993  										},
  2994  									},
  2995  								},
  2996  							},
  2997  						},
  2998  					},
  2999  				},
  3000  				Names: apiextensionsv1.CustomResourceDefinitionNames{
  3001  					Plural:   crdPlural,
  3002  					Singular: crdPlural,
  3003  					Kind:     crdPlural,
  3004  					ListKind: "list" + crdPlural,
  3005  				},
  3006  				Scope: apiextensionsv1.NamespaceScoped,
  3007  			},
  3008  		}
  3009  
  3010  		By(`Defer CRD clean up`)
  3011  		defer func() {
  3012  			Eventually(func() error {
  3013  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}))
  3014  			}).Should(Succeed())
  3015  		}()
  3016  
  3017  		By(`Create CSV`)
  3018  		packageName := genName("nginx-")
  3019  		stableChannel := "stable"
  3020  		packageNameStable := packageName + "-" + stableChannel
  3021  		csv := newCSV(packageNameStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil)
  3022  
  3023  		By(`Create PackageManifests`)
  3024  		manifests := []registry.PackageManifest{
  3025  			{
  3026  				PackageName: packageName,
  3027  				Channels: []registry.PackageChannel{
  3028  					{Name: stableChannel, CurrentCSVName: packageNameStable},
  3029  				},
  3030  				DefaultChannelName: stableChannel,
  3031  			},
  3032  		}
  3033  
  3034  		By(`Create the CatalogSource`)
  3035  		catalogSourceName := genName("mock-nginx-")
  3036  		_, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csv})
  3037  		defer cleanupCatalogSource()
  3038  
  3039  		By(`Attempt to get the catalog source before creating install plan`)
  3040  		_, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  3041  		require.NoError(GinkgoT(), err)
  3042  
  3043  		subscriptionName := genName("sub-nginx-")
  3044  		cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  3045  		defer cleanupSubscription()
  3046  
  3047  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  3048  		require.NoError(GinkgoT(), err)
  3049  		require.NotNil(GinkgoT(), subscription)
  3050  
  3051  		installPlanName := subscription.Status.InstallPlanRef.Name
  3052  
  3053  		By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  3054  		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed))
  3055  		require.NoError(GinkgoT(), err)
  3056  		GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
  3057  
  3058  		require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  3059  	})
  3060  
  3061  	It("[FLAKE] consistent generation", func() {
  3062  		By(`This It spec verifies that, in cases where there are multiple options to fulfil a dependency`)
  3063  		By(`across multiple catalogs, we only generate one installplan with one set of resolved resources.`)
  3064  		//issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2633
  3065  
  3066  		By(`Configure catalogs:`)
  3067  		By(`) - one catalog with a package that has a dependency`)
  3068  		By(`) - several duplicate catalog with a package that satisfies the dependency`)
  3069  		By(`Install the package from the main catalog`)
  3070  		By(`Should see only 1 installplan created`)
  3071  		By(`Should see the main CSV installed`)
  3072  
  3073  		log := func(s string) {
  3074  			GinkgoT().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s)
  3075  		}
  3076  
  3077  		mainPackageName := genName("nginx-")
  3078  		dependentPackageName := genName("nginxdep-")
  3079  
  3080  		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
  3081  		dependentPackageStable := fmt.Sprintf("%s-stable", dependentPackageName)
  3082  
  3083  		stableChannel := "stable"
  3084  
  3085  		dependentCRD := newCRD(genName("ins-"))
  3086  		mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil)
  3087  		dependentCSV := newCSV(dependentPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil)
  3088  
  3089  		defer func() {
  3090  			require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  3091  		}()
  3092  
  3093  		dependentCatalogName := genName("mock-ocs-dependent-")
  3094  		mainCatalogName := genName("mock-ocs-main-")
  3095  
  3096  		By(`Create separate manifests for each CatalogSource`)
  3097  		mainManifests := []registry.PackageManifest{
  3098  			{
  3099  				PackageName: mainPackageName,
  3100  				Channels: []registry.PackageChannel{
  3101  					{Name: stableChannel, CurrentCSVName: mainPackageStable},
  3102  				},
  3103  				DefaultChannelName: stableChannel,
  3104  			},
  3105  		}
  3106  
  3107  		dependentManifests := []registry.PackageManifest{
  3108  			{
  3109  				PackageName: dependentPackageName,
  3110  				Channels: []registry.PackageChannel{
  3111  					{Name: stableChannel, CurrentCSVName: dependentPackageStable},
  3112  				},
  3113  				DefaultChannelName: stableChannel,
  3114  			},
  3115  		}
  3116  
  3117  		By(`Defer CRD clean up`)
  3118  		defer func() {
  3119  			Eventually(func() error {
  3120  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{}))
  3121  			}).Should(Succeed())
  3122  		}()
  3123  
  3124  		By(`Create the dependent catalog source`)
  3125  		_, cleanupDependentCatalogSource := createInternalCatalogSource(c, crc, dependentCatalogName, generatedNamespace.GetName(), dependentManifests, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentCSV})
  3126  		defer cleanupDependentCatalogSource()
  3127  
  3128  		By(`Attempt to get the catalog source before creating install plan`)
  3129  		dependentCatalogSource, err := fetchCatalogSourceOnStatus(crc, dependentCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  3130  		require.NoError(GinkgoT(), err)
  3131  
  3132  		By(`Create the alt dependent catalog sources`)
  3133  		var wg sync.WaitGroup
  3134  		for i := 0; i < 4; i++ { // Creating more increases the odds that the race condition will be triggered
  3135  			wg.Add(1)
  3136  			go func(i int) {
  3137  				defer GinkgoRecover()
  3138  				By(`Create a CatalogSource pointing to the grpc pod`)
  3139  				addressSource := &operatorsv1alpha1.CatalogSource{
  3140  					TypeMeta: metav1.TypeMeta{
  3141  						Kind:       operatorsv1alpha1.CatalogSourceKind,
  3142  						APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion,
  3143  					},
  3144  					Spec: operatorsv1alpha1.CatalogSourceSpec{
  3145  						SourceType: operatorsv1alpha1.SourceTypeGrpc,
  3146  						Address:    dependentCatalogSource.Status.RegistryServiceStatus.Address(),
  3147  						GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{
  3148  							SecurityContextConfig: operatorsv1alpha1.Restricted,
  3149  						},
  3150  					},
  3151  				}
  3152  				addressSource.SetName(genName("alt-dep-"))
  3153  
  3154  				_, err := crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), addressSource, metav1.CreateOptions{})
  3155  				require.NoError(GinkgoT(), err)
  3156  
  3157  				By(`Attempt to get the catalog source before creating install plan`)
  3158  				_, err = fetchCatalogSourceOnStatus(crc, addressSource.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  3159  				require.NoError(GinkgoT(), err)
  3160  				wg.Done()
  3161  			}(i)
  3162  		}
  3163  		wg.Wait()
  3164  
  3165  		By(`Create the main catalog source`)
  3166  		_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, nil, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
  3167  		defer cleanupMainCatalogSource()
  3168  
  3169  		By(`Attempt to get the catalog source before creating install plan`)
  3170  		_, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
  3171  		require.NoError(GinkgoT(), err)
  3172  
  3173  		subscriptionName := genName("sub-nginx-")
  3174  		subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  3175  		defer subscriptionCleanup()
  3176  
  3177  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
  3178  		require.NoError(GinkgoT(), err)
  3179  		require.NotNil(GinkgoT(), subscription)
  3180  
  3181  		installPlanName := subscription.Status.InstallPlanRef.Name
  3182  
  3183  		By(`Wait for InstallPlan to be status: Complete before checking resource presence`)
  3184  		fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
  3185  		require.NoError(GinkgoT(), err)
  3186  		log(fmt.Sprintf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase))
  3187  
  3188  		require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
  3189  
  3190  		By(`Verify CSV is created`)
  3191  		_, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker)
  3192  		require.NoError(GinkgoT(), err)
  3193  
  3194  		By(`Make sure to clean up the installed CRD`)
  3195  		deleteOpts := &metav1.DeleteOptions{}
  3196  		defer func() {
  3197  			require.NoError(GinkgoT(), c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), *deleteOpts))
  3198  		}()
  3199  
  3200  		By(`ensure there is only one installplan`)
  3201  		ips, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{})
  3202  		require.NoError(GinkgoT(), err)
  3203  		require.Equal(GinkgoT(), 1, len(ips.Items), "If this test fails it should be taken seriously and not treated as a flake. \n%v", ips.Items)
  3204  	})
  3205  
  3206  	When("an InstallPlan is created with no valid OperatorGroup present", func() {
  3207  		var (
  3208  			installPlanName string
  3209  		)
  3210  
  3211  		BeforeEach(func() {
  3212  			By(`Make sure there are no OGs in the namespace already`)
  3213  			require.NoError(GinkgoT(), crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}))
  3214  
  3215  			By(`Create InstallPlan`)
  3216  			installPlanName = "ip"
  3217  			ip := newInstallPlanWithDummySteps(installPlanName, generatedNamespace.GetName(), operatorsv1alpha1.InstallPlanPhaseInstalling)
  3218  			outIP, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Create(context.Background(), ip, metav1.CreateOptions{})
  3219  			Expect(err).NotTo(HaveOccurred())
  3220  			Expect(outIP).NotTo(BeNil())
  3221  
  3222  			By(`The status gets ignored on create so we need to update it else the InstallPlan sync ignores`)
  3223  			By(`InstallPlans without any steps or bundle lookups`)
  3224  			outIP.Status = ip.Status
  3225  			_, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).UpdateStatus(context.Background(), outIP, metav1.UpdateOptions{})
  3226  			Expect(err).NotTo(HaveOccurred())
  3227  		})
  3228  
  3229  		AfterEach(func() {
  3230  			err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), installPlanName, metav1.DeleteOptions{})
  3231  			Expect(err).NotTo(HaveOccurred())
  3232  		})
  3233  
  3234  		It("[FLAKE] should clear up the condition in the InstallPlan status that contains an error message when a valid OperatorGroup is created", func() {
  3235  			By(`issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2636`)
  3236  
  3237  			By(`first wait for a condition with a message exists`)
  3238  			cond := operatorsv1alpha1.InstallPlanCondition{Type: operatorsv1alpha1.InstallPlanInstalled, Status: corev1.ConditionFalse, Reason: operatorsv1alpha1.InstallPlanReasonInstallCheckFailed,
  3239  				Message: "no operator group found that is managing this namespace"}
  3240  
  3241  			Eventually(func() bool {
  3242  				fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseInstalling))
  3243  				if err != nil || fetchedInstallPlan == nil {
  3244  					return false
  3245  				}
  3246  				if fetchedInstallPlan.Status.Phase != operatorsv1alpha1.InstallPlanPhaseInstalling {
  3247  					return false
  3248  				}
  3249  				return hasCondition(fetchedInstallPlan, cond)
  3250  			}, 5*time.Minute, interval).Should(BeTrue())
  3251  
  3252  			By(`Create an operatorgroup for the same namespace`)
  3253  			og := &operatorsv1.OperatorGroup{
  3254  				ObjectMeta: metav1.ObjectMeta{
  3255  					Name:      "og",
  3256  					Namespace: generatedNamespace.GetName(),
  3257  				},
  3258  				Spec: operatorsv1.OperatorGroupSpec{
  3259  					TargetNamespaces: []string{generatedNamespace.GetName()},
  3260  				},
  3261  			}
  3262  			Eventually(func() error {
  3263  				return ctx.Ctx().Client().Create(context.Background(), og)
  3264  			}, timeout, interval).Should(Succeed(), "could not create OperatorGroup")
  3265  
  3266  			By(`Wait for the OperatorGroup to be synced`)
  3267  			Eventually(
  3268  				func() ([]string, error) {
  3269  					err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(og), og)
  3270  					ctx.Ctx().Logf("Waiting for OperatorGroup(%v) to be synced with status.namespaces: %v", og.Name, og.Status.Namespaces)
  3271  					return og.Status.Namespaces, err
  3272  				},
  3273  				1*time.Minute,
  3274  				interval,
  3275  			).Should(ContainElement(generatedNamespace.GetName()))
  3276  
  3277  			By(`check that the condition has been cleared up`)
  3278  			Eventually(func() (bool, error) {
  3279  				fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseInstalling))
  3280  				if err != nil {
  3281  					return false, err
  3282  				}
  3283  				if fetchedInstallPlan == nil {
  3284  					return false, err
  3285  				}
  3286  				if hasCondition(fetchedInstallPlan, cond) {
  3287  					return false, nil
  3288  				}
  3289  				return true, nil
  3290  			}).Should(BeTrue())
  3291  		})
  3292  	})
  3293  
  3294  	It("compresses installplan step resource manifests to configmap references", func() {
  3295  		By(`Test ensures that all steps for index-based catalogs are references to configmaps. This avoids the problem`)
  3296  		By(`of installplans growing beyond the etcd size limit when manifests are written to the ip status.`)
  3297  		catsrc := &operatorsv1alpha1.CatalogSource{
  3298  			ObjectMeta: metav1.ObjectMeta{
  3299  				Name:      genName("kiali-"),
  3300  				Namespace: generatedNamespace.GetName(),
  3301  				Labels:    map[string]string{"olm.catalogSource": "kaili-catalog"},
  3302  			},
  3303  			Spec: operatorsv1alpha1.CatalogSourceSpec{
  3304  				Image:      "quay.io/operator-framework/ci-index:latest",
  3305  				SourceType: operatorsv1alpha1.SourceTypeGrpc,
  3306  				GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{
  3307  					SecurityContextConfig: operatorsv1alpha1.Restricted,
  3308  				},
  3309  			},
  3310  		}
  3311  		catsrc, err := crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Create(context.Background(), catsrc, metav1.CreateOptions{})
  3312  		Expect(err).ToNot(HaveOccurred())
  3313  
  3314  		By(`Wait for the CatalogSource to be ready`)
  3315  		catsrc, err = fetchCatalogSourceOnStatus(crc, catsrc.GetName(), catsrc.GetNamespace(), catalogSourceRegistryPodSynced())
  3316  		Expect(err).ToNot(HaveOccurred())
  3317  
  3318  		By(`Generate a Subscription`)
  3319  		subName := genName("kiali-")
  3320  		cleanUpSubscriptionFn := createSubscriptionForCatalog(crc, catsrc.GetNamespace(), subName, catsrc.GetName(), "kiali", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
  3321  		defer cleanUpSubscriptionFn()
  3322  
  3323  		sub, err := fetchSubscription(crc, catsrc.GetNamespace(), subName, subscriptionHasInstallPlanChecker())
  3324  		Expect(err).ToNot(HaveOccurred())
  3325  
  3326  		By(`Wait for the expected InstallPlan's execution to either fail or succeed`)
  3327  		ipName := sub.Status.InstallPlanRef.Name
  3328  		ip, err := waitForInstallPlan(crc, ipName, sub.GetNamespace(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete))
  3329  		Expect(err).ToNot(HaveOccurred())
  3330  		Expect(operatorsv1alpha1.InstallPlanPhaseComplete).To(Equal(ip.Status.Phase), "InstallPlan not complete")
  3331  
  3332  		By(`Ensure the InstallPlan contains the steps resolved from the bundle image`)
  3333  		operatorName := "kiali-operator"
  3334  		expectedSteps := map[registry.ResourceKey]struct{}{
  3335  			{Name: operatorName, Kind: "ClusterServiceVersion"}:                                  {},
  3336  			{Name: "kialis.kiali.io", Kind: "CustomResourceDefinition"}:                          {},
  3337  			{Name: "monitoringdashboards.monitoring.kiali.io", Kind: "CustomResourceDefinition"}: {},
  3338  			{Name: operatorName, Kind: "ServiceAccount"}:                                         {},
  3339  			{Name: operatorName, Kind: "ClusterRole"}:                                            {},
  3340  			{Name: operatorName, Kind: "ClusterRoleBinding"}:                                     {},
  3341  		}
  3342  		Expect(ip.Status.Plan).To(HaveLen(len(expectedSteps)), "number of expected steps does not match installed: %v", ip.Status.Plan)
  3343  
  3344  		for _, step := range ip.Status.Plan {
  3345  			key := registry.ResourceKey{
  3346  				Name: step.Resource.Name,
  3347  				Kind: step.Resource.Kind,
  3348  			}
  3349  			for expected := range expectedSteps {
  3350  				if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind {
  3351  					delete(expectedSteps, expected)
  3352  				}
  3353  			}
  3354  		}
  3355  		Expect(expectedSteps).To(HaveLen(0), "Actual resource steps do not match expected: %#v", expectedSteps)
  3356  
  3357  		By(`Ensure that all the steps have a configmap based reference`)
  3358  		for _, step := range ip.Status.Plan {
  3359  			manifest := step.Resource.Manifest
  3360  			var ref catalog.UnpackedBundleReference
  3361  			err := json.Unmarshal([]byte(manifest), &ref)
  3362  			Expect(err).ToNot(HaveOccurred())
  3363  			Expect(ref.Kind).To(Equal("ConfigMap"))
  3364  		}
  3365  	})
  3366  
  3367  	It("limits installed resources if the scoped serviceaccount has no permissions", func() {
  3368  		By("creating a scoped serviceaccount specified in the operatorgroup")
  3369  		By(`create SA`)
  3370  		sa := &corev1.ServiceAccount{
  3371  			ObjectMeta: metav1.ObjectMeta{
  3372  				Name:      genName("sa-"),
  3373  				Namespace: generatedNamespace.GetName(),
  3374  			},
  3375  		}
  3376  		_, err := c.KubernetesInterface().CoreV1().ServiceAccounts(generatedNamespace.GetName()).Create(context.Background(), sa, metav1.CreateOptions{})
  3377  		Expect(err).ToNot(HaveOccurred())
  3378  		By(`Create token secret for the serviceaccount`)
  3379  		_, cleanupSE := newTokenSecret(c, generatedNamespace.GetName(), sa.GetName())
  3380  		defer cleanupSE()
  3381  
  3382  		By(`role has no explicit permissions`)
  3383  		role := &rbacv1.ClusterRole{
  3384  			ObjectMeta: metav1.ObjectMeta{
  3385  				Name: genName("role-"),
  3386  			},
  3387  			Rules: []rbacv1.PolicyRule{},
  3388  		}
  3389  
  3390  		By(`bind role to SA`)
  3391  		rb := &rbacv1.ClusterRoleBinding{
  3392  			ObjectMeta: metav1.ObjectMeta{
  3393  				Name: genName("rb-"),
  3394  			},
  3395  			RoleRef: rbacv1.RoleRef{
  3396  				Name:     role.GetName(),
  3397  				Kind:     "ClusterRole",
  3398  				APIGroup: "rbac.authorization.k8s.io",
  3399  			},
  3400  			Subjects: []rbacv1.Subject{
  3401  				{
  3402  					Kind:      "ServiceAccount",
  3403  					Name:      sa.GetName(),
  3404  					APIGroup:  "",
  3405  					Namespace: sa.GetNamespace(),
  3406  				},
  3407  			},
  3408  		}
  3409  
  3410  		_, err = c.KubernetesInterface().RbacV1().ClusterRoleBindings().Create(context.Background(), rb, metav1.CreateOptions{})
  3411  		Expect(err).ToNot(HaveOccurred())
  3412  		defer c.KubernetesInterface().RbacV1().ClusterRoles().Delete(context.Background(), role.GetName(), metav1.DeleteOptions{})
  3413  
  3414  		By(`Update the existing OG to use the ServiceAccount`)
  3415  		Eventually(func() error {
  3416  			existingOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{})
  3417  			if err != nil {
  3418  				return err
  3419  			}
  3420  			existingOG.Spec.ServiceAccountName = sa.GetName()
  3421  			_, err = crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Update(context.Background(), existingOG, metav1.UpdateOptions{})
  3422  			return err
  3423  		}).Should(Succeed())
  3424  
  3425  		By(`Wait for the OperatorGroup to be synced and have a status.ServiceAccountRef`)
  3426  		By(`before moving on. Otherwise the catalog operator treats it as an invalid OperatorGroup`)
  3427  		By(`and the InstallPlan is resynced`)
  3428  		Eventually(func() (*corev1.ObjectReference, error) {
  3429  			outOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{})
  3430  			if err != nil {
  3431  				return nil, err
  3432  			}
  3433  			ctx.Ctx().Logf("[DEBUG] Operator Group Status: %+v\n", outOG.Status)
  3434  			return outOG.Status.ServiceAccountRef, nil
  3435  		}).ShouldNot(BeNil())
  3436  
  3437  		crd := apiextensionsv1.CustomResourceDefinition{
  3438  			ObjectMeta: metav1.ObjectMeta{
  3439  				Name: "ins" + ".cluster.com",
  3440  			},
  3441  			TypeMeta: metav1.TypeMeta{
  3442  				Kind:       "CustomResourceDefinition",
  3443  				APIVersion: "v1",
  3444  			},
  3445  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  3446  				Group: "cluster.com",
  3447  				Names: apiextensionsv1.CustomResourceDefinitionNames{
  3448  					Plural:   "ins",
  3449  					Singular: "ins",
  3450  					Kind:     "ins",
  3451  					ListKind: "ins" + "list",
  3452  				},
  3453  				Scope: apiextensionsv1.NamespaceScoped,
  3454  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  3455  					{
  3456  						Name:    "v1alpha1",
  3457  						Served:  true,
  3458  						Storage: true,
  3459  						Schema: &apiextensionsv1.CustomResourceValidation{
  3460  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  3461  								Type:        "object",
  3462  								Description: "my crd schema",
  3463  							},
  3464  						},
  3465  					},
  3466  				},
  3467  			},
  3468  		}
  3469  
  3470  		By(`Defer CRD clean up`)
  3471  		defer func() {
  3472  			Eventually(func() error {
  3473  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}))
  3474  			}).Should(Succeed())
  3475  		}()
  3476  
  3477  		scheme := runtime.NewScheme()
  3478  		Expect(apiextensionsv1.AddToScheme(scheme)).To(Succeed())
  3479  		var crdManifest bytes.Buffer
  3480  		Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&crd, &crdManifest)).To(Succeed())
  3481  		By("using the OLM client to create the CRD")
  3482  		plan := &operatorsv1alpha1.InstallPlan{
  3483  			ObjectMeta: metav1.ObjectMeta{
  3484  				Namespace: generatedNamespace.GetName(),
  3485  				Name:      genName("ip-"),
  3486  			},
  3487  			Spec: operatorsv1alpha1.InstallPlanSpec{
  3488  				Approval:                   operatorsv1alpha1.ApprovalAutomatic,
  3489  				Approved:                   true,
  3490  				ClusterServiceVersionNames: []string{},
  3491  			},
  3492  		}
  3493  
  3494  		Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed())
  3495  		plan.Status = operatorsv1alpha1.InstallPlanStatus{
  3496  			AttenuatedServiceAccountRef: &corev1.ObjectReference{
  3497  				Name:      sa.GetName(),
  3498  				Namespace: sa.GetNamespace(),
  3499  				Kind:      "ServiceAccount",
  3500  			},
  3501  			Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
  3502  			CatalogSources: []string{},
  3503  			Plan: []*operatorsv1alpha1.Step{
  3504  				{
  3505  					Status: operatorsv1alpha1.StepStatusUnknown,
  3506  					Resource: operatorsv1alpha1.StepResource{
  3507  						Name:     crd.GetName(),
  3508  						Version:  "v1",
  3509  						Kind:     "CustomResourceDefinition",
  3510  						Manifest: crdManifest.String(),
  3511  					},
  3512  				},
  3513  			},
  3514  		}
  3515  		Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed())
  3516  
  3517  		key := client.ObjectKeyFromObject(plan)
  3518  
  3519  		Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
  3520  			return plan, ctx.Ctx().Client().Get(context.Background(), key, plan)
  3521  		}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
  3522  
  3523  		By(`delete installplan, then create one with an additional resource that the SA does not have permissions to create`)
  3524  		By(`expect installplan to fail`)
  3525  		By("failing to install resources that are not explicitly allowed in the SA")
  3526  		err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), plan.GetName(), metav1.DeleteOptions{})
  3527  		Expect(err).ToNot(HaveOccurred())
  3528  
  3529  		service := &corev1.Service{
  3530  			TypeMeta: metav1.TypeMeta{
  3531  				Kind:       "Service",
  3532  				APIVersion: "v1",
  3533  			},
  3534  			ObjectMeta: metav1.ObjectMeta{
  3535  				Namespace: generatedNamespace.GetName(),
  3536  				Name:      "test-service",
  3537  			},
  3538  			Spec: corev1.ServiceSpec{
  3539  				Type: corev1.ServiceTypeClusterIP,
  3540  				Ports: []corev1.ServicePort{
  3541  					{
  3542  						Port: 12345,
  3543  					},
  3544  				},
  3545  			},
  3546  		}
  3547  
  3548  		Expect(corev1.AddToScheme(scheme)).To(Succeed())
  3549  		var manifest bytes.Buffer
  3550  		Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(service, &manifest)).To(Succeed())
  3551  
  3552  		newPlan := &operatorsv1alpha1.InstallPlan{
  3553  			ObjectMeta: metav1.ObjectMeta{
  3554  				Namespace: generatedNamespace.GetName(),
  3555  				Name:      genName("ip-"),
  3556  			},
  3557  			Spec: operatorsv1alpha1.InstallPlanSpec{
  3558  				Approval:                   operatorsv1alpha1.ApprovalAutomatic,
  3559  				Approved:                   true,
  3560  				ClusterServiceVersionNames: []string{},
  3561  			},
  3562  		}
  3563  
  3564  		Expect(ctx.Ctx().Client().Create(context.Background(), newPlan)).To(Succeed())
  3565  		newPlan.Status = operatorsv1alpha1.InstallPlanStatus{
  3566  			StartTime: &metav1.Time{Time: time.Unix(0, 0)}, // disable retries
  3567  			AttenuatedServiceAccountRef: &corev1.ObjectReference{
  3568  				Name:      sa.GetName(),
  3569  				Namespace: sa.GetNamespace(),
  3570  				Kind:      "ServiceAccount",
  3571  			},
  3572  			Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
  3573  			CatalogSources: []string{},
  3574  			Plan: []*operatorsv1alpha1.Step{
  3575  				{
  3576  					Status: operatorsv1alpha1.StepStatusUnknown,
  3577  					Resource: operatorsv1alpha1.StepResource{
  3578  						Name:     service.Name,
  3579  						Version:  "v1",
  3580  						Kind:     "Service",
  3581  						Manifest: manifest.String(),
  3582  					},
  3583  				},
  3584  			},
  3585  		}
  3586  		Expect(ctx.Ctx().Client().Status().Update(context.Background(), newPlan)).To(Succeed())
  3587  
  3588  		ipPhaseCheckerFunc := buildInstallPlanMessageCheckFunc(`cannot create resource "services" in API group`)
  3589  		_, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, newPlan.Name, newPlan.Namespace, ipPhaseCheckerFunc)
  3590  		require.NoError(GinkgoT(), err)
  3591  
  3592  		Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &crd))).To(Succeed())
  3593  	})
  3594  
  3595  	It("uses the correct client when installing resources from an installplan", func() {
  3596  		By("creating a scoped serviceaccount specifified in the operatorgroup")
  3597  		By(`create SA`)
  3598  		sa := &corev1.ServiceAccount{
  3599  			ObjectMeta: metav1.ObjectMeta{
  3600  				Name:      genName("sa-"),
  3601  				Namespace: generatedNamespace.GetName(),
  3602  			},
  3603  		}
  3604  		_, err := c.KubernetesInterface().CoreV1().ServiceAccounts(generatedNamespace.GetName()).Create(context.Background(), sa, metav1.CreateOptions{})
  3605  		Expect(err).ToNot(HaveOccurred())
  3606  		By(`Create token secret for the serviceaccount`)
  3607  		_, cleanupSE := newTokenSecret(c, generatedNamespace.GetName(), sa.GetName())
  3608  		defer cleanupSE()
  3609  
  3610  		By(`see https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/design/scoped-operator-install.md`)
  3611  		By(`create permissions with the ability to get and list CRDs, but not create CRDs`)
  3612  		role := &rbacv1.ClusterRole{
  3613  			ObjectMeta: metav1.ObjectMeta{
  3614  				Name: genName("role-"),
  3615  			},
  3616  			Rules: []rbacv1.PolicyRule{
  3617  				{
  3618  					APIGroups: []string{"operators.coreos.com"},
  3619  					Resources: []string{"subscriptions", "clusterserviceversions"},
  3620  					Verbs:     []string{"get", "create", "update", "patch"},
  3621  				},
  3622  				{
  3623  					APIGroups: []string{""},
  3624  					Resources: []string{"services", "serviceaccounts", "configmaps", "endpoints", "events", "persistentvolumeclaims", "pods"},
  3625  					Verbs:     []string{"create", "delete", "get", "list", "update", "patch", "watch"},
  3626  				},
  3627  				{
  3628  					APIGroups: []string{"apps"},
  3629  					Resources: []string{"deployments", "replicasets", "statefulsets"},
  3630  					Verbs:     []string{"list", "watch", "get", "create", "update", "patch", "delete"},
  3631  				},
  3632  				{
  3633  					APIGroups: []string{"apiextensions.k8s.io"},
  3634  					Resources: []string{"customresourcedefinitions"},
  3635  					Verbs:     []string{"get", "list", "watch"},
  3636  				},
  3637  			},
  3638  		}
  3639  
  3640  		_, err = c.KubernetesInterface().RbacV1().ClusterRoles().Create(context.Background(), role, metav1.CreateOptions{})
  3641  		Expect(err).ToNot(HaveOccurred())
  3642  
  3643  		By(`bind role to SA`)
  3644  		rb := &rbacv1.ClusterRoleBinding{
  3645  			ObjectMeta: metav1.ObjectMeta{
  3646  				Name: genName("rb-"),
  3647  			},
  3648  			RoleRef: rbacv1.RoleRef{
  3649  				Name:     role.GetName(),
  3650  				Kind:     "ClusterRole",
  3651  				APIGroup: "rbac.authorization.k8s.io",
  3652  			},
  3653  			Subjects: []rbacv1.Subject{
  3654  				{
  3655  					Kind:      "ServiceAccount",
  3656  					Name:      sa.GetName(),
  3657  					APIGroup:  "",
  3658  					Namespace: sa.GetNamespace(),
  3659  				},
  3660  			},
  3661  		}
  3662  
  3663  		_, err = c.KubernetesInterface().RbacV1().ClusterRoleBindings().Create(context.Background(), rb, metav1.CreateOptions{})
  3664  		Expect(err).ToNot(HaveOccurred())
  3665  		defer c.KubernetesInterface().RbacV1().ClusterRoles().Delete(context.Background(), role.GetName(), metav1.DeleteOptions{})
  3666  
  3667  		By(`Update the existing OG to use the ServiceAccount`)
  3668  		Eventually(func() error {
  3669  			existingOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{})
  3670  			if err != nil {
  3671  				return err
  3672  			}
  3673  			existingOG.Spec.ServiceAccountName = sa.GetName()
  3674  			_, err = crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Update(context.Background(), existingOG, metav1.UpdateOptions{})
  3675  			return err
  3676  		}).Should(Succeed())
  3677  
  3678  		By(`Wait for the OperatorGroup to be synced and have a status.ServiceAccountRef`)
  3679  		By(`before moving on. Otherwise the catalog operator treats it as an invalid OperatorGroup`)
  3680  		By(`and the InstallPlan is resynced`)
  3681  		Eventually(func() (*corev1.ObjectReference, error) {
  3682  			outOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{})
  3683  			if err != nil {
  3684  				return nil, err
  3685  			}
  3686  			ctx.Ctx().Logf("[DEBUG] Operator Group Status: %+v\n", outOG.Status)
  3687  			return outOG.Status.ServiceAccountRef, nil
  3688  		}).ShouldNot(BeNil())
  3689  
  3690  		By("using the OLM client to install CRDs from the installplan and the scoped client for other resources")
  3691  
  3692  		crd := apiextensionsv1.CustomResourceDefinition{
  3693  			ObjectMeta: metav1.ObjectMeta{
  3694  				Name: "ins" + ".cluster.com",
  3695  			},
  3696  			TypeMeta: metav1.TypeMeta{
  3697  				Kind:       "CustomResourceDefinition",
  3698  				APIVersion: "v1",
  3699  			},
  3700  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  3701  				Group: "cluster.com",
  3702  				Names: apiextensionsv1.CustomResourceDefinitionNames{
  3703  					Plural:   "ins",
  3704  					Singular: "ins",
  3705  					Kind:     "ins",
  3706  					ListKind: "ins" + "list",
  3707  				},
  3708  				Scope: apiextensionsv1.NamespaceScoped,
  3709  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  3710  					{
  3711  						Name:    "v1alpha1",
  3712  						Served:  true,
  3713  						Storage: true,
  3714  						Schema: &apiextensionsv1.CustomResourceValidation{
  3715  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  3716  								Type:        "object",
  3717  								Description: "my crd schema",
  3718  							},
  3719  						},
  3720  					},
  3721  				},
  3722  			},
  3723  		}
  3724  		csv := newCSV("stable", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil)
  3725  
  3726  		By(`Defer CRD clean up`)
  3727  		defer func() {
  3728  			Eventually(func() error {
  3729  				return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}))
  3730  			}).Should(Succeed())
  3731  			Eventually(func() error {
  3732  				return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv))
  3733  			}).Should(Succeed())
  3734  		}()
  3735  
  3736  		scheme := runtime.NewScheme()
  3737  		Expect(apiextensionsv1.AddToScheme(scheme)).To(Succeed())
  3738  		Expect(operatorsv1alpha1.AddToScheme(scheme)).To(Succeed())
  3739  		var crdManifest, csvManifest bytes.Buffer
  3740  		Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&crd, &crdManifest)).To(Succeed())
  3741  		Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&csv, &csvManifest)).To(Succeed())
  3742  
  3743  		plan := &operatorsv1alpha1.InstallPlan{
  3744  			ObjectMeta: metav1.ObjectMeta{
  3745  				Namespace: generatedNamespace.GetName(),
  3746  				Name:      genName("ip-"),
  3747  			},
  3748  			Spec: operatorsv1alpha1.InstallPlanSpec{
  3749  				Approval:                   operatorsv1alpha1.ApprovalAutomatic,
  3750  				Approved:                   true,
  3751  				ClusterServiceVersionNames: []string{csv.GetName()},
  3752  			},
  3753  		}
  3754  
  3755  		Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed())
  3756  		plan.Status = operatorsv1alpha1.InstallPlanStatus{
  3757  			AttenuatedServiceAccountRef: &corev1.ObjectReference{
  3758  				Name:      sa.GetName(),
  3759  				Namespace: sa.GetNamespace(),
  3760  				Kind:      "ServiceAccount",
  3761  			},
  3762  			Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
  3763  			CatalogSources: []string{},
  3764  			Plan: []*operatorsv1alpha1.Step{
  3765  				{
  3766  					Status: operatorsv1alpha1.StepStatusUnknown,
  3767  					Resource: operatorsv1alpha1.StepResource{
  3768  						Name:     csv.GetName(),
  3769  						Version:  "v1alpha1",
  3770  						Kind:     "ClusterServiceVersion",
  3771  						Manifest: csvManifest.String(),
  3772  					},
  3773  				},
  3774  				{
  3775  					Status: operatorsv1alpha1.StepStatusUnknown,
  3776  					Resource: operatorsv1alpha1.StepResource{
  3777  						Name:     crd.GetName(),
  3778  						Version:  "v1",
  3779  						Kind:     "CustomResourceDefinition",
  3780  						Manifest: crdManifest.String(),
  3781  					},
  3782  				},
  3783  			},
  3784  		}
  3785  		Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed())
  3786  
  3787  		key := client.ObjectKeyFromObject(plan)
  3788  
  3789  		Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
  3790  			return plan, ctx.Ctx().Client().Get(context.Background(), key, plan)
  3791  		}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
  3792  
  3793  		By(`delete installplan, and create one with just a CSV resource which should succeed`)
  3794  		By("installing additional resources that are allowed in the SA")
  3795  		err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), plan.GetName(), metav1.DeleteOptions{})
  3796  		Expect(err).ToNot(HaveOccurred())
  3797  
  3798  		newPlan := &operatorsv1alpha1.InstallPlan{
  3799  			ObjectMeta: metav1.ObjectMeta{
  3800  				Namespace: generatedNamespace.GetName(),
  3801  				Name:      genName("ip-"),
  3802  			},
  3803  			Spec: operatorsv1alpha1.InstallPlanSpec{
  3804  				Approval:                   operatorsv1alpha1.ApprovalAutomatic,
  3805  				Approved:                   true,
  3806  				ClusterServiceVersionNames: []string{csv.GetName()},
  3807  			},
  3808  		}
  3809  
  3810  		Expect(ctx.Ctx().Client().Create(context.Background(), newPlan)).To(Succeed())
  3811  		newPlan.Status = operatorsv1alpha1.InstallPlanStatus{
  3812  			AttenuatedServiceAccountRef: &corev1.ObjectReference{
  3813  				Name:      sa.GetName(),
  3814  				Namespace: sa.GetNamespace(),
  3815  				Kind:      "ServiceAccount",
  3816  			},
  3817  			Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
  3818  			CatalogSources: []string{},
  3819  			Plan: []*operatorsv1alpha1.Step{
  3820  				{
  3821  					Status: operatorsv1alpha1.StepStatusUnknown,
  3822  					Resource: operatorsv1alpha1.StepResource{
  3823  						Name:     csv.GetName(),
  3824  						Version:  "v1alpha1",
  3825  						Kind:     "ClusterServiceVersion",
  3826  						Manifest: csvManifest.String(),
  3827  					},
  3828  				},
  3829  			},
  3830  		}
  3831  		Expect(ctx.Ctx().Client().Status().Update(context.Background(), newPlan)).To(Succeed())
  3832  
  3833  		newKey := client.ObjectKeyFromObject(newPlan)
  3834  
  3835  		Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
  3836  			return newPlan, ctx.Ctx().Client().Get(context.Background(), newKey, newPlan)
  3837  		}).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete))
  3838  	})
  3839  })
  3840  
  3841  type checkInstallPlanFunc func(fip *operatorsv1alpha1.InstallPlan) bool
  3842  
  3843  func validateCRDVersions(t GinkgoTInterface, c operatorclient.ClientInterface, name string, expectedVersions map[string]struct{}) {
  3844  	By(`Retrieve CRD information`)
  3845  	crd, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), name, metav1.GetOptions{})
  3846  	require.NoError(t, err)
  3847  
  3848  	require.Equal(t, len(expectedVersions), len(crd.Spec.Versions), "number of CRD versions don't not match installed")
  3849  
  3850  	for _, version := range crd.Spec.Versions {
  3851  		_, ok := expectedVersions[version.Name]
  3852  		require.True(t, ok, "couldn't find %v in expected versions: %#v", version.Name, expectedVersions)
  3853  
  3854  		By(`Remove the entry from the expected steps set (to ensure no duplicates in resolved plan)`)
  3855  		delete(expectedVersions, version.Name)
  3856  	}
  3857  
  3858  	By(`Should have removed every matching version`)
  3859  	require.Equal(t, 0, len(expectedVersions), "Actual CRD versions do not match expected")
  3860  }
  3861  
  3862  func buildInstallPlanMessageCheckFunc(substring string) checkInstallPlanFunc {
  3863  	var lastMessage string
  3864  	lastTime := time.Now()
  3865  	return func(fip *operatorsv1alpha1.InstallPlan) bool {
  3866  		if fip.Status.Message != lastMessage {
  3867  			ctx.Ctx().Logf("waiting %s for installplan %s/%s to have message substring %q, have message %q", time.Since(lastTime), fip.Namespace, fip.Name, substring, fip.Status.Message)
  3868  			lastMessage = fip.Status.Message
  3869  			lastTime = time.Now()
  3870  		}
  3871  		return strings.Contains(fip.Status.Message, substring)
  3872  	}
  3873  }
  3874  
  3875  func buildInstallPlanPhaseCheckFunc(phases ...operatorsv1alpha1.InstallPlanPhase) checkInstallPlanFunc {
  3876  	var lastPhase operatorsv1alpha1.InstallPlanPhase
  3877  	lastTime := time.Now()
  3878  	return func(fip *operatorsv1alpha1.InstallPlan) bool {
  3879  		if fip.Status.Phase != lastPhase {
  3880  			ctx.Ctx().Logf("waiting %s for installplan %s/%s to be phases %v, in phase %s", time.Since(lastTime), fip.Namespace, fip.Name, phases, fip.Status.Phase)
  3881  			lastPhase = fip.Status.Phase
  3882  			lastTime = time.Now()
  3883  		}
  3884  		satisfiesAny := false
  3885  		for _, phase := range phases {
  3886  			satisfiesAny = satisfiesAny || fip.Status.Phase == phase
  3887  		}
  3888  		return satisfiesAny
  3889  	}
  3890  }
  3891  
  3892  func buildInstallPlanCleanupFunc(crc versioned.Interface, namespace string, installPlan *operatorsv1alpha1.InstallPlan) cleanupFunc {
  3893  	return func() {
  3894  		deleteOptions := &metav1.DeleteOptions{}
  3895  		for _, step := range installPlan.Status.Plan {
  3896  			if step.Resource.Kind == operatorsv1alpha1.ClusterServiceVersionKind {
  3897  				if err := crc.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(context.Background(), step.Resource.Name, *deleteOptions); err != nil {
  3898  					fmt.Println(err)
  3899  				}
  3900  			}
  3901  		}
  3902  
  3903  		if err := crc.OperatorsV1alpha1().InstallPlans(namespace).Delete(context.Background(), installPlan.GetName(), *deleteOptions); err != nil {
  3904  			fmt.Println(err)
  3905  		}
  3906  
  3907  		err := waitForDelete(func() error {
  3908  			_, err := crc.OperatorsV1alpha1().InstallPlans(namespace).Get(context.Background(), installPlan.GetName(), metav1.GetOptions{})
  3909  			return err
  3910  		})
  3911  
  3912  		if err != nil {
  3913  			fmt.Println(err)
  3914  		}
  3915  	}
  3916  }
  3917  
  3918  func fetchInstallPlan(t GinkgoTInterface, c versioned.Interface, name string, namespace string, checkPhase checkInstallPlanFunc) (*operatorsv1alpha1.InstallPlan, error) {
  3919  	return fetchInstallPlanWithNamespace(t, c, name, namespace, checkPhase)
  3920  }
  3921  
  3922  func fetchInstallPlanWithNamespace(t GinkgoTInterface, c versioned.Interface, name string, namespace string, checkPhase checkInstallPlanFunc) (*operatorsv1alpha1.InstallPlan, error) {
  3923  	var fetchedInstallPlan *operatorsv1alpha1.InstallPlan
  3924  	var err error
  3925  
  3926  	err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  3927  		fetchedInstallPlan, err = c.OperatorsV1alpha1().InstallPlans(namespace).Get(context.Background(), name, metav1.GetOptions{})
  3928  		if err != nil || fetchedInstallPlan == nil {
  3929  			return false, err
  3930  		}
  3931  
  3932  		return checkPhase(fetchedInstallPlan), nil
  3933  	})
  3934  	return fetchedInstallPlan, err
  3935  }
  3936  
  3937  // do not return an error if the installplan has not been created yet
  3938  func waitForInstallPlan(c versioned.Interface, name string, namespace string, checkPhase checkInstallPlanFunc) (*operatorsv1alpha1.InstallPlan, error) {
  3939  	var fetchedInstallPlan *operatorsv1alpha1.InstallPlan
  3940  	var err error
  3941  
  3942  	err = wait.Poll(pollInterval, pollDuration, func() (bool, error) {
  3943  		fetchedInstallPlan, err = c.OperatorsV1alpha1().InstallPlans(namespace).Get(context.Background(), name, metav1.GetOptions{})
  3944  		if err != nil && !apierrors.IsNotFound(err) {
  3945  			return false, err
  3946  		}
  3947  
  3948  		return checkPhase(fetchedInstallPlan), nil
  3949  	})
  3950  	return fetchedInstallPlan, err
  3951  }
  3952  
  3953  func newNginxInstallStrategy(name string, permissions []operatorsv1alpha1.StrategyDeploymentPermissions, clusterPermissions []operatorsv1alpha1.StrategyDeploymentPermissions) operatorsv1alpha1.NamedInstallStrategy {
  3954  	By(`Create an nginx details deployment`)
  3955  	details := operatorsv1alpha1.StrategyDetailsDeployment{
  3956  		DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{
  3957  			{
  3958  				Name: name,
  3959  				Spec: appsv1.DeploymentSpec{
  3960  					Selector: &metav1.LabelSelector{
  3961  						MatchLabels: map[string]string{"app": "nginx"},
  3962  					},
  3963  					Replicas: &singleInstance,
  3964  					Template: corev1.PodTemplateSpec{
  3965  						ObjectMeta: metav1.ObjectMeta{
  3966  							Labels: map[string]string{"app": "nginx"},
  3967  						},
  3968  						Spec: corev1.PodSpec{Containers: []corev1.Container{
  3969  							{
  3970  								Name:            genName("nginx"),
  3971  								Image:           *dummyImage,
  3972  								Ports:           []corev1.ContainerPort{{ContainerPort: 80}},
  3973  								ImagePullPolicy: corev1.PullIfNotPresent,
  3974  							},
  3975  						}},
  3976  					},
  3977  				},
  3978  			},
  3979  		},
  3980  		Permissions:        permissions,
  3981  		ClusterPermissions: clusterPermissions,
  3982  	}
  3983  	namedStrategy := operatorsv1alpha1.NamedInstallStrategy{
  3984  		StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment,
  3985  		StrategySpec: details,
  3986  	}
  3987  
  3988  	return namedStrategy
  3989  }
  3990  
  3991  func newCRD(plural string) apiextensionsv1.CustomResourceDefinition {
  3992  	crd := apiextensionsv1.CustomResourceDefinition{
  3993  		ObjectMeta: metav1.ObjectMeta{
  3994  			Name: plural + ".cluster.com",
  3995  		},
  3996  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  3997  			Group: "cluster.com",
  3998  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  3999  				{
  4000  					Name:    "v1alpha1",
  4001  					Served:  true,
  4002  					Storage: true,
  4003  					Schema: &apiextensionsv1.CustomResourceValidation{
  4004  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  4005  							Type:        "object",
  4006  							Description: "my crd schema",
  4007  						},
  4008  					},
  4009  				},
  4010  			},
  4011  			Names: apiextensionsv1.CustomResourceDefinitionNames{
  4012  				Plural:   plural,
  4013  				Singular: plural,
  4014  				Kind:     plural,
  4015  				ListKind: plural + "list",
  4016  			},
  4017  			Scope: apiextensionsv1.NamespaceScoped,
  4018  		},
  4019  	}
  4020  
  4021  	return crd
  4022  }
  4023  
  4024  func newCSV(name, namespace, replaces string, version semver.Version, owned []apiextensionsv1.CustomResourceDefinition, required []apiextensionsv1.CustomResourceDefinition, namedStrategy *operatorsv1alpha1.NamedInstallStrategy) operatorsv1alpha1.ClusterServiceVersion {
  4025  	csvType = metav1.TypeMeta{
  4026  		Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
  4027  		APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(),
  4028  	}
  4029  
  4030  	By(`set a simple default strategy if none given`)
  4031  	var strategy operatorsv1alpha1.NamedInstallStrategy
  4032  	if namedStrategy == nil {
  4033  		strategy = newNginxInstallStrategy(genName("dep"), nil, nil)
  4034  	} else {
  4035  		strategy = *namedStrategy
  4036  	}
  4037  
  4038  	csv := operatorsv1alpha1.ClusterServiceVersion{
  4039  		TypeMeta: csvType,
  4040  		ObjectMeta: metav1.ObjectMeta{
  4041  			Name:      name,
  4042  			Namespace: namespace,
  4043  		},
  4044  		Spec: operatorsv1alpha1.ClusterServiceVersionSpec{
  4045  			Replaces:       replaces,
  4046  			Version:        opver.OperatorVersion{Version: version},
  4047  			MinKubeVersion: "0.0.0",
  4048  			InstallModes: []operatorsv1alpha1.InstallMode{
  4049  				{
  4050  					Type:      operatorsv1alpha1.InstallModeTypeOwnNamespace,
  4051  					Supported: true,
  4052  				},
  4053  				{
  4054  					Type:      operatorsv1alpha1.InstallModeTypeSingleNamespace,
  4055  					Supported: true,
  4056  				},
  4057  				{
  4058  					Type:      operatorsv1alpha1.InstallModeTypeMultiNamespace,
  4059  					Supported: true,
  4060  				},
  4061  				{
  4062  					Type:      operatorsv1alpha1.InstallModeTypeAllNamespaces,
  4063  					Supported: true,
  4064  				},
  4065  			},
  4066  			InstallStrategy: strategy,
  4067  			CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{
  4068  				Owned:    nil,
  4069  				Required: nil,
  4070  			},
  4071  		},
  4072  	}
  4073  
  4074  	By(`Populate owned and required`)
  4075  	for _, crd := range owned {
  4076  		crdVersion := "v1alpha1"
  4077  		for _, v := range crd.Spec.Versions {
  4078  			if v.Served && v.Storage {
  4079  				crdVersion = v.Name
  4080  				break
  4081  			}
  4082  		}
  4083  		desc := operatorsv1alpha1.CRDDescription{
  4084  			Name:        crd.GetName(),
  4085  			Version:     crdVersion,
  4086  			Kind:        crd.Spec.Names.Plural,
  4087  			DisplayName: crd.GetName(),
  4088  			Description: crd.GetName(),
  4089  		}
  4090  		csv.Spec.CustomResourceDefinitions.Owned = append(csv.Spec.CustomResourceDefinitions.Owned, desc)
  4091  	}
  4092  
  4093  	for _, crd := range required {
  4094  		crdVersion := "v1alpha1"
  4095  		for _, v := range crd.Spec.Versions {
  4096  			if v.Served && v.Storage {
  4097  				crdVersion = v.Name
  4098  				break
  4099  			}
  4100  		}
  4101  		desc := operatorsv1alpha1.CRDDescription{
  4102  			Name:        crd.GetName(),
  4103  			Version:     crdVersion,
  4104  			Kind:        crd.Spec.Names.Plural,
  4105  			DisplayName: crd.GetName(),
  4106  			Description: crd.GetName(),
  4107  		}
  4108  		csv.Spec.CustomResourceDefinitions.Required = append(csv.Spec.CustomResourceDefinitions.Required, desc)
  4109  	}
  4110  
  4111  	return csv
  4112  }
  4113  
  4114  func newInstallPlanWithDummySteps(name, namespace string, phase operatorsv1alpha1.InstallPlanPhase) *operatorsv1alpha1.InstallPlan {
  4115  	return &operatorsv1alpha1.InstallPlan{
  4116  		ObjectMeta: metav1.ObjectMeta{
  4117  			Name:      name,
  4118  			Namespace: namespace,
  4119  		},
  4120  		Spec: operatorsv1alpha1.InstallPlanSpec{
  4121  			ClusterServiceVersionNames: []string{"foobar"},
  4122  			Approval:                   operatorsv1alpha1.ApprovalAutomatic,
  4123  			Approved:                   true,
  4124  		},
  4125  		Status: operatorsv1alpha1.InstallPlanStatus{
  4126  			CatalogSources: []string{"catalog"},
  4127  			Phase:          phase,
  4128  			Plan: []*operatorsv1alpha1.Step{
  4129  				{
  4130  					Resource: operatorsv1alpha1.StepResource{
  4131  						CatalogSource:          "catalog",
  4132  						CatalogSourceNamespace: namespace,
  4133  						Group:                  "",
  4134  						Version:                "v1",
  4135  						Kind:                   "Foo",
  4136  						Name:                   "bar",
  4137  					},
  4138  					Status: operatorsv1alpha1.StepStatusUnknown,
  4139  				},
  4140  			},
  4141  		},
  4142  	}
  4143  }
  4144  
  4145  func hasCondition(ip *operatorsv1alpha1.InstallPlan, expectedCondition operatorsv1alpha1.InstallPlanCondition) bool {
  4146  	for _, cond := range ip.Status.Conditions {
  4147  		if cond.Type == expectedCondition.Type && cond.Message == expectedCondition.Message && cond.Status == expectedCondition.Status {
  4148  			return true
  4149  		}
  4150  	}
  4151  	return false
  4152  }