github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/olm/operator_test.go (about)

     1  package olm
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/x509"
     9  	"crypto/x509/pkix"
    10  	"encoding/pem"
    11  	"errors"
    12  	"fmt"
    13  	"math"
    14  	"math/big"
    15  	"reflect"
    16  	"sort"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	configfake "github.com/openshift/client-go/config/clientset/versioned/fake"
    23  	hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
    24  	"github.com/sirupsen/logrus"
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    28  	appsv1 "k8s.io/api/apps/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	rbacv1 "k8s.io/api/rbac/v1"
    31  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    32  	apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
    33  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    34  	"k8s.io/apimachinery/pkg/api/meta"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/labels"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    40  	"k8s.io/apimachinery/pkg/util/intstr"
    41  	"k8s.io/apimachinery/pkg/util/wait"
    42  	k8sfake "k8s.io/client-go/kubernetes/fake"
    43  	k8sscheme "k8s.io/client-go/kubernetes/scheme"
    44  	metadatafake "k8s.io/client-go/metadata/fake"
    45  	"k8s.io/client-go/pkg/version"
    46  	"k8s.io/client-go/rest"
    47  	"k8s.io/client-go/tools/cache"
    48  	"k8s.io/client-go/tools/record"
    49  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    50  	apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
    51  	utilclock "k8s.io/utils/clock"
    52  	utilclocktesting "k8s.io/utils/clock/testing"
    53  
    54  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    55  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    56  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    57  	clienttesting "k8s.io/client-go/testing"
    58  
    59  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    60  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
    61  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs"
    62  	olmerrors "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/errors"
    63  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    64  	resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    65  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
    66  	csvutility "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/csv"
    67  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/labeler"
    68  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    69  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
    70  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    71  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
    72  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/scoped"
    73  )
    74  
    75  type TestStrategy struct{}
    76  
    77  func (t *TestStrategy) GetStrategyName() string {
    78  	return "teststrategy"
    79  }
    80  
    81  type TestInstaller struct {
    82  	installErr      error
    83  	checkInstallErr error
    84  }
    85  
    86  func NewTestInstaller(installErr error, checkInstallErr error) install.StrategyInstaller {
    87  	return &TestInstaller{
    88  		installErr:      installErr,
    89  		checkInstallErr: checkInstallErr,
    90  	}
    91  }
    92  
    93  func (i *TestInstaller) Install(s install.Strategy) error {
    94  	return i.installErr
    95  }
    96  
    97  func (i *TestInstaller) CheckInstalled(s install.Strategy) (bool, error) {
    98  	if i.checkInstallErr != nil {
    99  		return false, i.checkInstallErr
   100  	}
   101  	return true, nil
   102  }
   103  
   104  func (i *TestInstaller) ShouldRotateCerts(s install.Strategy) (bool, error) {
   105  	return false, nil
   106  }
   107  
   108  func (i *TestInstaller) CertsRotateAt() time.Time {
   109  	return time.Time{}
   110  }
   111  
   112  func (i *TestInstaller) CertsRotated() bool {
   113  	return false
   114  }
   115  
   116  func ownerLabelFromCSV(name, namespace string) map[string]string {
   117  	return map[string]string{
   118  		ownerutil.OwnerKey:          name,
   119  		ownerutil.OwnerNamespaceKey: namespace,
   120  		ownerutil.OwnerKind:         v1alpha1.ClusterServiceVersionKind,
   121  	}
   122  }
   123  
   124  func addDepSpecHashLabel(t *testing.T, labels map[string]string, strategy v1alpha1.NamedInstallStrategy) map[string]string {
   125  	hash, err := hashutil.DeepHashObject(&strategy.StrategySpec.DeploymentSpecs[0].Spec)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	labels[install.DeploymentSpecHashLabelKey] = hash
   130  	return labels
   131  }
   132  
   133  func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList {
   134  	apis := []*metav1.APIResourceList{}
   135  	for _, o := range objs {
   136  		switch o := o.(type) {
   137  		case *apiextensionsv1.CustomResourceDefinition:
   138  			crd := o
   139  			apis = append(apis, &metav1.APIResourceList{
   140  				GroupVersion: metav1.GroupVersion{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name}.String(),
   141  				APIResources: []metav1.APIResource{
   142  					{
   143  						Name:         crd.GetName(),
   144  						SingularName: crd.Spec.Names.Singular,
   145  						Namespaced:   crd.Spec.Scope == apiextensionsv1.NamespaceScoped,
   146  						Group:        crd.Spec.Group,
   147  						Version:      crd.Spec.Versions[0].Name,
   148  						Kind:         crd.Spec.Names.Kind,
   149  					},
   150  				},
   151  			})
   152  		case *apiregistrationv1.APIService:
   153  			a := o
   154  			names := strings.Split(a.Name, ".")
   155  			apis = append(apis, &metav1.APIResourceList{
   156  				GroupVersion: metav1.GroupVersion{Group: names[1], Version: a.Spec.Version}.String(),
   157  				APIResources: []metav1.APIResource{
   158  					{
   159  						Name:    names[1],
   160  						Group:   names[1],
   161  						Version: a.Spec.Version,
   162  						Kind:    names[1] + "Kind",
   163  					},
   164  				},
   165  			})
   166  		}
   167  	}
   168  	return apis
   169  }
   170  
   171  // fakeOperatorConfig is the configuration for a fake operator.
   172  type fakeOperatorConfig struct {
   173  	*operatorConfig
   174  
   175  	recorder          record.EventRecorder
   176  	namespaces        []string
   177  	fakeClientOptions []clientfake.Option
   178  	clientObjs        []runtime.Object
   179  	k8sObjs           []runtime.Object
   180  	extObjs           []runtime.Object
   181  	regObjs           []runtime.Object
   182  	partialMetadata   []runtime.Object
   183  	actionLog         *[]clienttesting.Action
   184  }
   185  
   186  // fakeOperatorOption applies an option to the given fake operator configuration.
   187  type fakeOperatorOption func(*fakeOperatorConfig)
   188  
   189  func withOperatorNamespace(namespace string) fakeOperatorOption {
   190  	return func(config *fakeOperatorConfig) {
   191  		config.operatorNamespace = namespace
   192  	}
   193  }
   194  
   195  func withClock(clock utilclock.Clock) fakeOperatorOption {
   196  	return func(config *fakeOperatorConfig) {
   197  		config.clock = clock
   198  	}
   199  }
   200  
   201  func withAPIReconciler(apiReconciler APIIntersectionReconciler) fakeOperatorOption {
   202  	return func(config *fakeOperatorConfig) {
   203  		if apiReconciler != nil {
   204  			config.apiReconciler = apiReconciler
   205  		}
   206  	}
   207  }
   208  
   209  func withAPILabeler(apiLabeler labeler.Labeler) fakeOperatorOption {
   210  	return func(config *fakeOperatorConfig) {
   211  		if apiLabeler != nil {
   212  			config.apiLabeler = apiLabeler
   213  		}
   214  	}
   215  }
   216  
   217  func withNamespaces(namespaces ...string) fakeOperatorOption {
   218  	return func(config *fakeOperatorConfig) {
   219  		config.namespaces = namespaces
   220  	}
   221  }
   222  
   223  func withClientObjs(clientObjs ...runtime.Object) fakeOperatorOption {
   224  	return func(config *fakeOperatorConfig) {
   225  		config.clientObjs = clientObjs
   226  	}
   227  }
   228  
   229  func withK8sObjs(k8sObjs ...runtime.Object) fakeOperatorOption {
   230  	return func(config *fakeOperatorConfig) {
   231  		config.k8sObjs = k8sObjs
   232  	}
   233  }
   234  
   235  func withExtObjs(extObjs ...runtime.Object) fakeOperatorOption {
   236  	return func(config *fakeOperatorConfig) {
   237  		config.extObjs = extObjs
   238  	}
   239  }
   240  
   241  func withRegObjs(regObjs ...runtime.Object) fakeOperatorOption {
   242  	return func(config *fakeOperatorConfig) {
   243  		config.regObjs = regObjs
   244  	}
   245  }
   246  
   247  func withPartialMetadata(objects ...runtime.Object) fakeOperatorOption {
   248  	return func(config *fakeOperatorConfig) {
   249  		config.partialMetadata = objects
   250  	}
   251  }
   252  
   253  func withActionLog(log *[]clienttesting.Action) fakeOperatorOption {
   254  	return func(config *fakeOperatorConfig) {
   255  		config.actionLog = log
   256  	}
   257  }
   258  
   259  func withLogger(logger *logrus.Logger) fakeOperatorOption {
   260  	return func(config *fakeOperatorConfig) {
   261  		config.logger = logger
   262  	}
   263  }
   264  
   265  // NewFakeOperator creates and starts a new operator using fake clients.
   266  func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Operator, error) {
   267  	logrus.SetLevel(logrus.DebugLevel)
   268  	// Apply options to default config
   269  	config := &fakeOperatorConfig{
   270  		operatorConfig: &operatorConfig{
   271  			resyncPeriod:      queueinformer.ResyncWithJitter(5*time.Minute, 0.1),
   272  			operatorNamespace: "default",
   273  			watchedNamespaces: []string{metav1.NamespaceAll},
   274  			clock:             &utilclock.RealClock{},
   275  			logger:            logrus.New(),
   276  			strategyResolver:  &install.StrategyResolver{},
   277  			apiReconciler:     APIIntersectionReconcileFunc(ReconcileAPIIntersection),
   278  			apiLabeler:        labeler.Func(LabelSetsFor),
   279  			restConfig:        &rest.Config{},
   280  		},
   281  		recorder: &record.FakeRecorder{},
   282  		// default expected namespaces
   283  		namespaces: []string{"default", "kube-system", "kube-public"},
   284  		actionLog:  &[]clienttesting.Action{},
   285  	}
   286  	for _, option := range options {
   287  		option(config)
   288  	}
   289  
   290  	scheme := runtime.NewScheme()
   291  	if err := k8sscheme.AddToScheme(scheme); err != nil {
   292  		return nil, err
   293  	}
   294  	if err := metav1.AddMetaToScheme(scheme); err != nil {
   295  		return nil, err
   296  	}
   297  	if err := fake.AddToScheme(scheme); err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	// Create client fakes
   302  	externalFake := fake.NewReactionForwardingClientsetDecorator(config.clientObjs, config.fakeClientOptions...)
   303  	config.externalClient = externalFake
   304  	// TODO: Using the ReactionForwardingClientsetDecorator for k8s objects causes issues with adding Resources for discovery.
   305  	// For now, directly use a SimpleClientset instead.
   306  	k8sClientFake := k8sfake.NewSimpleClientset(config.k8sObjs...)
   307  	k8sClientFake.Resources = apiResourcesForObjects(append(config.extObjs, config.regObjs...))
   308  	k8sClientFake.PrependReactor("*", "*", clienttesting.ReactionFunc(func(action clienttesting.Action) (bool, runtime.Object, error) {
   309  		*config.actionLog = append(*config.actionLog, action)
   310  		return false, nil, nil
   311  	}))
   312  	apiextensionsFake := apiextensionsfake.NewSimpleClientset(config.extObjs...)
   313  	config.operatorClient = operatorclient.NewClient(k8sClientFake, apiextensionsFake, apiregistrationfake.NewSimpleClientset(config.regObjs...))
   314  	config.configClient = configfake.NewSimpleClientset()
   315  	metadataFake := metadatafake.NewSimpleMetadataClient(scheme, config.partialMetadata...)
   316  	config.metadataClient = metadataFake
   317  	// It's a travesty that we need to do this, but the fakes leave us no other option. In the API server, of course
   318  	// changes to objects are transparently exposed in the metadata client. In fake-land, we need to enforce that ourselves.
   319  	propagate := func(action clienttesting.Action) (bool, runtime.Object, error) {
   320  		var err error
   321  		switch action.GetVerb() {
   322  		case "create":
   323  			a := action.(clienttesting.CreateAction)
   324  			m := a.GetObject().(metav1.ObjectMetaAccessor).GetObjectMeta().(*metav1.ObjectMeta)
   325  			_, err = metadataFake.Resource(action.GetResource()).Namespace(action.GetNamespace()).(metadatafake.MetadataClient).CreateFake(&metav1.PartialObjectMetadata{ObjectMeta: *m}, metav1.CreateOptions{})
   326  		case "update":
   327  			a := action.(clienttesting.UpdateAction)
   328  			m := a.GetObject().(metav1.ObjectMetaAccessor).GetObjectMeta().(*metav1.ObjectMeta)
   329  			_, err = metadataFake.Resource(action.GetResource()).Namespace(action.GetNamespace()).(metadatafake.MetadataClient).UpdateFake(&metav1.PartialObjectMetadata{ObjectMeta: *m}, metav1.UpdateOptions{})
   330  		case "delete":
   331  			a := action.(clienttesting.DeleteAction)
   332  			err = metadataFake.Resource(action.GetResource()).Delete(context.TODO(), a.GetName(), metav1.DeleteOptions{})
   333  		}
   334  		return false, nil, err
   335  	}
   336  	externalFake.PrependReactor("*", "*", propagate)
   337  	apiextensionsFake.PrependReactor("*", "*", propagate)
   338  
   339  	for _, ns := range config.namespaces {
   340  		_, err := config.operatorClient.KubernetesInterface().CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, metav1.CreateOptions{})
   341  		// Ignore already-exists errors
   342  		if err != nil && !apierrors.IsAlreadyExists(err) {
   343  			return nil, err
   344  		}
   345  	}
   346  
   347  	op, err := newOperatorWithConfig(ctx, config.operatorConfig)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  	op.recorder = config.recorder
   352  
   353  	op.csvSetGenerator = csvutility.NewSetGenerator(config.logger, op.lister)
   354  	op.csvReplaceFinder = csvutility.NewReplaceFinder(config.logger, config.externalClient)
   355  	op.serviceAccountSyncer = scoped.NewUserDefinedServiceAccountSyncer(config.logger, scheme, config.operatorClient, op.client)
   356  
   357  	// Only start the operator's informers (no reconciliation)
   358  	op.RunInformers(ctx)
   359  
   360  	if ok := cache.WaitForCacheSync(ctx.Done(), op.HasSynced); !ok {
   361  		return nil, fmt.Errorf("failed to wait for caches to sync")
   362  	}
   363  
   364  	op.clientFactory = &stubClientFactory{
   365  		operatorClient:   config.operatorClient,
   366  		kubernetesClient: config.externalClient,
   367  	}
   368  
   369  	return op, nil
   370  }
   371  
   372  type fakeAPIIntersectionReconciler struct {
   373  	Result APIReconciliationResult
   374  }
   375  
   376  func (f fakeAPIIntersectionReconciler) Reconcile(resolvercache.APISet, OperatorGroupSurface, ...OperatorGroupSurface) APIReconciliationResult {
   377  	return f.Result
   378  }
   379  
   380  func buildFakeAPIIntersectionReconcilerThatReturns(result APIReconciliationResult) APIIntersectionReconciler {
   381  	return fakeAPIIntersectionReconciler{
   382  		Result: result,
   383  	}
   384  }
   385  
   386  func deployment(deploymentName, namespace, serviceAccountName string, templateAnnotations map[string]string) *appsv1.Deployment {
   387  	var (
   388  		singleInstance       = int32(1)
   389  		revisionHistoryLimit = int32(1)
   390  	)
   391  	return &appsv1.Deployment{
   392  		ObjectMeta: metav1.ObjectMeta{
   393  			Name:      deploymentName,
   394  			Namespace: namespace,
   395  			Labels:    map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   396  		},
   397  		Spec: appsv1.DeploymentSpec{
   398  			Selector: &metav1.LabelSelector{
   399  				MatchLabels: map[string]string{
   400  					"app": deploymentName,
   401  				},
   402  			},
   403  			RevisionHistoryLimit: &revisionHistoryLimit,
   404  			Replicas:             &singleInstance,
   405  			Template: corev1.PodTemplateSpec{
   406  				ObjectMeta: metav1.ObjectMeta{
   407  					Labels: map[string]string{
   408  						"app": deploymentName,
   409  					},
   410  					Annotations: templateAnnotations,
   411  				},
   412  				Spec: corev1.PodSpec{
   413  					ServiceAccountName: serviceAccountName,
   414  					Containers: []corev1.Container{
   415  						{
   416  							Name:  deploymentName + "-c1",
   417  							Image: "nginx:1.7.9",
   418  							Ports: []corev1.ContainerPort{
   419  								{
   420  									ContainerPort: 80,
   421  								},
   422  							},
   423  						},
   424  					},
   425  				},
   426  			},
   427  		},
   428  		Status: appsv1.DeploymentStatus{
   429  			Replicas:          singleInstance,
   430  			AvailableReplicas: singleInstance,
   431  			UpdatedReplicas:   singleInstance,
   432  			Conditions: []appsv1.DeploymentCondition{{
   433  				Type:   appsv1.DeploymentAvailable,
   434  				Status: corev1.ConditionTrue,
   435  			}},
   436  		},
   437  	}
   438  }
   439  
   440  func serviceAccount(name, namespace string) *corev1.ServiceAccount {
   441  	serviceAccount := &corev1.ServiceAccount{
   442  		ObjectMeta: metav1.ObjectMeta{
   443  			Name:      name,
   444  			Namespace: namespace,
   445  			Labels:    map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   446  		},
   447  	}
   448  
   449  	return serviceAccount
   450  }
   451  
   452  func service(name, namespace, deploymentName string, targetPort int, ownerReferences ...metav1.OwnerReference) *corev1.Service {
   453  	service := &corev1.Service{
   454  		ObjectMeta: metav1.ObjectMeta{
   455  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   456  		},
   457  		Spec: corev1.ServiceSpec{
   458  			Ports: []corev1.ServicePort{
   459  				{
   460  					Port:       int32(443),
   461  					TargetPort: intstr.FromInt(targetPort),
   462  				},
   463  			},
   464  			Selector: map[string]string{
   465  				"app": deploymentName,
   466  			},
   467  		},
   468  	}
   469  	service.SetName(name)
   470  	service.SetNamespace(namespace)
   471  	service.SetOwnerReferences(ownerReferences)
   472  
   473  	return service
   474  }
   475  
   476  func clusterRoleBinding(name, clusterRoleName, serviceAccountName, serviceAccountNamespace string) *rbacv1.ClusterRoleBinding {
   477  	clusterRoleBinding := &rbacv1.ClusterRoleBinding{
   478  		ObjectMeta: metav1.ObjectMeta{
   479  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   480  		},
   481  		Subjects: []rbacv1.Subject{
   482  			{
   483  				Kind:      "ServiceAccount",
   484  				APIGroup:  "",
   485  				Name:      serviceAccountName,
   486  				Namespace: serviceAccountNamespace,
   487  			},
   488  		},
   489  		RoleRef: rbacv1.RoleRef{
   490  			APIGroup: "rbac.authorization.k8s.io",
   491  			Kind:     "ClusterRole",
   492  			Name:     clusterRoleName,
   493  		},
   494  	}
   495  	clusterRoleBinding.SetName(name)
   496  
   497  	return clusterRoleBinding
   498  }
   499  
   500  func clusterRole(name string, rules []rbacv1.PolicyRule) *rbacv1.ClusterRole {
   501  	clusterRole := &rbacv1.ClusterRole{
   502  		ObjectMeta: metav1.ObjectMeta{
   503  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   504  		},
   505  		Rules: rules,
   506  	}
   507  	clusterRole.SetName(name)
   508  
   509  	return clusterRole
   510  }
   511  
   512  func role(name, namespace string, rules []rbacv1.PolicyRule) *rbacv1.Role {
   513  	role := &rbacv1.Role{
   514  		ObjectMeta: metav1.ObjectMeta{
   515  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   516  		},
   517  		Rules: rules,
   518  	}
   519  	role.SetName(name)
   520  	role.SetNamespace(namespace)
   521  
   522  	return role
   523  }
   524  
   525  func roleBinding(name, namespace, roleName, serviceAccountName, serviceAccountNamespace string) *rbacv1.RoleBinding {
   526  	roleBinding := &rbacv1.RoleBinding{
   527  		ObjectMeta: metav1.ObjectMeta{
   528  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   529  		},
   530  		Subjects: []rbacv1.Subject{
   531  			{
   532  				Kind:      "ServiceAccount",
   533  				APIGroup:  "",
   534  				Name:      serviceAccountName,
   535  				Namespace: serviceAccountNamespace,
   536  			},
   537  		},
   538  		RoleRef: rbacv1.RoleRef{
   539  			APIGroup: "rbac.authorization.k8s.io",
   540  			Kind:     "Role",
   541  			Name:     roleName,
   542  		},
   543  	}
   544  	roleBinding.SetName(name)
   545  	roleBinding.SetNamespace(namespace)
   546  
   547  	return roleBinding
   548  }
   549  
   550  func tlsSecret(name, namespace string, certPEM, privPEM []byte) *corev1.Secret {
   551  	secret := &corev1.Secret{
   552  		ObjectMeta: metav1.ObjectMeta{
   553  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   554  		},
   555  		Data: map[string][]byte{
   556  			"tls.crt": certPEM,
   557  			"tls.key": privPEM,
   558  		},
   559  		Type: corev1.SecretTypeTLS,
   560  	}
   561  	secret.SetName(name)
   562  	secret.SetNamespace(namespace)
   563  
   564  	return secret
   565  }
   566  
   567  func withCA(secret *corev1.Secret, caPEM []byte) *corev1.Secret {
   568  	secret.Data[install.OLMCAPEMKey] = caPEM
   569  	return secret
   570  }
   571  
   572  func keyPairToTLSSecret(name, namespace string, kp *certs.KeyPair) *corev1.Secret {
   573  	var privPEM []byte
   574  	var certPEM []byte
   575  
   576  	if kp != nil {
   577  		var err error
   578  		certPEM, privPEM, err = kp.ToPEM()
   579  		if err != nil {
   580  			panic(err)
   581  		}
   582  	}
   583  
   584  	return tlsSecret(name, namespace, certPEM, privPEM)
   585  }
   586  
   587  func signedServingPair(notAfter time.Time, ca *certs.KeyPair, hosts []string) *certs.KeyPair {
   588  	servingPair, err := certs.CreateSignedServingPair(notAfter, install.Organization, ca, hosts)
   589  	if err != nil {
   590  		panic(err)
   591  	}
   592  
   593  	return servingPair
   594  }
   595  
   596  func withAnnotations(obj runtime.Object, annotations map[string]string) runtime.Object {
   597  	meta, ok := obj.(metav1.Object)
   598  	if !ok {
   599  		panic("could not find metadata on object")
   600  	}
   601  	meta.SetAnnotations(annotations)
   602  	return meta.(runtime.Object)
   603  }
   604  
   605  func csvWithAnnotations(csv *v1alpha1.ClusterServiceVersion, annotations map[string]string) *v1alpha1.ClusterServiceVersion {
   606  	return withAnnotations(csv, annotations).(*v1alpha1.ClusterServiceVersion)
   607  }
   608  
   609  func withUID(obj runtime.Object, uid types.UID) runtime.Object {
   610  	meta, ok := obj.(metav1.Object)
   611  	if !ok {
   612  		panic("could not find metadata on object")
   613  	}
   614  	meta.SetUID(uid)
   615  	return meta.(runtime.Object)
   616  }
   617  
   618  func csvWithUID(csv *v1alpha1.ClusterServiceVersion, uid types.UID) *v1alpha1.ClusterServiceVersion {
   619  	return withUID(csv, uid).(*v1alpha1.ClusterServiceVersion)
   620  }
   621  
   622  func withLabels(obj runtime.Object, labels map[string]string) runtime.Object {
   623  	meta, ok := obj.(metav1.Object)
   624  	if !ok {
   625  		panic("could not find metadata on object")
   626  	}
   627  	meta.SetLabels(labels)
   628  	return meta.(runtime.Object)
   629  }
   630  
   631  func csvWithLabels(csv *v1alpha1.ClusterServiceVersion, labels map[string]string) *v1alpha1.ClusterServiceVersion {
   632  	return withLabels(csv, labels).(*v1alpha1.ClusterServiceVersion)
   633  }
   634  
   635  func addAnnotations(annotations map[string]string, add map[string]string) map[string]string {
   636  	out := map[string]string{}
   637  	for k, v := range annotations {
   638  		out[k] = v
   639  	}
   640  	for k, v := range add {
   641  		out[k] = v
   642  	}
   643  	return out
   644  }
   645  
   646  func addAnnotation(obj runtime.Object, key string, value string) runtime.Object {
   647  	meta, ok := obj.(metav1.Object)
   648  	if !ok {
   649  		panic("could not find metadata on object")
   650  	}
   651  	return withAnnotations(obj, addAnnotations(meta.GetAnnotations(), map[string]string{key: value}))
   652  }
   653  
   654  func csvWithStatusReason(csv *v1alpha1.ClusterServiceVersion, reason v1alpha1.ConditionReason) *v1alpha1.ClusterServiceVersion {
   655  	out := csv.DeepCopy()
   656  	out.Status.Reason = reason
   657  	return csv
   658  }
   659  
   660  func installStrategy(deploymentName string, permissions []v1alpha1.StrategyDeploymentPermissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy {
   661  	var singleInstance = int32(1)
   662  	strategy := v1alpha1.StrategyDetailsDeployment{
   663  		DeploymentSpecs: []v1alpha1.StrategyDeploymentSpec{
   664  			{
   665  				Name: deploymentName,
   666  				Spec: appsv1.DeploymentSpec{
   667  					Selector: &metav1.LabelSelector{
   668  						MatchLabels: map[string]string{
   669  							"app": deploymentName,
   670  						},
   671  					},
   672  					Replicas: &singleInstance,
   673  					Template: corev1.PodTemplateSpec{
   674  						ObjectMeta: metav1.ObjectMeta{
   675  							Labels: map[string]string{
   676  								"app": deploymentName,
   677  							},
   678  						},
   679  						Spec: corev1.PodSpec{
   680  							ServiceAccountName: "sa",
   681  							Containers: []corev1.Container{
   682  								{
   683  									Name:  deploymentName + "-c1",
   684  									Image: "nginx:1.7.9",
   685  									Ports: []corev1.ContainerPort{
   686  										{
   687  											ContainerPort: 80,
   688  										},
   689  									},
   690  								},
   691  							},
   692  						},
   693  					},
   694  				},
   695  			},
   696  		},
   697  		Permissions:        permissions,
   698  		ClusterPermissions: clusterPermissions,
   699  	}
   700  
   701  	return v1alpha1.NamedInstallStrategy{
   702  		StrategyName: v1alpha1.InstallStrategyNameDeployment,
   703  		StrategySpec: strategy,
   704  	}
   705  }
   706  
   707  func apiServiceInstallStrategy(deploymentName string, cahash string, permissions []v1alpha1.StrategyDeploymentPermissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy {
   708  	strategy := installStrategy(deploymentName, permissions, clusterPermissions)
   709  
   710  	strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Annotations = map[string]string{install.OLMCAHashAnnotationKey: cahash}
   711  
   712  	strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Volumes = []corev1.Volume{{
   713  		Name: "apiservice-cert",
   714  		VolumeSource: corev1.VolumeSource{
   715  			Secret: &corev1.SecretVolumeSource{
   716  				SecretName: "v1.a1-cert",
   717  				Items: []corev1.KeyToPath{
   718  					{
   719  						Key:  "tls.crt",
   720  						Path: "apiserver.crt",
   721  					},
   722  					{
   723  						Key:  "tls.key",
   724  						Path: "apiserver.key",
   725  					},
   726  				},
   727  			},
   728  		},
   729  	}}
   730  	strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{
   731  		Name:      "apiservice-cert",
   732  		MountPath: "/apiserver.local.config/certificates",
   733  	}}
   734  	return strategy
   735  }
   736  
   737  func withTemplateAnnotations(strategy v1alpha1.NamedInstallStrategy, annotations map[string]string) v1alpha1.NamedInstallStrategy {
   738  	strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Annotations = annotations
   739  	return strategy
   740  }
   741  
   742  func csv(
   743  	name, namespace, minKubeVersion, replaces string,
   744  	installStrategy v1alpha1.NamedInstallStrategy,
   745  	owned, required []*apiextensionsv1.CustomResourceDefinition,
   746  	phase v1alpha1.ClusterServiceVersionPhase,
   747  ) *v1alpha1.ClusterServiceVersion {
   748  	requiredCRDDescs := make([]v1alpha1.CRDDescription, 0)
   749  	for _, crd := range required {
   750  		requiredCRDDescs = append(requiredCRDDescs, v1alpha1.CRDDescription{Name: crd.GetName(), Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind})
   751  	}
   752  
   753  	ownedCRDDescs := make([]v1alpha1.CRDDescription, 0)
   754  	for _, crd := range owned {
   755  		ownedCRDDescs = append(ownedCRDDescs, v1alpha1.CRDDescription{Name: crd.GetName(), Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind})
   756  	}
   757  
   758  	return &v1alpha1.ClusterServiceVersion{
   759  		TypeMeta: metav1.TypeMeta{
   760  			Kind:       v1alpha1.ClusterServiceVersionKind,
   761  			APIVersion: v1alpha1.SchemeGroupVersion.String(),
   762  		},
   763  		ObjectMeta: metav1.ObjectMeta{
   764  			Name:      name,
   765  			Namespace: namespace,
   766  		},
   767  		Spec: v1alpha1.ClusterServiceVersionSpec{
   768  			MinKubeVersion:  minKubeVersion,
   769  			Replaces:        replaces,
   770  			InstallStrategy: installStrategy,
   771  			InstallModes: []v1alpha1.InstallMode{
   772  				{
   773  					Type:      v1alpha1.InstallModeTypeOwnNamespace,
   774  					Supported: true,
   775  				},
   776  				{
   777  					Type:      v1alpha1.InstallModeTypeSingleNamespace,
   778  					Supported: true,
   779  				},
   780  				{
   781  					Type:      v1alpha1.InstallModeTypeMultiNamespace,
   782  					Supported: true,
   783  				},
   784  				{
   785  					Type:      v1alpha1.InstallModeTypeAllNamespaces,
   786  					Supported: true,
   787  				},
   788  			},
   789  			CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
   790  				Owned:    ownedCRDDescs,
   791  				Required: requiredCRDDescs,
   792  			},
   793  		},
   794  		Status: v1alpha1.ClusterServiceVersionStatus{
   795  			Phase: phase,
   796  		},
   797  	}
   798  }
   799  
   800  func withConditionReason(csv *v1alpha1.ClusterServiceVersion, reason v1alpha1.ConditionReason) *v1alpha1.ClusterServiceVersion {
   801  	csv.Status.Reason = reason
   802  	return csv
   803  }
   804  
   805  func withPhase(csv *v1alpha1.ClusterServiceVersion, phase v1alpha1.ClusterServiceVersionPhase, reason v1alpha1.ConditionReason, message string, now metav1.Time) *v1alpha1.ClusterServiceVersion {
   806  	csv.SetPhase(phase, reason, message, &now)
   807  	return csv
   808  }
   809  
   810  func withCertInfo(csv *v1alpha1.ClusterServiceVersion, rotateAt metav1.Time, lastUpdated metav1.Time) *v1alpha1.ClusterServiceVersion {
   811  	csv.Status.CertsRotateAt = &rotateAt
   812  	csv.Status.CertsLastUpdated = &lastUpdated
   813  	return csv
   814  }
   815  
   816  func withAPIServices(csv *v1alpha1.ClusterServiceVersion, owned, required []v1alpha1.APIServiceDescription) *v1alpha1.ClusterServiceVersion {
   817  	csv.Spec.APIServiceDefinitions = v1alpha1.APIServiceDefinitions{
   818  		Owned:    owned,
   819  		Required: required,
   820  	}
   821  	return csv
   822  }
   823  
   824  func withInstallModes(csv *v1alpha1.ClusterServiceVersion, installModes []v1alpha1.InstallMode) *v1alpha1.ClusterServiceVersion {
   825  	csv.Spec.InstallModes = installModes
   826  	return csv
   827  }
   828  
   829  func apis(apis ...string) []v1alpha1.APIServiceDescription {
   830  	descs := []v1alpha1.APIServiceDescription{}
   831  	for _, av := range apis {
   832  		split := strings.Split(av, ".")
   833  		descs = append(descs, v1alpha1.APIServiceDescription{
   834  			Group:          split[0],
   835  			Version:        split[1],
   836  			Kind:           split[2],
   837  			DeploymentName: split[0],
   838  		})
   839  	}
   840  	return descs
   841  }
   842  
   843  func apiService(group, version, serviceName, serviceNamespace, deploymentName string, caBundle []byte, availableStatus apiregistrationv1.ConditionStatus, ownerLabel map[string]string) *apiregistrationv1.APIService {
   844  	apiService := &apiregistrationv1.APIService{
   845  		ObjectMeta: metav1.ObjectMeta{
   846  			Labels:          ownerLabel,
   847  			OwnerReferences: []metav1.OwnerReference{},
   848  		},
   849  		Spec: apiregistrationv1.APIServiceSpec{
   850  			Group:                group,
   851  			Version:              version,
   852  			GroupPriorityMinimum: int32(2000),
   853  			VersionPriority:      int32(15),
   854  			CABundle:             caBundle,
   855  			Service: &apiregistrationv1.ServiceReference{
   856  				Name:      serviceName,
   857  				Namespace: serviceNamespace,
   858  			},
   859  		},
   860  		Status: apiregistrationv1.APIServiceStatus{
   861  			Conditions: []apiregistrationv1.APIServiceCondition{
   862  				{
   863  					Type:   apiregistrationv1.Available,
   864  					Status: availableStatus,
   865  				},
   866  			},
   867  		},
   868  	}
   869  	apiServiceName := fmt.Sprintf("%s.%s", version, group)
   870  	apiService.SetName(apiServiceName)
   871  
   872  	return apiService
   873  }
   874  
   875  func crd(name, version, group string) *apiextensionsv1.CustomResourceDefinition {
   876  	return &apiextensionsv1.CustomResourceDefinition{
   877  		ObjectMeta: metav1.ObjectMeta{
   878  			Name:   name + "." + group,
   879  			Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   880  		},
   881  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   882  			Group: group,
   883  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   884  				{
   885  					Name:    version,
   886  					Storage: true,
   887  					Served:  true,
   888  				},
   889  			},
   890  			Names: apiextensionsv1.CustomResourceDefinitionNames{
   891  				Kind: name,
   892  			},
   893  		},
   894  		Status: apiextensionsv1.CustomResourceDefinitionStatus{
   895  			Conditions: []apiextensionsv1.CustomResourceDefinitionCondition{
   896  				{
   897  					Type:   apiextensionsv1.Established,
   898  					Status: apiextensionsv1.ConditionTrue,
   899  				},
   900  				{
   901  					Type:   apiextensionsv1.NamesAccepted,
   902  					Status: apiextensionsv1.ConditionTrue,
   903  				},
   904  			},
   905  		},
   906  	}
   907  }
   908  
   909  func generateCA(notAfter time.Time, organization string) (*certs.KeyPair, error) {
   910  	notBefore := time.Now()
   911  
   912  	serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
   913  	if err != nil {
   914  		return nil, err
   915  	}
   916  
   917  	caDetails := &x509.Certificate{
   918  		SerialNumber: serial,
   919  		Subject: pkix.Name{
   920  			Organization: []string{install.Organization},
   921  		},
   922  		NotBefore:             notBefore,
   923  		NotAfter:              notAfter,
   924  		IsCA:                  true,
   925  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
   926  		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   927  		BasicConstraintsValid: true,
   928  	}
   929  
   930  	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   931  	if err != nil {
   932  		return nil, err
   933  	}
   934  
   935  	publicKey := &privateKey.PublicKey
   936  	certRaw, err := x509.CreateCertificate(rand.Reader, caDetails, caDetails, publicKey, privateKey)
   937  	if err != nil {
   938  		return nil, err
   939  	}
   940  
   941  	cert, err := x509.ParseCertificate(certRaw)
   942  	if err != nil {
   943  		return nil, err
   944  	}
   945  
   946  	ca := &certs.KeyPair{
   947  		Cert: cert,
   948  		Priv: privateKey,
   949  	}
   950  
   951  	return ca, nil
   952  }
   953  
   954  func TestTransitionCSV(t *testing.T) {
   955  	logrus.SetLevel(logrus.DebugLevel)
   956  	namespace := "ns"
   957  
   958  	apiHash, err := resolvercache.APIKeyToGVKHash(opregistry.APIKey{Group: "g1", Version: "v1", Kind: "c1"})
   959  	require.NoError(t, err)
   960  
   961  	defaultOperatorGroup := &operatorsv1.OperatorGroup{
   962  		TypeMeta: metav1.TypeMeta{
   963  			Kind:       "OperatorGroup",
   964  			APIVersion: operatorsv1.SchemeGroupVersion.String(),
   965  		},
   966  		ObjectMeta: metav1.ObjectMeta{
   967  			Name:      "default",
   968  			Namespace: namespace,
   969  		},
   970  		Spec: operatorsv1.OperatorGroupSpec{},
   971  		Status: operatorsv1.OperatorGroupStatus{
   972  			Namespaces: []string{namespace},
   973  		},
   974  	}
   975  
   976  	defaultTemplateAnnotations := map[string]string{
   977  		operatorsv1.OperatorGroupTargetsAnnotationKey:   namespace,
   978  		operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace,
   979  		operatorsv1.OperatorGroupAnnotationKey:          defaultOperatorGroup.GetName(),
   980  	}
   981  
   982  	// Generate valid and expired CA fixtures
   983  	validCA, err := generateCA(time.Now().Add(10*365*24*time.Hour), install.Organization)
   984  	require.NoError(t, err)
   985  	validCAPEM, _, err := validCA.ToPEM()
   986  	require.NoError(t, err)
   987  	validCAHash := certs.PEMSHA256(validCAPEM)
   988  
   989  	expiredCA, err := generateCA(time.Now(), install.Organization)
   990  	require.NoError(t, err)
   991  	expiredCAPEM, _, err := expiredCA.ToPEM()
   992  	require.NoError(t, err)
   993  	expiredCAHash := certs.PEMSHA256(expiredCAPEM)
   994  
   995  	type csvState struct {
   996  		exists bool
   997  		phase  v1alpha1.ClusterServiceVersionPhase //nolint:structcheck
   998  		reason v1alpha1.ConditionReason
   999  	}
  1000  	type operatorConfig struct {
  1001  		apiReconciler APIIntersectionReconciler
  1002  		apiLabeler    labeler.Labeler
  1003  	}
  1004  	type initial struct {
  1005  		csvs       []*v1alpha1.ClusterServiceVersion
  1006  		clientObjs []runtime.Object
  1007  		crds       []runtime.Object
  1008  		objs       []runtime.Object
  1009  		apis       []runtime.Object
  1010  	}
  1011  	type expected struct {
  1012  		csvStates map[string]csvState
  1013  		objs      []runtime.Object
  1014  		err       map[string]error
  1015  	}
  1016  	tests := []struct {
  1017  		name     string
  1018  		config   operatorConfig
  1019  		initial  initial
  1020  		expected expected
  1021  	}{
  1022  		{
  1023  			name: "SingleCSVNoneToPending/CRD",
  1024  			initial: initial{
  1025  				csvs: []*v1alpha1.ClusterServiceVersion{
  1026  					csvWithAnnotations(csv("csv1",
  1027  						namespace,
  1028  						"0.0.0",
  1029  						"",
  1030  						installStrategy("csv1-dep1", nil, nil),
  1031  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1032  						[]*apiextensionsv1.CustomResourceDefinition{},
  1033  						v1alpha1.CSVPhaseNone,
  1034  					), defaultTemplateAnnotations),
  1035  				},
  1036  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1037  			},
  1038  			expected: expected{
  1039  				csvStates: map[string]csvState{
  1040  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1041  				},
  1042  			},
  1043  		},
  1044  		{
  1045  			name: "SingleCSVNoneToPending/APIService/Required",
  1046  			initial: initial{
  1047  				csvs: []*v1alpha1.ClusterServiceVersion{
  1048  					withAPIServices(csvWithAnnotations(csv("csv1",
  1049  						namespace,
  1050  						"0.0.0",
  1051  						"",
  1052  						installStrategy("csv1-dep1", nil, nil),
  1053  						[]*apiextensionsv1.CustomResourceDefinition{},
  1054  						[]*apiextensionsv1.CustomResourceDefinition{},
  1055  						v1alpha1.CSVPhaseNone,
  1056  					), defaultTemplateAnnotations), nil, apis("a1.corev1.a1Kind")),
  1057  				},
  1058  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "a1Kind.corev1.a1")},
  1059  			},
  1060  			expected: expected{
  1061  				csvStates: map[string]csvState{
  1062  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1063  				},
  1064  			},
  1065  		},
  1066  		{
  1067  			name: "SingleCSVPendingToFailed/BadStrategyPermissions",
  1068  			initial: initial{
  1069  				csvs: []*v1alpha1.ClusterServiceVersion{
  1070  					csvWithUID(csvWithAnnotations(csv("csv1",
  1071  						namespace,
  1072  						"0.0.0",
  1073  						"",
  1074  						installStrategy("csv1-dep1",
  1075  							nil,
  1076  							[]v1alpha1.StrategyDeploymentPermissions{
  1077  								{
  1078  									ServiceAccountName: "sa",
  1079  									Rules: []rbacv1.PolicyRule{
  1080  										{
  1081  											Verbs:           []string{"*"},
  1082  											Resources:       []string{"*"},
  1083  											NonResourceURLs: []string{"/osb"},
  1084  										},
  1085  									},
  1086  								},
  1087  							}),
  1088  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1089  						[]*apiextensionsv1.CustomResourceDefinition{},
  1090  						v1alpha1.CSVPhasePending,
  1091  					), defaultTemplateAnnotations), types.UID("csv-uid")),
  1092  				},
  1093  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1094  				crds: []runtime.Object{
  1095  					crd("c1", "v1", "g1"),
  1096  				},
  1097  				objs: []runtime.Object{
  1098  					&corev1.ServiceAccount{
  1099  						ObjectMeta: metav1.ObjectMeta{
  1100  							Name:      "sa",
  1101  							Namespace: namespace,
  1102  							OwnerReferences: []metav1.OwnerReference{
  1103  								{
  1104  									Kind: v1alpha1.ClusterServiceVersionKind,
  1105  									UID:  "csv-uid",
  1106  								},
  1107  							},
  1108  						},
  1109  					},
  1110  				},
  1111  			},
  1112  			expected: expected{
  1113  				csvStates: map[string]csvState{
  1114  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed},
  1115  				},
  1116  			},
  1117  		},
  1118  		{
  1119  			name: "SingleCSVPendingToPending/CRD",
  1120  			initial: initial{
  1121  				csvs: []*v1alpha1.ClusterServiceVersion{
  1122  					csvWithAnnotations(csv("csv1",
  1123  						namespace,
  1124  						"0.0.0",
  1125  						"",
  1126  						installStrategy("csv1-dep1", nil, nil),
  1127  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1128  						[]*apiextensionsv1.CustomResourceDefinition{},
  1129  						v1alpha1.CSVPhasePending,
  1130  					), defaultTemplateAnnotations),
  1131  				},
  1132  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1133  				crds:       []runtime.Object{},
  1134  			},
  1135  			expected: expected{
  1136  				csvStates: map[string]csvState{
  1137  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1138  				},
  1139  				err: map[string]error{
  1140  					"csv1": ErrRequirementsNotMet,
  1141  				},
  1142  			},
  1143  		},
  1144  		{
  1145  			name: "SingleCSVPendingToPending/APIService/Required/Missing",
  1146  			initial: initial{
  1147  				csvs: []*v1alpha1.ClusterServiceVersion{
  1148  					withAPIServices(csvWithAnnotations(csv("csv1",
  1149  						namespace,
  1150  						"0.0.0",
  1151  						"",
  1152  						installStrategy("csv1-dep1", nil, nil),
  1153  						[]*apiextensionsv1.CustomResourceDefinition{},
  1154  						[]*apiextensionsv1.CustomResourceDefinition{},
  1155  						v1alpha1.CSVPhasePending,
  1156  					), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")),
  1157  				},
  1158  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1159  			},
  1160  			expected: expected{
  1161  				csvStates: map[string]csvState{
  1162  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1163  				},
  1164  				err: map[string]error{
  1165  					"csv1": ErrRequirementsNotMet,
  1166  				},
  1167  			},
  1168  		},
  1169  		{
  1170  			name: "SingleCSVPendingToPending/APIService/Required/Unavailable",
  1171  			initial: initial{
  1172  				csvs: []*v1alpha1.ClusterServiceVersion{
  1173  					withAPIServices(csvWithAnnotations(csv("csv1",
  1174  						namespace,
  1175  						"0.0.0",
  1176  						"",
  1177  						installStrategy("csv1-dep1", nil, nil),
  1178  						[]*apiextensionsv1.CustomResourceDefinition{},
  1179  						[]*apiextensionsv1.CustomResourceDefinition{},
  1180  						v1alpha1.CSVPhasePending,
  1181  					), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")),
  1182  				},
  1183  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1184  				apis:       []runtime.Object{apiService("a1", "v1", "", "", "", validCAPEM, apiregistrationv1.ConditionFalse, ownerLabelFromCSV("csv1", namespace))},
  1185  			},
  1186  			expected: expected{
  1187  				csvStates: map[string]csvState{
  1188  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1189  				},
  1190  				err: map[string]error{
  1191  					"csv1": ErrRequirementsNotMet,
  1192  				},
  1193  			},
  1194  		},
  1195  		{
  1196  			name: "SingleCSVPendingToPending/APIService/Required/Unknown",
  1197  			initial: initial{
  1198  				csvs: []*v1alpha1.ClusterServiceVersion{
  1199  					withAPIServices(csvWithAnnotations(csv("csv1",
  1200  						namespace,
  1201  						"0.0.0",
  1202  						"",
  1203  						installStrategy("csv1-dep1", nil, nil),
  1204  						[]*apiextensionsv1.CustomResourceDefinition{},
  1205  						[]*apiextensionsv1.CustomResourceDefinition{},
  1206  						v1alpha1.CSVPhasePending,
  1207  					), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")),
  1208  				},
  1209  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1210  				apis:       []runtime.Object{apiService("a1", "v1", "", "", "", validCAPEM, apiregistrationv1.ConditionUnknown, ownerLabelFromCSV("csv1", namespace))},
  1211  			},
  1212  			expected: expected{
  1213  				csvStates: map[string]csvState{
  1214  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1215  				},
  1216  				err: map[string]error{
  1217  					"csv1": ErrRequirementsNotMet,
  1218  				},
  1219  			},
  1220  		},
  1221  		{
  1222  			name: "SingleCSVPendingToPending/APIService/Owned/DeploymentNotFound",
  1223  			initial: initial{
  1224  				csvs: []*v1alpha1.ClusterServiceVersion{
  1225  					withAPIServices(csvWithAnnotations(csv("csv1",
  1226  						namespace,
  1227  						"0.0.0",
  1228  						"",
  1229  						installStrategy("b1", nil, nil),
  1230  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1231  						[]*apiextensionsv1.CustomResourceDefinition{},
  1232  						v1alpha1.CSVPhasePending,
  1233  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil),
  1234  				},
  1235  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")},
  1236  				crds: []runtime.Object{
  1237  					crd("c1", "v1", "g1"),
  1238  				},
  1239  			},
  1240  			expected: expected{
  1241  				csvStates: map[string]csvState{
  1242  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  1243  				},
  1244  				err: map[string]error{
  1245  					"csv1": ErrRequirementsNotMet,
  1246  				},
  1247  			},
  1248  		},
  1249  		{
  1250  			name: "CSVPendingToFailed/CRDOwnerConflict",
  1251  			initial: initial{
  1252  				csvs: []*v1alpha1.ClusterServiceVersion{
  1253  					csvWithAnnotations(csv("csv1",
  1254  						namespace,
  1255  						"0.0.0",
  1256  						"",
  1257  						installStrategy("csv1-dep1", nil, nil),
  1258  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1259  						[]*apiextensionsv1.CustomResourceDefinition{},
  1260  						v1alpha1.CSVPhaseSucceeded,
  1261  					), defaultTemplateAnnotations),
  1262  					csvWithAnnotations(csv("csv2",
  1263  						namespace,
  1264  						"0.0.0",
  1265  						"",
  1266  						installStrategy("csv2-dep1", nil, nil),
  1267  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1268  						[]*apiextensionsv1.CustomResourceDefinition{},
  1269  						v1alpha1.CSVPhasePending,
  1270  					), defaultTemplateAnnotations),
  1271  				},
  1272  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1273  				crds: []runtime.Object{
  1274  					crd("c1", "v1", "g1"),
  1275  				},
  1276  				objs: []runtime.Object{
  1277  					withLabels(
  1278  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  1279  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  1280  					),
  1281  				},
  1282  			},
  1283  			expected: expected{
  1284  				csvStates: map[string]csvState{
  1285  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  1286  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonOwnerConflict},
  1287  				},
  1288  				err: map[string]error{
  1289  					"csv2": ErrCRDOwnerConflict,
  1290  				},
  1291  			},
  1292  		},
  1293  		{
  1294  			name: "CSVPendingToFailed/APIServiceOwnerConflict",
  1295  			initial: initial{
  1296  				csvs: []*v1alpha1.ClusterServiceVersion{
  1297  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1298  						namespace,
  1299  						"0.0.0",
  1300  						"",
  1301  						installStrategy("a1", nil, nil),
  1302  						[]*apiextensionsv1.CustomResourceDefinition{},
  1303  						[]*apiextensionsv1.CustomResourceDefinition{},
  1304  						v1alpha1.CSVPhaseSucceeded,
  1305  					), defaultTemplateAnnotations),
  1306  						apis("a1.v1.a1Kind"), nil), metav1.NewTime(time.Now().Add(24*time.Hour)), metav1.NewTime(time.Now())),
  1307  					withAPIServices(csvWithAnnotations(csv("csv2",
  1308  						namespace,
  1309  						"0.0.0",
  1310  						"",
  1311  						installStrategy("a1", nil, nil),
  1312  						[]*apiextensionsv1.CustomResourceDefinition{},
  1313  						[]*apiextensionsv1.CustomResourceDefinition{},
  1314  						v1alpha1.CSVPhasePending,
  1315  					), defaultTemplateAnnotations),
  1316  						apis("a1.v1.a1Kind"), nil),
  1317  				},
  1318  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "a1Kind.v1.a1")},
  1319  				apis:       []runtime.Object{apiService("a1", "v1", "a1-service", namespace, "", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace))},
  1320  				objs: []runtime.Object{
  1321  					withLabels(
  1322  						deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1323  							install.OLMCAHashAnnotationKey: validCAHash,
  1324  						})),
  1325  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(apiServiceInstallStrategy("a1", validCAHash, nil, nil), addAnnotations(defaultTemplateAnnotations, map[string]string{
  1326  							install.OLMCAHashAnnotationKey: validCAHash,
  1327  						}))),
  1328  					),
  1329  					withAnnotations(keyPairToTLSSecret("a1-service-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"a1-service.ns", "a1-service.ns.svc"})), map[string]string{
  1330  						install.OLMCAHashAnnotationKey: validCAHash,
  1331  					}),
  1332  					service("a1", namespace, "a1", 80),
  1333  					serviceAccount("sa", namespace),
  1334  					role("a1-cert", namespace, []rbacv1.PolicyRule{
  1335  						{
  1336  							Verbs:         []string{"get"},
  1337  							APIGroups:     []string{""},
  1338  							Resources:     []string{"secrets"},
  1339  							ResourceNames: []string{"a1-service-cert"},
  1340  						},
  1341  					}),
  1342  					roleBinding("a1-service-cert", namespace, "a1-cert", "sa", namespace),
  1343  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1344  						{
  1345  							Verbs:         []string{"get"},
  1346  							APIGroups:     []string{""},
  1347  							Resources:     []string{"configmaps"},
  1348  							ResourceNames: []string{"extension-apiserver-authentication"},
  1349  						},
  1350  					}),
  1351  					roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1352  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1353  						{
  1354  							Verbs:     []string{"create"},
  1355  							APIGroups: []string{"authentication.k8s.io"},
  1356  							Resources: []string{"tokenreviews"},
  1357  						},
  1358  						{
  1359  							Verbs:     []string{"create"},
  1360  							APIGroups: []string{"authentication.k8s.io"},
  1361  							Resources: []string{"subjectaccessreviews"},
  1362  						},
  1363  					}),
  1364  					clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1365  				},
  1366  			},
  1367  			expected: expected{
  1368  				csvStates: map[string]csvState{
  1369  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  1370  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonOwnerConflict},
  1371  				},
  1372  				err: map[string]error{
  1373  					"csv2": ErrAPIServiceOwnerConflict,
  1374  				},
  1375  			},
  1376  		},
  1377  		{
  1378  			name: "SingleCSVFailedToPending/Deployment",
  1379  			initial: initial{
  1380  				csvs: []*v1alpha1.ClusterServiceVersion{
  1381  					csvWithAnnotations(csv("csv1",
  1382  						namespace,
  1383  						"0.0.0",
  1384  						"",
  1385  						installStrategy("a1", nil, nil),
  1386  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1387  						[]*apiextensionsv1.CustomResourceDefinition{},
  1388  						v1alpha1.CSVPhaseFailed,
  1389  					), defaultTemplateAnnotations),
  1390  				},
  1391  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1392  				crds: []runtime.Object{
  1393  					crd("c1", "v1", "g1"),
  1394  				},
  1395  			},
  1396  			expected: expected{
  1397  				csvStates: map[string]csvState{
  1398  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsReinstall},
  1399  				},
  1400  			},
  1401  		},
  1402  		{
  1403  			name: "SingleCSVFailedToPending/CRD",
  1404  			initial: initial{
  1405  				csvs: []*v1alpha1.ClusterServiceVersion{
  1406  					csvWithAnnotations(csv("csv1",
  1407  						namespace,
  1408  						"0.0.0",
  1409  						"",
  1410  						installStrategy("a1", nil, nil),
  1411  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1412  						[]*apiextensionsv1.CustomResourceDefinition{},
  1413  						v1alpha1.CSVPhaseFailed,
  1414  					), defaultTemplateAnnotations),
  1415  				},
  1416  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1417  				objs: []runtime.Object{
  1418  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  1419  				},
  1420  			},
  1421  			expected: expected{
  1422  				csvStates: map[string]csvState{
  1423  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsNotMet},
  1424  				},
  1425  			},
  1426  		},
  1427  		{
  1428  			name: "SingleCSVPendingToInstallReady/CRD",
  1429  			initial: initial{
  1430  				csvs: []*v1alpha1.ClusterServiceVersion{
  1431  					csvWithAnnotations(csv("csv1",
  1432  						namespace,
  1433  						"0.0.0",
  1434  						"",
  1435  						installStrategy("csv1-dep1", nil, nil),
  1436  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1437  						[]*apiextensionsv1.CustomResourceDefinition{},
  1438  						v1alpha1.CSVPhasePending,
  1439  					), defaultTemplateAnnotations),
  1440  				},
  1441  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1442  				crds: []runtime.Object{
  1443  					crd("c1", "v1", "g1"),
  1444  				},
  1445  			},
  1446  			expected: expected{
  1447  				csvStates: map[string]csvState{
  1448  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady},
  1449  				},
  1450  			},
  1451  		},
  1452  		{
  1453  			name: "SingleCSVPendingToInstallReady/APIService/Required",
  1454  			initial: initial{
  1455  				csvs: []*v1alpha1.ClusterServiceVersion{
  1456  					withAPIServices(csvWithAnnotations(csv("csv1",
  1457  						namespace,
  1458  						"0.0.0",
  1459  						"",
  1460  						installStrategy("csv1-dep1", nil, nil),
  1461  						[]*apiextensionsv1.CustomResourceDefinition{},
  1462  						[]*apiextensionsv1.CustomResourceDefinition{},
  1463  						v1alpha1.CSVPhasePending,
  1464  					), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")),
  1465  				},
  1466  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1467  				apis:       []runtime.Object{apiService("a1", "v1", "", "", "", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace))},
  1468  			},
  1469  			expected: expected{
  1470  				csvStates: map[string]csvState{
  1471  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady},
  1472  				},
  1473  			},
  1474  		},
  1475  		{
  1476  			name: "SingleCSVInstallReadyToInstalling",
  1477  			initial: initial{
  1478  				csvs: []*v1alpha1.ClusterServiceVersion{
  1479  					csvWithAnnotations(csv("csv1",
  1480  						namespace,
  1481  						"0.0.0",
  1482  						"",
  1483  						installStrategy("csv1-dep1", nil, nil),
  1484  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1485  						[]*apiextensionsv1.CustomResourceDefinition{},
  1486  						v1alpha1.CSVPhaseInstallReady,
  1487  					), defaultTemplateAnnotations),
  1488  				},
  1489  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")},
  1490  				crds: []runtime.Object{
  1491  					crd("c1", "v1", "g1"),
  1492  				},
  1493  			},
  1494  			expected: expected{
  1495  				csvStates: map[string]csvState{
  1496  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseInstalling},
  1497  				},
  1498  			},
  1499  		},
  1500  		{
  1501  			name: "SingleCSVInstallReadyToInstalling/APIService/Owned",
  1502  			initial: initial{
  1503  				csvs: []*v1alpha1.ClusterServiceVersion{
  1504  					withAPIServices(csvWithAnnotations(csv("csv1",
  1505  						namespace,
  1506  						"0.0.0",
  1507  						"",
  1508  						installStrategy("a1", nil, nil),
  1509  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1510  						[]*apiextensionsv1.CustomResourceDefinition{},
  1511  						v1alpha1.CSVPhaseInstallReady,
  1512  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil),
  1513  				},
  1514  				objs: []runtime.Object{
  1515  					// Note: Ideally we would not pre-create these objects, but fake client does not support
  1516  					// creation through SSA, see issue here: https://github.com/kubernetes/kubernetes/issues/115598
  1517  					// Once resolved, these objects and others in this file may be removed.
  1518  					roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1519  					service("a1-service", namespace, "a1", 80),
  1520  					clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1521  				},
  1522  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")},
  1523  				crds: []runtime.Object{
  1524  					crd("c1", "v1", "g1"),
  1525  				},
  1526  			},
  1527  			expected: expected{
  1528  				csvStates: map[string]csvState{
  1529  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseInstalling},
  1530  				},
  1531  			},
  1532  		},
  1533  		{
  1534  			name: "SingleCSVSucceededToPending/APIService/Owned/CertRotation",
  1535  			initial: initial{
  1536  				csvs: []*v1alpha1.ClusterServiceVersion{
  1537  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1538  						namespace,
  1539  						"0.0.0",
  1540  						"",
  1541  						installStrategy("a1", nil, nil),
  1542  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1543  						[]*apiextensionsv1.CustomResourceDefinition{},
  1544  						v1alpha1.CSVPhaseSucceeded,
  1545  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1546  				},
  1547  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1548  				apis: []runtime.Object{
  1549  					apiService("a1", "v1", "a1-service", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1550  				},
  1551  				objs: []runtime.Object{
  1552  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1553  						install.OLMCAHashAnnotationKey: validCAHash,
  1554  					})),
  1555  					withLabels(withAnnotations(keyPairToTLSSecret("a1-service-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"a1-service.ns", "a1-service.ns.svc"})), map[string]string{
  1556  						install.OLMCAHashAnnotationKey: validCAHash,
  1557  					}), map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}),
  1558  					service("a1-service", namespace, "a1", 80),
  1559  					serviceAccount("sa", namespace),
  1560  					role("a1-service-cert", namespace, []rbacv1.PolicyRule{
  1561  						{
  1562  							Verbs:         []string{"get"},
  1563  							APIGroups:     []string{""},
  1564  							Resources:     []string{"secrets"},
  1565  							ResourceNames: []string{"a1-service-cert"},
  1566  						},
  1567  					}),
  1568  					roleBinding("a1-service-cert", namespace, "a1-service-cert", "sa", namespace),
  1569  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1570  						{
  1571  							Verbs:         []string{"get"},
  1572  							APIGroups:     []string{""},
  1573  							Resources:     []string{"configmaps"},
  1574  							ResourceNames: []string{"extension-apiserver-authentication"},
  1575  						},
  1576  					}),
  1577  					roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1578  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1579  						{
  1580  							Verbs:     []string{"create"},
  1581  							APIGroups: []string{"authentication.k8s.io"},
  1582  							Resources: []string{"tokenreviews"},
  1583  						},
  1584  						{
  1585  							Verbs:     []string{"create"},
  1586  							APIGroups: []string{"authentication.k8s.io"},
  1587  							Resources: []string{"subjectaccessreviews"},
  1588  						},
  1589  					}),
  1590  					clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1591  				},
  1592  				crds: []runtime.Object{
  1593  					crd("c1", "v1", "g1"),
  1594  				},
  1595  			},
  1596  			expected: expected{
  1597  				csvStates: map[string]csvState{
  1598  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsCertRotation},
  1599  				},
  1600  			},
  1601  		},
  1602  		{
  1603  			name: "SingleCSVSucceededToFailed/APIService/Owned/BadCAHash/Deployment",
  1604  			initial: initial{
  1605  				csvs: []*v1alpha1.ClusterServiceVersion{
  1606  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1607  						namespace,
  1608  						"0.0.0",
  1609  						"",
  1610  						installStrategy("a1", nil, nil),
  1611  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1612  						[]*apiextensionsv1.CustomResourceDefinition{},
  1613  						v1alpha1.CSVPhaseSucceeded,
  1614  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1615  				},
  1616  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1617  				apis: []runtime.Object{
  1618  					apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1619  				},
  1620  				objs: []runtime.Object{
  1621  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1622  						install.OLMCAHashAnnotationKey: "a-pretty-bad-hash",
  1623  					})),
  1624  					withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{
  1625  						install.OLMCAHashAnnotationKey: validCAHash,
  1626  					}),
  1627  					service("v1-a1", namespace, "a1", 80),
  1628  					serviceAccount("sa", namespace),
  1629  					role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
  1630  						{
  1631  							Verbs:         []string{"get"},
  1632  							APIGroups:     []string{""},
  1633  							Resources:     []string{"secrets"},
  1634  							ResourceNames: []string{"v1.a1-cert"},
  1635  						},
  1636  					}),
  1637  					roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
  1638  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1639  						{
  1640  							Verbs:         []string{"get"},
  1641  							APIGroups:     []string{""},
  1642  							Resources:     []string{"configmaps"},
  1643  							ResourceNames: []string{"extension-apiserver-authentication"},
  1644  						},
  1645  					}),
  1646  					roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1647  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1648  						{
  1649  							Verbs:     []string{"create"},
  1650  							APIGroups: []string{"authentication.k8s.io"},
  1651  							Resources: []string{"tokenreviews"},
  1652  						},
  1653  						{
  1654  							Verbs:     []string{"create"},
  1655  							APIGroups: []string{"authentication.k8s.io"},
  1656  							Resources: []string{"subjectaccessreviews"},
  1657  						},
  1658  					}),
  1659  					clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1660  				},
  1661  				crds: []runtime.Object{
  1662  					crd("c1", "v1", "g1"),
  1663  				},
  1664  			},
  1665  			expected: expected{
  1666  				csvStates: map[string]csvState{
  1667  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue},
  1668  				},
  1669  			},
  1670  		},
  1671  		{
  1672  			name: "SingleCSVSucceededToFailed/APIService/Owned/BadCAHash/Secret",
  1673  			initial: initial{
  1674  				csvs: []*v1alpha1.ClusterServiceVersion{
  1675  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1676  						namespace,
  1677  						"0.0.0",
  1678  						"",
  1679  						installStrategy("a1", nil, nil),
  1680  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1681  						[]*apiextensionsv1.CustomResourceDefinition{},
  1682  						v1alpha1.CSVPhaseSucceeded,
  1683  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1684  				},
  1685  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1686  				apis: []runtime.Object{
  1687  					apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1688  				},
  1689  				objs: []runtime.Object{
  1690  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1691  						install.OLMCAHashAnnotationKey: validCAHash,
  1692  					})),
  1693  					withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{
  1694  						install.OLMCAHashAnnotationKey: "also-a-pretty-bad-hash",
  1695  					}),
  1696  					service("v1-a1", namespace, "a1", 80),
  1697  					serviceAccount("sa", namespace),
  1698  					role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
  1699  						{
  1700  							Verbs:         []string{"get"},
  1701  							APIGroups:     []string{""},
  1702  							Resources:     []string{"secrets"},
  1703  							ResourceNames: []string{"v1.a1-cert"},
  1704  						},
  1705  					}),
  1706  					roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
  1707  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1708  						{
  1709  							Verbs:         []string{"get"},
  1710  							APIGroups:     []string{""},
  1711  							Resources:     []string{"configmaps"},
  1712  							ResourceNames: []string{"extension-apiserver-authentication"},
  1713  						},
  1714  					}),
  1715  					roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1716  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1717  						{
  1718  							Verbs:     []string{"create"},
  1719  							APIGroups: []string{"authentication.k8s.io"},
  1720  							Resources: []string{"tokenreviews"},
  1721  						},
  1722  						{
  1723  							Verbs:     []string{"create"},
  1724  							APIGroups: []string{"authentication.k8s.io"},
  1725  							Resources: []string{"subjectaccessreviews"},
  1726  						},
  1727  					}),
  1728  					clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1729  				},
  1730  				crds: []runtime.Object{
  1731  					crd("c1", "v1", "g1"),
  1732  				},
  1733  			},
  1734  			expected: expected{
  1735  				csvStates: map[string]csvState{
  1736  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue},
  1737  				},
  1738  			},
  1739  		},
  1740  		{
  1741  			name: "SingleCSVSucceededToFailed/APIService/Owned/BadCAHash/DeploymentAndSecret",
  1742  			initial: initial{
  1743  				csvs: []*v1alpha1.ClusterServiceVersion{
  1744  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1745  						namespace,
  1746  						"0.0.0",
  1747  						"",
  1748  						installStrategy("a1", nil, nil),
  1749  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1750  						[]*apiextensionsv1.CustomResourceDefinition{},
  1751  						v1alpha1.CSVPhaseSucceeded,
  1752  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1753  				},
  1754  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1755  				apis: []runtime.Object{
  1756  					apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1757  				},
  1758  				objs: []runtime.Object{
  1759  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1760  						install.OLMCAHashAnnotationKey: "a-pretty-bad-hash",
  1761  					})),
  1762  					withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{
  1763  						install.OLMCAHashAnnotationKey: "also-a-pretty-bad-hash",
  1764  					}),
  1765  					service("v1-a1", namespace, "a1", 80),
  1766  					serviceAccount("sa", namespace),
  1767  					role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
  1768  						{
  1769  							Verbs:         []string{"get"},
  1770  							APIGroups:     []string{""},
  1771  							Resources:     []string{"secrets"},
  1772  							ResourceNames: []string{"v1.a1-cert"},
  1773  						},
  1774  					}),
  1775  					roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
  1776  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1777  						{
  1778  							Verbs:         []string{"get"},
  1779  							APIGroups:     []string{""},
  1780  							Resources:     []string{"configmaps"},
  1781  							ResourceNames: []string{"extension-apiserver-authentication"},
  1782  						},
  1783  					}),
  1784  					roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1785  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1786  						{
  1787  							Verbs:     []string{"create"},
  1788  							APIGroups: []string{"authentication.k8s.io"},
  1789  							Resources: []string{"tokenreviews"},
  1790  						},
  1791  						{
  1792  							Verbs:     []string{"create"},
  1793  							APIGroups: []string{"authentication.k8s.io"},
  1794  							Resources: []string{"subjectaccessreviews"},
  1795  						},
  1796  					}),
  1797  					clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1798  				},
  1799  				crds: []runtime.Object{
  1800  					crd("c1", "v1", "g1"),
  1801  				},
  1802  			},
  1803  			expected: expected{
  1804  				csvStates: map[string]csvState{
  1805  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue},
  1806  				},
  1807  			},
  1808  		},
  1809  		{
  1810  			name: "SingleCSVSucceededToFailed/APIService/Owned/BadCA",
  1811  			initial: initial{
  1812  				csvs: []*v1alpha1.ClusterServiceVersion{
  1813  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1814  						namespace,
  1815  						"0.0.0",
  1816  						"",
  1817  						installStrategy("a1", nil, nil),
  1818  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1819  						[]*apiextensionsv1.CustomResourceDefinition{},
  1820  						v1alpha1.CSVPhaseSucceeded,
  1821  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1822  				},
  1823  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1824  				apis: []runtime.Object{
  1825  					apiService("a1", "v1", "v1-a1", namespace, "a1", []byte("a-bad-ca"), apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1826  				},
  1827  				objs: []runtime.Object{
  1828  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1829  						install.OLMCAHashAnnotationKey: validCAHash,
  1830  					})),
  1831  					withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{
  1832  						install.OLMCAHashAnnotationKey: validCAHash,
  1833  					}),
  1834  					service("v1-a1", namespace, "a1", 80),
  1835  					serviceAccount("sa", namespace),
  1836  					role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
  1837  						{
  1838  							Verbs:         []string{"get"},
  1839  							APIGroups:     []string{""},
  1840  							Resources:     []string{"secrets"},
  1841  							ResourceNames: []string{"v1.a1-cert"},
  1842  						},
  1843  					}),
  1844  					roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
  1845  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1846  						{
  1847  							Verbs:         []string{"get"},
  1848  							APIGroups:     []string{""},
  1849  							Resources:     []string{"configmaps"},
  1850  							ResourceNames: []string{"extension-apiserver-authentication"},
  1851  						},
  1852  					}),
  1853  					roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1854  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1855  						{
  1856  							Verbs:     []string{"create"},
  1857  							APIGroups: []string{"authentication.k8s.io"},
  1858  							Resources: []string{"tokenreviews"},
  1859  						},
  1860  						{
  1861  							Verbs:     []string{"create"},
  1862  							APIGroups: []string{"authentication.k8s.io"},
  1863  							Resources: []string{"subjectaccessreviews"},
  1864  						},
  1865  					}),
  1866  					clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1867  				},
  1868  				crds: []runtime.Object{
  1869  					crd("c1", "v1", "g1"),
  1870  				},
  1871  			},
  1872  			expected: expected{
  1873  				csvStates: map[string]csvState{
  1874  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue},
  1875  				},
  1876  			},
  1877  		},
  1878  		{
  1879  			name: "SingleCSVSucceededToFailed/APIService/Owned/BadServingCert",
  1880  			initial: initial{
  1881  				csvs: []*v1alpha1.ClusterServiceVersion{
  1882  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1883  						namespace,
  1884  						"0.0.0",
  1885  						"",
  1886  						installStrategy("a1", nil, nil),
  1887  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1888  						[]*apiextensionsv1.CustomResourceDefinition{},
  1889  						v1alpha1.CSVPhaseSucceeded,
  1890  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1891  				},
  1892  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1893  				apis: []runtime.Object{
  1894  					apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1895  				},
  1896  				objs: []runtime.Object{
  1897  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1898  						install.OLMCAHashAnnotationKey: validCAHash,
  1899  					})),
  1900  					withAnnotations(tlsSecret("v1.a1-cert", namespace, []byte("bad-cert"), []byte("bad-key")), map[string]string{
  1901  						install.OLMCAHashAnnotationKey: validCAHash,
  1902  					}),
  1903  					service("v1-a1", namespace, "a1", 80),
  1904  					serviceAccount("sa", namespace),
  1905  					role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
  1906  						{
  1907  							Verbs:         []string{"get"},
  1908  							APIGroups:     []string{""},
  1909  							Resources:     []string{"secrets"},
  1910  							ResourceNames: []string{"v1.a1-cert"},
  1911  						},
  1912  					}),
  1913  					roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
  1914  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1915  						{
  1916  							Verbs:         []string{"get"},
  1917  							APIGroups:     []string{""},
  1918  							Resources:     []string{"configmaps"},
  1919  							ResourceNames: []string{"extension-apiserver-authentication"},
  1920  						},
  1921  					}),
  1922  					roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1923  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1924  						{
  1925  							Verbs:     []string{"create"},
  1926  							APIGroups: []string{"authentication.k8s.io"},
  1927  							Resources: []string{"tokenreviews"},
  1928  						},
  1929  						{
  1930  							Verbs:     []string{"create"},
  1931  							APIGroups: []string{"authentication.k8s.io"},
  1932  							Resources: []string{"subjectaccessreviews"},
  1933  						},
  1934  					}),
  1935  					clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  1936  				},
  1937  				crds: []runtime.Object{
  1938  					crd("c1", "v1", "g1"),
  1939  				},
  1940  			},
  1941  			expected: expected{
  1942  				csvStates: map[string]csvState{
  1943  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue},
  1944  				},
  1945  			},
  1946  		},
  1947  		{
  1948  			name: "SingleCSVSucceededToFailed/APIService/Owned/ExpiredCA",
  1949  			initial: initial{
  1950  				csvs: []*v1alpha1.ClusterServiceVersion{
  1951  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  1952  						namespace,
  1953  						"0.0.0",
  1954  						"",
  1955  						installStrategy("a1", nil, nil),
  1956  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  1957  						[]*apiextensionsv1.CustomResourceDefinition{},
  1958  						v1alpha1.CSVPhaseSucceeded,
  1959  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  1960  				},
  1961  				clientObjs: []runtime.Object{defaultOperatorGroup},
  1962  				apis: []runtime.Object{
  1963  					apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  1964  				},
  1965  				objs: []runtime.Object{
  1966  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  1967  						install.OLMCAHashAnnotationKey: expiredCAHash,
  1968  					})),
  1969  					withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{
  1970  						install.OLMCAHashAnnotationKey: expiredCAHash,
  1971  					}),
  1972  					service(install.ServiceName("a1"), namespace, "a1", 80),
  1973  					serviceAccount("sa", namespace),
  1974  					role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{
  1975  						{
  1976  							Verbs:         []string{"get"},
  1977  							APIGroups:     []string{""},
  1978  							Resources:     []string{"secrets"},
  1979  							ResourceNames: []string{install.SecretName(install.ServiceName("a1"))},
  1980  						},
  1981  					}),
  1982  					roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace),
  1983  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  1984  						{
  1985  							Verbs:         []string{"get"},
  1986  							APIGroups:     []string{""},
  1987  							Resources:     []string{"configmaps"},
  1988  							ResourceNames: []string{"extension-apiserver-authentication"},
  1989  						},
  1990  					}),
  1991  					roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  1992  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  1993  						{
  1994  							Verbs:     []string{"create"},
  1995  							APIGroups: []string{"authentication.k8s.io"},
  1996  							Resources: []string{"tokenreviews"},
  1997  						},
  1998  						{
  1999  							Verbs:     []string{"create"},
  2000  							APIGroups: []string{"authentication.k8s.io"},
  2001  							Resources: []string{"subjectaccessreviews"},
  2002  						},
  2003  					}),
  2004  					clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace),
  2005  				},
  2006  				crds: []runtime.Object{
  2007  					crd("c1", "v1", "g1"),
  2008  				},
  2009  			},
  2010  			expected: expected{
  2011  				csvStates: map[string]csvState{
  2012  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonNeedsCertRotation},
  2013  				},
  2014  			},
  2015  		},
  2016  		{
  2017  			name: "SingleCSVFailedToPending/APIService/Owned/ExpiredCA",
  2018  			initial: initial{
  2019  				csvs: []*v1alpha1.ClusterServiceVersion{
  2020  					withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  2021  						namespace,
  2022  						"0.0.0",
  2023  						"",
  2024  						installStrategy("a1", nil, nil),
  2025  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2026  						[]*apiextensionsv1.CustomResourceDefinition{},
  2027  						v1alpha1.CSVPhaseFailed,
  2028  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()),
  2029  				},
  2030  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2031  				apis: []runtime.Object{
  2032  					apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  2033  				},
  2034  				objs: []runtime.Object{
  2035  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  2036  						install.OLMCAHashAnnotationKey: expiredCAHash,
  2037  					})),
  2038  					withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{
  2039  						install.OLMCAHashAnnotationKey: expiredCAHash,
  2040  					}),
  2041  					service(install.ServiceName("a1"), namespace, "a1", 80),
  2042  					serviceAccount("sa", namespace),
  2043  					role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{
  2044  						{
  2045  							Verbs:         []string{"get"},
  2046  							APIGroups:     []string{""},
  2047  							Resources:     []string{"secrets"},
  2048  							ResourceNames: []string{install.SecretName(install.ServiceName("a1"))},
  2049  						},
  2050  					}),
  2051  					roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace),
  2052  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  2053  						{
  2054  							Verbs:         []string{"get"},
  2055  							APIGroups:     []string{""},
  2056  							Resources:     []string{"configmaps"},
  2057  							ResourceNames: []string{"extension-apiserver-authentication"},
  2058  						},
  2059  					}),
  2060  					roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  2061  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  2062  						{
  2063  							Verbs:     []string{"create"},
  2064  							APIGroups: []string{"authentication.k8s.io"},
  2065  							Resources: []string{"tokenreviews"},
  2066  						},
  2067  						{
  2068  							Verbs:     []string{"create"},
  2069  							APIGroups: []string{"authentication.k8s.io"},
  2070  							Resources: []string{"subjectaccessreviews"},
  2071  						},
  2072  					}),
  2073  					clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace),
  2074  				},
  2075  				crds: []runtime.Object{
  2076  					crd("c1", "v1", "g1"),
  2077  				},
  2078  			},
  2079  			expected: expected{
  2080  				csvStates: map[string]csvState{
  2081  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsCertRotation},
  2082  				},
  2083  			},
  2084  		},
  2085  		{
  2086  			name: "SingleCSVFailedToPending/InstallModes/Owned/PreviouslyUnsupported",
  2087  			initial: initial{
  2088  				csvs: []*v1alpha1.ClusterServiceVersion{
  2089  					withConditionReason(csvWithAnnotations(csv("csv1",
  2090  						namespace,
  2091  						"0.0.0",
  2092  						"",
  2093  						installStrategy("a1", nil, nil),
  2094  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2095  						[]*apiextensionsv1.CustomResourceDefinition{},
  2096  						v1alpha1.CSVPhaseFailed,
  2097  					), defaultTemplateAnnotations), v1alpha1.CSVReasonUnsupportedOperatorGroup),
  2098  				},
  2099  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2100  				apis:       []runtime.Object{},
  2101  				objs: []runtime.Object{
  2102  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2103  					serviceAccount("sa", namespace),
  2104  				},
  2105  				crds: []runtime.Object{
  2106  					crd("c1", "v1", "g1"),
  2107  				},
  2108  			},
  2109  			expected: expected{
  2110  				csvStates: map[string]csvState{
  2111  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsUnknown},
  2112  				},
  2113  			},
  2114  		},
  2115  		{
  2116  			name: "SingleCSVFailedToPending/InstallModes/Owned/PreviouslyNoOperatorGroups",
  2117  			initial: initial{
  2118  				csvs: []*v1alpha1.ClusterServiceVersion{
  2119  					withConditionReason(csvWithAnnotations(csv("csv1",
  2120  						namespace,
  2121  						"0.0.0",
  2122  						"",
  2123  						installStrategy("a1", nil, nil),
  2124  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2125  						[]*apiextensionsv1.CustomResourceDefinition{},
  2126  						v1alpha1.CSVPhaseFailed,
  2127  					), defaultTemplateAnnotations), v1alpha1.CSVReasonNoOperatorGroup),
  2128  				},
  2129  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2130  				apis:       []runtime.Object{},
  2131  				objs: []runtime.Object{
  2132  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2133  					serviceAccount("sa", namespace),
  2134  				},
  2135  				crds: []runtime.Object{
  2136  					crd("c1", "v1", "g1"),
  2137  				},
  2138  			},
  2139  			expected: expected{
  2140  				csvStates: map[string]csvState{
  2141  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsUnknown},
  2142  				},
  2143  			},
  2144  		},
  2145  		{
  2146  			name: "SingleCSVFailedToPending/InstallModes/Owned/PreviouslyTooManyOperatorGroups",
  2147  			initial: initial{
  2148  				csvs: []*v1alpha1.ClusterServiceVersion{
  2149  					withConditionReason(csvWithAnnotations(csv("csv1",
  2150  						namespace,
  2151  						"0.0.0",
  2152  						"",
  2153  						installStrategy("a1", nil, nil),
  2154  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2155  						[]*apiextensionsv1.CustomResourceDefinition{},
  2156  						v1alpha1.CSVPhaseFailed,
  2157  					), defaultTemplateAnnotations), v1alpha1.CSVReasonTooManyOperatorGroups),
  2158  				},
  2159  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2160  				apis:       []runtime.Object{},
  2161  				objs: []runtime.Object{
  2162  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2163  					serviceAccount("sa", namespace),
  2164  				},
  2165  				crds: []runtime.Object{
  2166  					crd("c1", "v1", "g1"),
  2167  				},
  2168  			},
  2169  			expected: expected{
  2170  				csvStates: map[string]csvState{
  2171  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsUnknown},
  2172  				},
  2173  			},
  2174  		},
  2175  		{
  2176  			name: "SingleCSVSucceededToFailed/InstallModes/Owned/Unsupported",
  2177  			initial: initial{
  2178  				csvs: []*v1alpha1.ClusterServiceVersion{
  2179  					withInstallModes(withConditionReason(csvWithAnnotations(csv("csv1",
  2180  						namespace,
  2181  						"0.0.0",
  2182  						"",
  2183  						installStrategy("a1", nil, nil),
  2184  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2185  						[]*apiextensionsv1.CustomResourceDefinition{},
  2186  						v1alpha1.CSVPhaseSucceeded,
  2187  					), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful),
  2188  						[]v1alpha1.InstallMode{
  2189  							{
  2190  								Type:      v1alpha1.InstallModeTypeSingleNamespace,
  2191  								Supported: false,
  2192  							},
  2193  						},
  2194  					),
  2195  				},
  2196  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2197  				apis:       []runtime.Object{},
  2198  				objs: []runtime.Object{
  2199  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2200  					serviceAccount("sa", namespace),
  2201  				},
  2202  				crds: []runtime.Object{
  2203  					crd("c1", "v1", "g1"),
  2204  				},
  2205  			},
  2206  			expected: expected{
  2207  				csvStates: map[string]csvState{
  2208  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonUnsupportedOperatorGroup},
  2209  				},
  2210  			},
  2211  		},
  2212  		{
  2213  			name: "SingleCSVSucceededToFailed/InstallModes/Owned/NoOperatorGroups",
  2214  			initial: initial{
  2215  				csvs: []*v1alpha1.ClusterServiceVersion{
  2216  					withConditionReason(csvWithAnnotations(csv("csv1",
  2217  						namespace,
  2218  						"0.0.0",
  2219  						"",
  2220  						installStrategy("a1", nil, nil),
  2221  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2222  						[]*apiextensionsv1.CustomResourceDefinition{},
  2223  						v1alpha1.CSVPhaseSucceeded,
  2224  					), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful),
  2225  				},
  2226  				apis: []runtime.Object{},
  2227  				objs: []runtime.Object{
  2228  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2229  					serviceAccount("sa", namespace),
  2230  				},
  2231  				crds: []runtime.Object{
  2232  					crd("c1", "v1", "g1"),
  2233  				},
  2234  			},
  2235  			expected: expected{
  2236  				csvStates: map[string]csvState{
  2237  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonNoOperatorGroup},
  2238  				},
  2239  				err: map[string]error{
  2240  					"csv1": fmt.Errorf("csv in namespace with no operatorgroups"),
  2241  				},
  2242  			},
  2243  		},
  2244  		{
  2245  			name: "SingleCSVSucceededToFailed/InstallModes/Owned/TooManyOperatorGroups",
  2246  			initial: initial{
  2247  				csvs: []*v1alpha1.ClusterServiceVersion{
  2248  					withConditionReason(csvWithAnnotations(csv("csv1",
  2249  						namespace,
  2250  						"0.0.0",
  2251  						"",
  2252  						installStrategy("a1", nil, nil),
  2253  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2254  						[]*apiextensionsv1.CustomResourceDefinition{},
  2255  						v1alpha1.CSVPhaseSucceeded,
  2256  					), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful),
  2257  				},
  2258  				clientObjs: []runtime.Object{
  2259  					defaultOperatorGroup,
  2260  					&operatorsv1.OperatorGroup{
  2261  						TypeMeta: metav1.TypeMeta{
  2262  							Kind:       "OperatorGroup",
  2263  							APIVersion: operatorsv1.SchemeGroupVersion.String(),
  2264  						},
  2265  						ObjectMeta: metav1.ObjectMeta{
  2266  							Name:      "default-2",
  2267  							Namespace: namespace,
  2268  						},
  2269  						Spec: operatorsv1.OperatorGroupSpec{},
  2270  						Status: operatorsv1.OperatorGroupStatus{
  2271  							Namespaces: []string{namespace},
  2272  						},
  2273  					},
  2274  				},
  2275  				apis: []runtime.Object{},
  2276  				objs: []runtime.Object{
  2277  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2278  					serviceAccount("sa", namespace),
  2279  				},
  2280  				crds: []runtime.Object{
  2281  					crd("c1", "v1", "g1"),
  2282  				},
  2283  			},
  2284  			expected: expected{
  2285  				csvStates: map[string]csvState{
  2286  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonTooManyOperatorGroups},
  2287  				},
  2288  				err: map[string]error{
  2289  					"csv1": fmt.Errorf("csv created in namespace with multiple operatorgroups, can't pick one automatically"),
  2290  				},
  2291  			},
  2292  		},
  2293  		{
  2294  			name: "SingleCSVSucceededToSucceeded/OperatorGroupChanged",
  2295  			initial: initial{
  2296  				csvs: []*v1alpha1.ClusterServiceVersion{
  2297  					withConditionReason(csvWithAnnotations(csv("csv1",
  2298  						namespace,
  2299  						"0.0.0",
  2300  						"",
  2301  						installStrategy("a1", nil, nil),
  2302  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2303  						[]*apiextensionsv1.CustomResourceDefinition{},
  2304  						v1alpha1.CSVPhaseSucceeded,
  2305  					), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful),
  2306  				},
  2307  				clientObjs: []runtime.Object{
  2308  					&operatorsv1.OperatorGroup{
  2309  						TypeMeta: metav1.TypeMeta{
  2310  							Kind:       "OperatorGroup",
  2311  							APIVersion: operatorsv1.SchemeGroupVersion.String(),
  2312  						},
  2313  						ObjectMeta: metav1.ObjectMeta{
  2314  							Name:      "default",
  2315  							Namespace: namespace,
  2316  						},
  2317  						Spec: operatorsv1.OperatorGroupSpec{},
  2318  						Status: operatorsv1.OperatorGroupStatus{
  2319  							Namespaces: []string{namespace, "new-namespace"},
  2320  						},
  2321  					},
  2322  				},
  2323  				apis: []runtime.Object{},
  2324  				objs: []runtime.Object{
  2325  					deployment("a1", namespace, "sa", defaultTemplateAnnotations),
  2326  					serviceAccount("sa", namespace),
  2327  				},
  2328  				crds: []runtime.Object{
  2329  					crd("c1", "v1", "g1"),
  2330  				},
  2331  			},
  2332  			expected: expected{
  2333  				csvStates: map[string]csvState{
  2334  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded, reason: v1alpha1.CSVReasonInstallSuccessful},
  2335  				},
  2336  			},
  2337  		},
  2338  		{
  2339  			name: "SingleCSVInstallingToSucceeded/UnmanagedDeploymentNotAffected",
  2340  			initial: initial{
  2341  				csvs: []*v1alpha1.ClusterServiceVersion{
  2342  					csvWithAnnotations(csv("csv1",
  2343  						namespace,
  2344  						"0.0.0",
  2345  						"",
  2346  						installStrategy("csv1-dep1", nil, nil),
  2347  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2348  						[]*apiextensionsv1.CustomResourceDefinition{},
  2349  						v1alpha1.CSVPhaseInstalling,
  2350  					), defaultTemplateAnnotations),
  2351  				},
  2352  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2353  				crds: []runtime.Object{
  2354  					crd("c1", "v1", "g1"),
  2355  				},
  2356  				objs: []runtime.Object{
  2357  					withLabels(
  2358  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2359  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2360  					),
  2361  					deployment("extra-dep", namespace, "sa", nil),
  2362  				},
  2363  			},
  2364  			expected: expected{
  2365  				csvStates: map[string]csvState{
  2366  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2367  				},
  2368  				objs: []runtime.Object{
  2369  					deployment("extra-dep", namespace, "sa", nil),
  2370  				},
  2371  			},
  2372  		},
  2373  		{
  2374  			name: "SingleCSVInstallingToInstallReady",
  2375  			initial: initial{
  2376  				csvs: []*v1alpha1.ClusterServiceVersion{
  2377  					csvWithAnnotations(csv("csv1",
  2378  						namespace,
  2379  						"0.0.0",
  2380  						"",
  2381  						installStrategy("csv1-dep1", nil, nil),
  2382  						[]*apiextensionsv1.CustomResourceDefinition{},
  2383  						[]*apiextensionsv1.CustomResourceDefinition{},
  2384  						v1alpha1.CSVPhaseInstalling,
  2385  					), defaultTemplateAnnotations),
  2386  				},
  2387  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2388  				crds:       []runtime.Object{},
  2389  				objs: []runtime.Object{
  2390  					withLabels(
  2391  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2392  						map[string]string{install.DeploymentSpecHashLabelKey: "BadHash"},
  2393  					),
  2394  				},
  2395  			},
  2396  			expected: expected{
  2397  				csvStates: map[string]csvState{
  2398  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady, reason: "InstallWaiting"},
  2399  				},
  2400  			},
  2401  		},
  2402  		{
  2403  			name: "SingleCSVInstallingToInstallReadyDueToAnnotations",
  2404  			initial: initial{
  2405  				csvs: []*v1alpha1.ClusterServiceVersion{
  2406  					csvWithAnnotations(csv("csv1",
  2407  						namespace,
  2408  						"0.0.0",
  2409  						"",
  2410  						installStrategy("csv1-dep1", nil, nil),
  2411  						[]*apiextensionsv1.CustomResourceDefinition{},
  2412  						[]*apiextensionsv1.CustomResourceDefinition{},
  2413  						v1alpha1.CSVPhaseInstalling,
  2414  					), defaultTemplateAnnotations),
  2415  				},
  2416  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2417  				crds:       []runtime.Object{},
  2418  				objs: []runtime.Object{
  2419  					deployment("csv1-dep1", namespace, "sa", map[string]string{}),
  2420  				},
  2421  			},
  2422  			expected: expected{
  2423  				csvStates: map[string]csvState{
  2424  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady, reason: ""},
  2425  				},
  2426  			},
  2427  		},
  2428  		{
  2429  			name: "SingleCSVSucceededToSucceeded/UnmanagedDeploymentInNamespace",
  2430  			initial: initial{
  2431  				csvs: []*v1alpha1.ClusterServiceVersion{
  2432  					withConditionReason(csvWithAnnotations(csv("csv1",
  2433  						namespace,
  2434  						"0.0.0",
  2435  						"",
  2436  						installStrategy("csv1-dep1", nil, nil),
  2437  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2438  						[]*apiextensionsv1.CustomResourceDefinition{},
  2439  						v1alpha1.CSVPhaseSucceeded,
  2440  					), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful),
  2441  				},
  2442  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2443  				crds: []runtime.Object{
  2444  					crd("c1", "v1", "g1"),
  2445  				},
  2446  				objs: []runtime.Object{
  2447  					withLabels(
  2448  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2449  						addDepSpecHashLabel(t, map[string]string{
  2450  							ownerutil.OwnerKey:          "csv1",
  2451  							ownerutil.OwnerNamespaceKey: namespace,
  2452  							ownerutil.OwnerKind:         "ClusterServiceVersion",
  2453  						}, withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2454  					),
  2455  					deployment("extra-dep", namespace, "sa", nil),
  2456  				},
  2457  			},
  2458  			expected: expected{
  2459  				csvStates: map[string]csvState{
  2460  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2461  				},
  2462  				objs: []runtime.Object{
  2463  					deployment("extra-dep", namespace, "sa", nil),
  2464  				},
  2465  			},
  2466  		},
  2467  		{
  2468  			name: "SingleCSVSucceededToFailed/CRD",
  2469  			initial: initial{
  2470  				csvs: []*v1alpha1.ClusterServiceVersion{
  2471  					withAPIServices(csvWithAnnotations(csv("csv1",
  2472  						namespace,
  2473  						"0.0.0",
  2474  						"",
  2475  						installStrategy("a1", nil, nil),
  2476  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2477  						[]*apiextensionsv1.CustomResourceDefinition{},
  2478  						v1alpha1.CSVPhaseSucceeded,
  2479  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil),
  2480  				},
  2481  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2482  			},
  2483  			expected: expected{
  2484  				csvStates: map[string]csvState{
  2485  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed},
  2486  				},
  2487  			},
  2488  		},
  2489  		{
  2490  			name: "SingleCSVSucceededToPending/DeploymentSpecChanged",
  2491  			initial: initial{
  2492  				csvs: []*v1alpha1.ClusterServiceVersion{
  2493  					withConditionReason(csvWithAnnotations(csv("csv1",
  2494  						namespace,
  2495  						"0.0.0",
  2496  						"",
  2497  						installStrategy("csv1-dep1", nil, nil),
  2498  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2499  						[]*apiextensionsv1.CustomResourceDefinition{},
  2500  						v1alpha1.CSVPhaseSucceeded,
  2501  					), addAnnotations(defaultTemplateAnnotations, map[string]string{"new": "annotation"})), v1alpha1.CSVReasonInstallSuccessful),
  2502  				},
  2503  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2504  				crds: []runtime.Object{
  2505  					crd("c1", "v1", "g1"),
  2506  				},
  2507  				objs: []runtime.Object{
  2508  					withLabels(
  2509  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2510  						addDepSpecHashLabel(t, map[string]string{
  2511  							ownerutil.OwnerKey:          "csv1",
  2512  							ownerutil.OwnerNamespaceKey: namespace,
  2513  							ownerutil.OwnerKind:         "ClusterServiceVersion",
  2514  						}, withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2515  					),
  2516  				},
  2517  			},
  2518  			expected: expected{
  2519  				csvStates: map[string]csvState{
  2520  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  2521  				},
  2522  			},
  2523  		},
  2524  		{
  2525  			name: "CSVSucceededToReplacing",
  2526  			initial: initial{
  2527  				csvs: []*v1alpha1.ClusterServiceVersion{
  2528  					withAnnotations(csv("csv1",
  2529  						namespace,
  2530  						"0.0.0",
  2531  						"",
  2532  						installStrategy("csv1-dep1", nil, nil),
  2533  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2534  						[]*apiextensionsv1.CustomResourceDefinition{},
  2535  						v1alpha1.CSVPhaseSucceeded,
  2536  					), defaultTemplateAnnotations).(*v1alpha1.ClusterServiceVersion),
  2537  					csvWithAnnotations(csv("csv2",
  2538  						namespace,
  2539  						"0.0.0",
  2540  						"csv1",
  2541  						installStrategy("csv2-dep1", nil, nil),
  2542  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2543  						[]*apiextensionsv1.CustomResourceDefinition{},
  2544  						v1alpha1.CSVPhaseNone,
  2545  					), defaultTemplateAnnotations),
  2546  				},
  2547  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2548  				crds: []runtime.Object{
  2549  					crd("c1", "v1", "g1"),
  2550  				},
  2551  				objs: []runtime.Object{
  2552  					deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2553  				},
  2554  			},
  2555  			expected: expected{
  2556  				csvStates: map[string]csvState{
  2557  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  2558  					"csv2": {exists: true, phase: v1alpha1.CSVPhasePending},
  2559  				},
  2560  			},
  2561  		},
  2562  		{
  2563  			name: "CSVReplacingToDeleted",
  2564  			initial: initial{
  2565  				csvs: []*v1alpha1.ClusterServiceVersion{
  2566  					csvWithAnnotations(csv("csv1",
  2567  						namespace,
  2568  						"0.0.0",
  2569  						"",
  2570  						installStrategy("csv1-dep1", nil, nil),
  2571  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2572  						[]*apiextensionsv1.CustomResourceDefinition{},
  2573  						v1alpha1.CSVPhaseReplacing,
  2574  					), defaultTemplateAnnotations),
  2575  					csvWithAnnotations(csv("csv2",
  2576  						namespace,
  2577  						"0.0.0",
  2578  						"csv1",
  2579  						installStrategy("csv2-dep1", nil, nil),
  2580  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2581  						[]*apiextensionsv1.CustomResourceDefinition{},
  2582  						v1alpha1.CSVPhaseSucceeded,
  2583  					), defaultTemplateAnnotations),
  2584  				},
  2585  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2586  				crds: []runtime.Object{
  2587  					crd("c1", "v1", "g1"),
  2588  				},
  2589  				objs: []runtime.Object{
  2590  					withLabels(
  2591  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2592  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2593  					),
  2594  					withLabels(
  2595  						deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations),
  2596  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)),
  2597  					),
  2598  				},
  2599  			},
  2600  			expected: expected{
  2601  				csvStates: map[string]csvState{
  2602  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseDeleting},
  2603  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2604  				},
  2605  			},
  2606  		},
  2607  		{
  2608  			name: "CSVDeletedToGone",
  2609  			initial: initial{
  2610  				csvs: []*v1alpha1.ClusterServiceVersion{
  2611  					csvWithAnnotations(csv("csv1",
  2612  						namespace,
  2613  						"0.0.0",
  2614  						"",
  2615  						installStrategy("csv1-dep1", nil, nil),
  2616  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2617  						[]*apiextensionsv1.CustomResourceDefinition{},
  2618  						v1alpha1.CSVPhaseDeleting,
  2619  					), defaultTemplateAnnotations),
  2620  					csvWithAnnotations(csv("csv2",
  2621  						namespace,
  2622  						"0.0.0",
  2623  						"csv1",
  2624  						installStrategy("csv2-dep1", nil, nil),
  2625  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2626  						[]*apiextensionsv1.CustomResourceDefinition{},
  2627  						v1alpha1.CSVPhaseSucceeded,
  2628  					), defaultTemplateAnnotations),
  2629  				},
  2630  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2631  				crds: []runtime.Object{
  2632  					crd("c1", "v1", "g1"),
  2633  				},
  2634  				objs: []runtime.Object{
  2635  					withLabels(
  2636  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2637  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2638  					),
  2639  					withLabels(
  2640  						deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations),
  2641  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)),
  2642  					),
  2643  				},
  2644  			},
  2645  			expected: expected{
  2646  				csvStates: map[string]csvState{
  2647  					"csv1": {exists: false, phase: v1alpha1.CSVPhaseNone},
  2648  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2649  				},
  2650  			},
  2651  		},
  2652  		{
  2653  			name: "CSVMultipleReplacingToDeleted",
  2654  			initial: initial{
  2655  				// order matters in this test case - we want to apply the latest CSV first to test the GC marking
  2656  				csvs: []*v1alpha1.ClusterServiceVersion{
  2657  					csvWithLabels(csvWithAnnotations(csv("csv3",
  2658  						namespace,
  2659  						"0.0.0",
  2660  						"csv2",
  2661  						installStrategy("csv3-dep1", nil, nil),
  2662  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2663  						[]*apiextensionsv1.CustomResourceDefinition{},
  2664  						v1alpha1.CSVPhaseSucceeded,
  2665  					), defaultTemplateAnnotations), labels.Set{
  2666  						APILabelKeyPrefix + apiHash: "provided",
  2667  					}),
  2668  					csvWithLabels(csvWithAnnotations(csv("csv1",
  2669  						namespace,
  2670  						"0.0.0",
  2671  						"",
  2672  						installStrategy("csv1-dep1", nil, nil),
  2673  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2674  						[]*apiextensionsv1.CustomResourceDefinition{},
  2675  						v1alpha1.CSVPhaseReplacing,
  2676  					), defaultTemplateAnnotations), labels.Set{
  2677  						APILabelKeyPrefix + apiHash: "provided",
  2678  					}),
  2679  					csvWithLabels(csvWithAnnotations(csv("csv2",
  2680  						namespace,
  2681  						"0.0.0",
  2682  						"csv1",
  2683  						installStrategy("csv2-dep1", nil, nil),
  2684  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2685  						[]*apiextensionsv1.CustomResourceDefinition{},
  2686  						v1alpha1.CSVPhaseReplacing,
  2687  					), defaultTemplateAnnotations), labels.Set{
  2688  						APILabelKeyPrefix + apiHash: "provided",
  2689  					}),
  2690  				},
  2691  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2692  				crds: []runtime.Object{
  2693  					crd("c1", "v1", "g1"),
  2694  				},
  2695  				objs: []runtime.Object{
  2696  					withLabels(
  2697  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2698  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2699  					),
  2700  					withLabels(
  2701  						deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations),
  2702  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)),
  2703  					),
  2704  					withLabels(
  2705  						deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations),
  2706  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)),
  2707  					),
  2708  				},
  2709  			},
  2710  			expected: expected{
  2711  				csvStates: map[string]csvState{
  2712  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  2713  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  2714  					"csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2715  				},
  2716  			},
  2717  		},
  2718  		{
  2719  			name: "CSVMultipleDeletedToGone",
  2720  			initial: initial{
  2721  				csvs: []*v1alpha1.ClusterServiceVersion{
  2722  					csvWithAnnotations(csv("csv3",
  2723  						namespace,
  2724  						"0.0.0",
  2725  						"csv2",
  2726  						installStrategy("csv3-dep1", nil, nil),
  2727  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2728  						[]*apiextensionsv1.CustomResourceDefinition{},
  2729  						v1alpha1.CSVPhaseSucceeded,
  2730  					), defaultTemplateAnnotations),
  2731  					csvWithAnnotations(csv("csv1",
  2732  						namespace,
  2733  						"0.0.0",
  2734  						"",
  2735  						installStrategy("csv1-dep1", nil, nil),
  2736  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2737  						[]*apiextensionsv1.CustomResourceDefinition{},
  2738  						v1alpha1.CSVPhaseDeleting,
  2739  					), defaultTemplateAnnotations),
  2740  					csvWithAnnotations(csv("csv2",
  2741  						namespace,
  2742  						"0.0.0",
  2743  						"csv1",
  2744  						installStrategy("csv2-dep1", nil, nil),
  2745  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2746  						[]*apiextensionsv1.CustomResourceDefinition{},
  2747  						v1alpha1.CSVPhaseReplacing,
  2748  					), defaultTemplateAnnotations),
  2749  				},
  2750  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2751  				crds: []runtime.Object{
  2752  					crd("c1", "v1", "g1"),
  2753  				},
  2754  				objs: []runtime.Object{
  2755  					withLabels(
  2756  						deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  2757  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)),
  2758  					),
  2759  					withLabels(
  2760  						deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations),
  2761  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)),
  2762  					),
  2763  					withLabels(
  2764  						deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations),
  2765  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)),
  2766  					),
  2767  				},
  2768  			},
  2769  			expected: expected{
  2770  				csvStates: map[string]csvState{
  2771  					"csv1": {exists: false, phase: v1alpha1.CSVPhaseNone},
  2772  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseDeleting},
  2773  					"csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2774  				},
  2775  			},
  2776  		},
  2777  		{
  2778  			name: "CSVMultipleDeletedToGone/AfterOneDeleted",
  2779  			initial: initial{
  2780  				csvs: []*v1alpha1.ClusterServiceVersion{
  2781  					csvWithAnnotations(csv("csv2",
  2782  						namespace,
  2783  						"0.0.0",
  2784  						"csv1",
  2785  						installStrategy("csv2-dep1", nil, nil),
  2786  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2787  						[]*apiextensionsv1.CustomResourceDefinition{},
  2788  						v1alpha1.CSVPhaseReplacing,
  2789  					), defaultTemplateAnnotations),
  2790  					csvWithAnnotations(csv("csv3",
  2791  						namespace,
  2792  						"0.0.0",
  2793  						"csv2",
  2794  						installStrategy("csv3-dep1", nil, nil),
  2795  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2796  						[]*apiextensionsv1.CustomResourceDefinition{},
  2797  						v1alpha1.CSVPhaseSucceeded,
  2798  					), defaultTemplateAnnotations),
  2799  				},
  2800  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2801  				crds: []runtime.Object{
  2802  					crd("c1", "v1", "g1"),
  2803  				},
  2804  				objs: []runtime.Object{
  2805  					withLabels(
  2806  						deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations),
  2807  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)),
  2808  					),
  2809  					withLabels(
  2810  						deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations),
  2811  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)),
  2812  					),
  2813  				},
  2814  			},
  2815  			expected: expected{
  2816  				csvStates: map[string]csvState{
  2817  					"csv1": {exists: false, phase: v1alpha1.CSVPhaseNone},
  2818  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseDeleting},
  2819  					"csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2820  				},
  2821  			},
  2822  		},
  2823  		{
  2824  			name: "CSVMultipleDeletedToGone/AfterTwoDeleted",
  2825  			initial: initial{
  2826  				csvs: []*v1alpha1.ClusterServiceVersion{
  2827  					csvWithAnnotations(csv("csv2",
  2828  						namespace,
  2829  						"0.0.0",
  2830  						"csv1",
  2831  						installStrategy("csv2-dep1", nil, nil),
  2832  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2833  						[]*apiextensionsv1.CustomResourceDefinition{},
  2834  						v1alpha1.CSVPhaseDeleting,
  2835  					), defaultTemplateAnnotations),
  2836  					csvWithAnnotations(csv("csv3",
  2837  						namespace,
  2838  						"0.0.0",
  2839  						"csv2",
  2840  						installStrategy("csv3-dep1", nil, nil),
  2841  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2842  						[]*apiextensionsv1.CustomResourceDefinition{},
  2843  						v1alpha1.CSVPhaseSucceeded,
  2844  					), defaultTemplateAnnotations),
  2845  				},
  2846  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2847  				crds: []runtime.Object{
  2848  					crd("c1", "v1", "g1"),
  2849  				},
  2850  				objs: []runtime.Object{
  2851  					withLabels(
  2852  						deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations),
  2853  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)),
  2854  					),
  2855  					withLabels(
  2856  						deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations),
  2857  						addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)),
  2858  					),
  2859  				},
  2860  			},
  2861  			expected: expected{
  2862  				csvStates: map[string]csvState{
  2863  					"csv2": {exists: false, phase: v1alpha1.CSVPhaseNone},
  2864  					"csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded},
  2865  				},
  2866  			},
  2867  		},
  2868  		{
  2869  			name:   "SingleCSVNoneToFailed/InterOperatorGroupOwnerConflict",
  2870  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(APIConflict)},
  2871  			initial: initial{
  2872  				csvs: []*v1alpha1.ClusterServiceVersion{
  2873  					csvWithAnnotations(csv("csv1",
  2874  						namespace,
  2875  						"0.0.0",
  2876  						"",
  2877  						installStrategy("csv1-dep1", nil, nil),
  2878  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2879  						[]*apiextensionsv1.CustomResourceDefinition{},
  2880  						v1alpha1.CSVPhaseNone,
  2881  					), defaultTemplateAnnotations),
  2882  				},
  2883  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2884  			},
  2885  			expected: expected{
  2886  				csvStates: map[string]csvState{
  2887  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonInterOperatorGroupOwnerConflict},
  2888  				},
  2889  			},
  2890  		},
  2891  		{
  2892  			name:   "SingleCSVNoneToNone/AddAPIs",
  2893  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(AddAPIs)},
  2894  			initial: initial{
  2895  				csvs: []*v1alpha1.ClusterServiceVersion{
  2896  					csvWithAnnotations(csv("csv1",
  2897  						namespace,
  2898  						"0.0.0",
  2899  						"",
  2900  						installStrategy("csv1-dep1", nil, nil),
  2901  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2902  						[]*apiextensionsv1.CustomResourceDefinition{},
  2903  						v1alpha1.CSVPhaseNone,
  2904  					), defaultTemplateAnnotations),
  2905  				},
  2906  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2907  			},
  2908  			expected: expected{
  2909  				csvStates: map[string]csvState{
  2910  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseNone},
  2911  				},
  2912  			},
  2913  		},
  2914  		{
  2915  			name:   "SingleCSVNoneToNone/RemoveAPIs",
  2916  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(RemoveAPIs)},
  2917  			initial: initial{
  2918  				csvs: []*v1alpha1.ClusterServiceVersion{
  2919  					csvWithAnnotations(csv("csv1",
  2920  						namespace,
  2921  						"0.0.0",
  2922  						"",
  2923  						installStrategy("csv1-dep1", nil, nil),
  2924  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2925  						[]*apiextensionsv1.CustomResourceDefinition{},
  2926  						v1alpha1.CSVPhaseNone,
  2927  					), defaultTemplateAnnotations),
  2928  				},
  2929  				clientObjs: []runtime.Object{defaultOperatorGroup},
  2930  			},
  2931  			expected: expected{
  2932  				csvStates: map[string]csvState{
  2933  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseNone},
  2934  				},
  2935  			},
  2936  		},
  2937  		{
  2938  			name:   "SingleCSVNoneToFailed/StaticOperatorGroup/AddAPIs",
  2939  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(AddAPIs)},
  2940  			initial: initial{
  2941  				csvs: []*v1alpha1.ClusterServiceVersion{
  2942  					csvWithAnnotations(csv("csv1",
  2943  						namespace,
  2944  						"0.0.0",
  2945  						"",
  2946  						installStrategy("csv1-dep1", nil, nil),
  2947  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2948  						[]*apiextensionsv1.CustomResourceDefinition{},
  2949  						v1alpha1.CSVPhaseNone,
  2950  					), defaultTemplateAnnotations),
  2951  				},
  2952  				clientObjs: []runtime.Object{
  2953  					func() *operatorsv1.OperatorGroup {
  2954  						// Make the default OperatorGroup static
  2955  						static := defaultOperatorGroup.DeepCopy()
  2956  						static.Spec.StaticProvidedAPIs = true
  2957  						return static
  2958  					}(),
  2959  				},
  2960  			},
  2961  			expected: expected{
  2962  				csvStates: map[string]csvState{
  2963  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs},
  2964  				},
  2965  			},
  2966  		},
  2967  		{
  2968  			name:   "SingleCSVNoneToFailed/StaticOperatorGroup/RemoveAPIs",
  2969  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(RemoveAPIs)},
  2970  			initial: initial{
  2971  				csvs: []*v1alpha1.ClusterServiceVersion{
  2972  					csvWithAnnotations(csv("csv1",
  2973  						namespace,
  2974  						"0.0.0",
  2975  						"",
  2976  						installStrategy("csv1-dep1", nil, nil),
  2977  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  2978  						[]*apiextensionsv1.CustomResourceDefinition{},
  2979  						v1alpha1.CSVPhaseNone,
  2980  					), defaultTemplateAnnotations),
  2981  				},
  2982  				clientObjs: []runtime.Object{
  2983  					func() *operatorsv1.OperatorGroup {
  2984  						// Make the default OperatorGroup static
  2985  						static := defaultOperatorGroup.DeepCopy()
  2986  						static.Spec.StaticProvidedAPIs = true
  2987  						return static
  2988  					}(),
  2989  				},
  2990  			},
  2991  			expected: expected{
  2992  				csvStates: map[string]csvState{
  2993  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs},
  2994  				},
  2995  			},
  2996  		},
  2997  		{
  2998  			name:   "SingleCSVNoneToPending/StaticOperatorGroup/NoAPIConflict",
  2999  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(NoAPIConflict)},
  3000  			initial: initial{
  3001  				csvs: []*v1alpha1.ClusterServiceVersion{
  3002  					csvWithAnnotations(csv("csv1",
  3003  						namespace,
  3004  						"0.0.0",
  3005  						"",
  3006  						installStrategy("csv1-dep1", nil, nil),
  3007  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3008  						[]*apiextensionsv1.CustomResourceDefinition{},
  3009  						v1alpha1.CSVPhaseNone,
  3010  					), defaultTemplateAnnotations),
  3011  				},
  3012  				clientObjs: []runtime.Object{
  3013  					func() *operatorsv1.OperatorGroup {
  3014  						// Make the default OperatorGroup static
  3015  						static := defaultOperatorGroup.DeepCopy()
  3016  						static.Spec.StaticProvidedAPIs = true
  3017  						return static
  3018  					}(),
  3019  				},
  3020  			},
  3021  			expected: expected{
  3022  				csvStates: map[string]csvState{
  3023  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  3024  				},
  3025  			},
  3026  		},
  3027  		{
  3028  			name:   "SingleCSVFailedToPending/InterOperatorGroupOwnerConflict/NoAPIConflict",
  3029  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(NoAPIConflict)},
  3030  			initial: initial{
  3031  				csvs: []*v1alpha1.ClusterServiceVersion{
  3032  					csvWithAnnotations(csvWithStatusReason(csv("csv1",
  3033  						namespace,
  3034  						"0.0.0",
  3035  						"",
  3036  						installStrategy("csv1-dep1", nil, nil),
  3037  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3038  						[]*apiextensionsv1.CustomResourceDefinition{},
  3039  						v1alpha1.CSVPhaseFailed,
  3040  					), v1alpha1.CSVReasonInterOperatorGroupOwnerConflict), defaultTemplateAnnotations),
  3041  				},
  3042  				clientObjs: []runtime.Object{defaultOperatorGroup},
  3043  			},
  3044  			expected: expected{
  3045  				csvStates: map[string]csvState{
  3046  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  3047  				},
  3048  			},
  3049  		},
  3050  		{
  3051  			name:   "SingleCSVFailedToPending/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/NoAPIConflict",
  3052  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(NoAPIConflict)},
  3053  			initial: initial{
  3054  				csvs: []*v1alpha1.ClusterServiceVersion{
  3055  					csvWithAnnotations(csvWithStatusReason(csv("csv1",
  3056  						namespace,
  3057  						"0.0.0",
  3058  						"",
  3059  						installStrategy("csv1-dep1", nil, nil),
  3060  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3061  						[]*apiextensionsv1.CustomResourceDefinition{},
  3062  						v1alpha1.CSVPhaseFailed,
  3063  					), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations),
  3064  				},
  3065  				clientObjs: []runtime.Object{
  3066  					func() *operatorsv1.OperatorGroup {
  3067  						// Make the default OperatorGroup static
  3068  						static := defaultOperatorGroup.DeepCopy()
  3069  						static.Spec.StaticProvidedAPIs = true
  3070  						return static
  3071  					}(),
  3072  				},
  3073  			},
  3074  			expected: expected{
  3075  				csvStates: map[string]csvState{
  3076  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  3077  				},
  3078  			},
  3079  		},
  3080  		{
  3081  			name:   "SingleCSVFailedToFailed/InterOperatorGroupOwnerConflict/APIConflict",
  3082  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(APIConflict)},
  3083  			initial: initial{
  3084  				csvs: []*v1alpha1.ClusterServiceVersion{
  3085  					csvWithAnnotations(csvWithStatusReason(csv("csv1",
  3086  						namespace,
  3087  						"0.0.0",
  3088  						"",
  3089  						installStrategy("csv1-dep1", nil, nil),
  3090  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3091  						[]*apiextensionsv1.CustomResourceDefinition{},
  3092  						v1alpha1.CSVPhaseFailed,
  3093  					), v1alpha1.CSVReasonInterOperatorGroupOwnerConflict), defaultTemplateAnnotations),
  3094  				},
  3095  				clientObjs: []runtime.Object{defaultOperatorGroup},
  3096  			},
  3097  			expected: expected{
  3098  				csvStates: map[string]csvState{
  3099  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonInterOperatorGroupOwnerConflict},
  3100  				},
  3101  			},
  3102  		},
  3103  		{
  3104  			name:   "SingleCSVFailedToFailed/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/AddAPIs",
  3105  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(AddAPIs)},
  3106  			initial: initial{
  3107  				csvs: []*v1alpha1.ClusterServiceVersion{
  3108  					csvWithAnnotations(csvWithStatusReason(csv("csv1",
  3109  						namespace,
  3110  						"0.0.0",
  3111  						"",
  3112  						installStrategy("csv1-dep1", nil, nil),
  3113  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3114  						[]*apiextensionsv1.CustomResourceDefinition{},
  3115  						v1alpha1.CSVPhaseFailed,
  3116  					), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations),
  3117  				},
  3118  				clientObjs: []runtime.Object{
  3119  					func() *operatorsv1.OperatorGroup {
  3120  						// Make the default OperatorGroup static
  3121  						static := defaultOperatorGroup.DeepCopy()
  3122  						static.Spec.StaticProvidedAPIs = true
  3123  						return static
  3124  					}(),
  3125  				},
  3126  			},
  3127  			expected: expected{
  3128  				csvStates: map[string]csvState{
  3129  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs},
  3130  				},
  3131  			},
  3132  		},
  3133  		{
  3134  			name:   "SingleCSVFailedToFailed/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/RemoveAPIs",
  3135  			config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(RemoveAPIs)},
  3136  			initial: initial{
  3137  				csvs: []*v1alpha1.ClusterServiceVersion{
  3138  					csvWithAnnotations(csvWithStatusReason(csv("csv1",
  3139  						namespace,
  3140  						"0.0.0",
  3141  						"",
  3142  						installStrategy("csv1-dep1", nil, nil),
  3143  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3144  						[]*apiextensionsv1.CustomResourceDefinition{},
  3145  						v1alpha1.CSVPhaseFailed,
  3146  					), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations),
  3147  				},
  3148  				clientObjs: []runtime.Object{
  3149  					func() *operatorsv1.OperatorGroup {
  3150  						// Make the default OperatorGroup static
  3151  						static := defaultOperatorGroup.DeepCopy()
  3152  						static.Spec.StaticProvidedAPIs = true
  3153  						return static
  3154  					}(),
  3155  				},
  3156  			},
  3157  			expected: expected{
  3158  				csvStates: map[string]csvState{
  3159  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs},
  3160  				},
  3161  			},
  3162  		},
  3163  	}
  3164  	for _, tt := range tests {
  3165  		t.Run(tt.name, func(t *testing.T) {
  3166  			// Create test operator
  3167  			ctx, cancel := context.WithCancel(context.TODO())
  3168  			defer cancel()
  3169  			clientObjects := tt.initial.clientObjs
  3170  			var partials []runtime.Object
  3171  			for _, csv := range tt.initial.csvs {
  3172  				clientObjects = append(clientObjects, csv)
  3173  				partials = append(partials, &metav1.PartialObjectMetadata{
  3174  					ObjectMeta: csv.ObjectMeta,
  3175  				})
  3176  			}
  3177  			op, err := NewFakeOperator(
  3178  				ctx,
  3179  				withNamespaces(namespace, "kube-system"),
  3180  				withClientObjs(clientObjects...),
  3181  				withK8sObjs(tt.initial.objs...),
  3182  				withExtObjs(tt.initial.crds...),
  3183  				withRegObjs(tt.initial.apis...),
  3184  				withPartialMetadata(partials...),
  3185  				withOperatorNamespace(namespace),
  3186  				withAPIReconciler(tt.config.apiReconciler),
  3187  				withAPILabeler(tt.config.apiLabeler),
  3188  			)
  3189  			require.NoError(t, err)
  3190  
  3191  			// run csv sync for each CSV
  3192  			for _, csv := range tt.initial.csvs {
  3193  				err := op.syncClusterServiceVersion(csv)
  3194  				expectedErr := tt.expected.err[csv.Name]
  3195  				require.Equal(t, expectedErr, err)
  3196  			}
  3197  
  3198  			// get csvs in the cluster
  3199  			outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{}
  3200  			outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{})
  3201  			require.NoError(t, err)
  3202  			for _, csv := range outCSVs.Items {
  3203  				outCSVMap[csv.GetName()] = csv.DeepCopy()
  3204  			}
  3205  
  3206  			// verify expectations of csvs in cluster
  3207  			for csvName, csvState := range tt.expected.csvStates {
  3208  				csv, ok := outCSVMap[csvName]
  3209  				require.Equal(t, ok, csvState.exists, "%s existence should be %t", csvName, csvState.exists)
  3210  				if csvState.exists {
  3211  					if csvState.reason != "" {
  3212  						require.EqualValues(t, string(csvState.reason), string(csv.Status.Reason), "%s had incorrect condition reason - %v", csvName, csv)
  3213  					}
  3214  				}
  3215  			}
  3216  
  3217  			// Verify other objects
  3218  			if tt.expected.objs != nil {
  3219  				RequireObjectsInNamespace(t, op.opClient, op.client, namespace, tt.expected.objs)
  3220  			}
  3221  		})
  3222  	}
  3223  }
  3224  
  3225  // TODO: Merge the following set of tests with those defined in TestTransitionCSV
  3226  // once those tests are updated to include validation against CSV phases.
  3227  func TestTransitionCSVFailForward(t *testing.T) {
  3228  	logrus.SetLevel(logrus.DebugLevel)
  3229  	namespace := "ns"
  3230  
  3231  	defaultOperatorGroup := &operatorsv1.OperatorGroup{
  3232  		TypeMeta: metav1.TypeMeta{
  3233  			Kind:       "OperatorGroup",
  3234  			APIVersion: operatorsv1.SchemeGroupVersion.String(),
  3235  		},
  3236  		ObjectMeta: metav1.ObjectMeta{
  3237  			Name:      "default",
  3238  			Namespace: namespace,
  3239  			Annotations: map[string]string{
  3240  				"olm.providedAPIs": "c1.v1.g1",
  3241  			},
  3242  		},
  3243  		Spec: operatorsv1.OperatorGroupSpec{},
  3244  		Status: operatorsv1.OperatorGroupStatus{
  3245  			Namespaces: []string{namespace},
  3246  		},
  3247  	}
  3248  
  3249  	defaultTemplateAnnotations := map[string]string{
  3250  		operatorsv1.OperatorGroupTargetsAnnotationKey:   namespace,
  3251  		operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace,
  3252  		operatorsv1.OperatorGroupAnnotationKey:          defaultOperatorGroup.GetName(),
  3253  	}
  3254  
  3255  	type csvState struct {
  3256  		exists bool
  3257  		phase  v1alpha1.ClusterServiceVersionPhase
  3258  		reason v1alpha1.ConditionReason
  3259  	}
  3260  	type operatorConfig struct {
  3261  		apiReconciler APIIntersectionReconciler
  3262  		apiLabeler    labeler.Labeler
  3263  	}
  3264  	type initial struct {
  3265  		csvs       []*v1alpha1.ClusterServiceVersion
  3266  		clientObjs []runtime.Object
  3267  		crds       []runtime.Object
  3268  		objs       []runtime.Object
  3269  		apis       []runtime.Object
  3270  	}
  3271  	type expected struct {
  3272  		csvStates map[string]csvState
  3273  		objs      []runtime.Object
  3274  		err       map[string]error
  3275  	}
  3276  	tests := []struct {
  3277  		name     string
  3278  		config   operatorConfig
  3279  		initial  initial
  3280  		expected expected
  3281  	}{
  3282  		{
  3283  			name: "FailForwardEnabled/CSV1/FailedToReplacing",
  3284  			initial: initial{
  3285  				csvs: []*v1alpha1.ClusterServiceVersion{
  3286  					csvWithAnnotations(csv("csv1",
  3287  						namespace,
  3288  						"1.0.0",
  3289  						"",
  3290  						installStrategy("csv1-dep1", nil, nil),
  3291  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3292  						[]*apiextensionsv1.CustomResourceDefinition{},
  3293  						v1alpha1.CSVPhaseFailed,
  3294  					), addAnnotations(defaultTemplateAnnotations, map[string]string{})),
  3295  					csv("csv2",
  3296  						namespace,
  3297  						"2.0.0",
  3298  						"csv1",
  3299  						installStrategy("csv2-dep1", nil, nil),
  3300  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3301  						[]*apiextensionsv1.CustomResourceDefinition{},
  3302  						v1alpha1.CSVPhaseNone,
  3303  					),
  3304  				},
  3305  				clientObjs: []runtime.Object{
  3306  					func() *operatorsv1.OperatorGroup {
  3307  						og := defaultOperatorGroup.DeepCopy()
  3308  						og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyUnsafeFailForward
  3309  						return og
  3310  					}(),
  3311  				},
  3312  			},
  3313  			expected: expected{
  3314  				csvStates: map[string]csvState{
  3315  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  3316  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseNone},
  3317  				},
  3318  			},
  3319  		},
  3320  		{
  3321  			name: "FailForwardDisabled/CSV1/FailedToPending",
  3322  			initial: initial{
  3323  				csvs: []*v1alpha1.ClusterServiceVersion{
  3324  					csvWithAnnotations(csv("csv1",
  3325  						namespace,
  3326  						"1.0.0",
  3327  						"",
  3328  						installStrategy("csv1-dep1", nil, nil),
  3329  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3330  						[]*apiextensionsv1.CustomResourceDefinition{},
  3331  						v1alpha1.CSVPhaseFailed,
  3332  					), addAnnotations(defaultTemplateAnnotations, map[string]string{})),
  3333  					csv("csv2",
  3334  						namespace,
  3335  						"2.0.0",
  3336  						"csv1",
  3337  						installStrategy("csv2-dep1", nil, nil),
  3338  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3339  						[]*apiextensionsv1.CustomResourceDefinition{},
  3340  						v1alpha1.CSVPhaseNone,
  3341  					),
  3342  				},
  3343  				clientObjs: []runtime.Object{
  3344  					func() *operatorsv1.OperatorGroup {
  3345  						og := defaultOperatorGroup.DeepCopy()
  3346  						og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyDefault
  3347  						return og
  3348  					}(),
  3349  				},
  3350  			},
  3351  			expected: expected{
  3352  				csvStates: map[string]csvState{
  3353  					"csv1": {exists: true, phase: v1alpha1.CSVPhasePending},
  3354  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseNone},
  3355  				},
  3356  			},
  3357  		},
  3358  		{
  3359  			name: "FailForwardEnabled/ReplacementChain/CSV2/FailedToReplacing",
  3360  			initial: initial{
  3361  				csvs: []*v1alpha1.ClusterServiceVersion{
  3362  					csvWithAnnotations(csv("csv1",
  3363  						namespace,
  3364  						"1.0.0",
  3365  						"",
  3366  						installStrategy("csv1-dep1", nil, nil),
  3367  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3368  						[]*apiextensionsv1.CustomResourceDefinition{},
  3369  						v1alpha1.CSVPhaseReplacing,
  3370  					), addAnnotations(defaultTemplateAnnotations, map[string]string{})),
  3371  					csvWithAnnotations(csv("csv2",
  3372  						namespace,
  3373  						"2.0.0",
  3374  						"csv1",
  3375  						installStrategy("csv2-dep1", nil, nil),
  3376  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3377  						[]*apiextensionsv1.CustomResourceDefinition{},
  3378  						v1alpha1.CSVPhaseFailed,
  3379  					), addAnnotations(defaultTemplateAnnotations, map[string]string{})),
  3380  					csv("csv3",
  3381  						namespace,
  3382  						"3.0.0",
  3383  						"csv2",
  3384  						installStrategy("csv3-dep1", nil, nil),
  3385  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3386  						[]*apiextensionsv1.CustomResourceDefinition{},
  3387  						v1alpha1.CSVPhaseNone,
  3388  					),
  3389  				},
  3390  				clientObjs: []runtime.Object{
  3391  					func() *operatorsv1.OperatorGroup {
  3392  						og := defaultOperatorGroup.DeepCopy()
  3393  						og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyUnsafeFailForward
  3394  						return og
  3395  					}(),
  3396  				},
  3397  			},
  3398  			expected: expected{
  3399  				csvStates: map[string]csvState{
  3400  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  3401  					"csv2": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  3402  					"csv3": {exists: true, phase: v1alpha1.CSVPhaseNone},
  3403  				},
  3404  			},
  3405  		},
  3406  		{
  3407  			name: "FailForwardDisabled/ReplacementChain/CSV2/FailedToPending",
  3408  			initial: initial{
  3409  				csvs: []*v1alpha1.ClusterServiceVersion{
  3410  					csvWithAnnotations(csv("csv1",
  3411  						namespace,
  3412  						"1.0.0",
  3413  						"",
  3414  						installStrategy("csv1-dep1", nil, nil),
  3415  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3416  						[]*apiextensionsv1.CustomResourceDefinition{},
  3417  						v1alpha1.CSVPhaseReplacing,
  3418  					), addAnnotations(defaultTemplateAnnotations, map[string]string{})),
  3419  					csvWithAnnotations(csv("csv2",
  3420  						namespace,
  3421  						"2.0.0",
  3422  						"csv1",
  3423  						installStrategy("csv2-dep1", nil, nil),
  3424  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3425  						[]*apiextensionsv1.CustomResourceDefinition{},
  3426  						v1alpha1.CSVPhaseFailed,
  3427  					), addAnnotations(defaultTemplateAnnotations, map[string]string{})),
  3428  					csv("csv3",
  3429  						namespace,
  3430  						"3.0.0",
  3431  						"csv2",
  3432  						installStrategy("csv3-dep1", nil, nil),
  3433  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3434  						[]*apiextensionsv1.CustomResourceDefinition{},
  3435  						v1alpha1.CSVPhaseNone,
  3436  					),
  3437  				},
  3438  				clientObjs: []runtime.Object{
  3439  					func() *operatorsv1.OperatorGroup {
  3440  						og := defaultOperatorGroup.DeepCopy()
  3441  						og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyDefault
  3442  						return og
  3443  					}(),
  3444  				},
  3445  			},
  3446  			expected: expected{
  3447  				csvStates: map[string]csvState{
  3448  					"csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing},
  3449  					"csv2": {exists: true, phase: v1alpha1.CSVPhasePending},
  3450  					"csv3": {exists: true, phase: v1alpha1.CSVPhaseNone},
  3451  				},
  3452  			},
  3453  		},
  3454  	}
  3455  	for _, tt := range tests {
  3456  		t.Run(tt.name, func(t *testing.T) {
  3457  			// Create test operator
  3458  			ctx, cancel := context.WithCancel(context.TODO())
  3459  			defer cancel()
  3460  			clientObjects := tt.initial.clientObjs
  3461  			var partials []runtime.Object
  3462  			for _, csv := range tt.initial.csvs {
  3463  				clientObjects = append(clientObjects, csv)
  3464  				partials = append(partials, &metav1.PartialObjectMetadata{
  3465  					ObjectMeta: csv.ObjectMeta,
  3466  				})
  3467  			}
  3468  			op, err := NewFakeOperator(
  3469  				ctx,
  3470  				withNamespaces(namespace, "kube-system"),
  3471  				withClientObjs(clientObjects...),
  3472  				withK8sObjs(tt.initial.objs...),
  3473  				withExtObjs(tt.initial.crds...),
  3474  				withRegObjs(tt.initial.apis...),
  3475  				withPartialMetadata(partials...),
  3476  				withOperatorNamespace(namespace),
  3477  				withAPIReconciler(tt.config.apiReconciler),
  3478  				withAPILabeler(tt.config.apiLabeler),
  3479  			)
  3480  			require.NoError(t, err)
  3481  
  3482  			// run csv sync for each CSV
  3483  			for _, csv := range tt.initial.csvs {
  3484  				err := op.syncClusterServiceVersion(csv)
  3485  				expectedErr := tt.expected.err[csv.Name]
  3486  				require.Equal(t, expectedErr, err)
  3487  			}
  3488  
  3489  			// get csvs in the cluster
  3490  			outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{}
  3491  			outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{})
  3492  			require.NoError(t, err)
  3493  			for _, csv := range outCSVs.Items {
  3494  				outCSVMap[csv.GetName()] = csv.DeepCopy()
  3495  			}
  3496  
  3497  			// verify expectations of csvs in cluster
  3498  			for csvName, csvState := range tt.expected.csvStates {
  3499  				csv, ok := outCSVMap[csvName]
  3500  				require.Equal(t, ok, csvState.exists, "%s existence should be %t", csvName, csvState.exists)
  3501  				if csvState.exists {
  3502  					if csvState.reason != "" {
  3503  						require.EqualValues(t, string(csvState.reason), string(csv.Status.Reason), "%s had incorrect condition reason - %v", csvName, csv)
  3504  					}
  3505  					require.Equal(t, csvState.phase, csv.Status.Phase)
  3506  				}
  3507  			}
  3508  
  3509  			// Verify other objects
  3510  			if tt.expected.objs != nil {
  3511  				RequireObjectsInNamespace(t, op.opClient, op.client, namespace, tt.expected.objs)
  3512  			}
  3513  		})
  3514  	}
  3515  }
  3516  
  3517  func TestWebhookCABundleRetrieval(t *testing.T) {
  3518  	logrus.SetLevel(logrus.DebugLevel)
  3519  	namespace := "ns"
  3520  	missingCAError := fmt.Errorf("unable to find CA")
  3521  	caBundle := []byte("Foo")
  3522  
  3523  	type initial struct {
  3524  		csvs []*v1alpha1.ClusterServiceVersion
  3525  		crds []runtime.Object
  3526  		objs []runtime.Object
  3527  		desc v1alpha1.WebhookDescription
  3528  	}
  3529  	type expected struct {
  3530  		caBundle []byte
  3531  		err      error
  3532  	}
  3533  	tests := []struct {
  3534  		name     string
  3535  		initial  initial
  3536  		expected expected
  3537  	}{
  3538  		{
  3539  			name: "MissingCAResource",
  3540  			initial: initial{
  3541  				csvs: []*v1alpha1.ClusterServiceVersion{
  3542  					csv("csv1",
  3543  						namespace,
  3544  						"0.0.0",
  3545  						"",
  3546  						installStrategy("csv1-dep1",
  3547  							nil,
  3548  							[]v1alpha1.StrategyDeploymentPermissions{},
  3549  						),
  3550  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3551  						[]*apiextensionsv1.CustomResourceDefinition{},
  3552  						v1alpha1.CSVPhaseInstalling,
  3553  					),
  3554  				},
  3555  				desc: v1alpha1.WebhookDescription{
  3556  					GenerateName: "webhook",
  3557  					Type:         v1alpha1.ValidatingAdmissionWebhook,
  3558  				},
  3559  			},
  3560  			expected: expected{
  3561  				caBundle: nil,
  3562  				err:      missingCAError,
  3563  			},
  3564  		},
  3565  		{
  3566  			name: "RetrieveCAFromConversionWebhook",
  3567  			initial: initial{
  3568  				csvs: []*v1alpha1.ClusterServiceVersion{
  3569  					csvWithConversionWebhook(csv("csv1",
  3570  						namespace,
  3571  						"0.0.0",
  3572  						"",
  3573  						installStrategy("csv1-dep1",
  3574  							nil,
  3575  							[]v1alpha1.StrategyDeploymentPermissions{},
  3576  						),
  3577  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3578  						[]*apiextensionsv1.CustomResourceDefinition{},
  3579  						v1alpha1.CSVPhaseInstalling,
  3580  					), "csv1-dep1", []string{"c1.g1"}),
  3581  				},
  3582  				crds: []runtime.Object{
  3583  					crdWithConversionWebhook(crd("c1", "v1", "g1"), caBundle),
  3584  				},
  3585  				desc: v1alpha1.WebhookDescription{
  3586  					GenerateName:   "webhook",
  3587  					Type:           v1alpha1.ConversionWebhook,
  3588  					ConversionCRDs: []string{"c1.g1"},
  3589  				},
  3590  			},
  3591  			expected: expected{
  3592  				caBundle: caBundle,
  3593  				err:      nil,
  3594  			},
  3595  		},
  3596  		{
  3597  			name: "FailToRetrieveCAFromConversionWebhook",
  3598  			initial: initial{
  3599  				csvs: []*v1alpha1.ClusterServiceVersion{
  3600  					csvWithConversionWebhook(csv("csv1",
  3601  						namespace,
  3602  						"0.0.0",
  3603  						"",
  3604  						installStrategy("csv1-dep1",
  3605  							nil,
  3606  							[]v1alpha1.StrategyDeploymentPermissions{},
  3607  						),
  3608  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3609  						[]*apiextensionsv1.CustomResourceDefinition{},
  3610  						v1alpha1.CSVPhaseInstalling,
  3611  					), "csv1-dep1", []string{"c1.g1"}),
  3612  				},
  3613  				crds: []runtime.Object{
  3614  					crdWithConversionWebhook(crd("c1", "v1", "g1"), nil),
  3615  				},
  3616  				desc: v1alpha1.WebhookDescription{
  3617  					GenerateName:   "webhook",
  3618  					Type:           v1alpha1.ConversionWebhook,
  3619  					ConversionCRDs: []string{"c1.g1"},
  3620  				},
  3621  			},
  3622  			expected: expected{
  3623  				caBundle: nil,
  3624  				err:      missingCAError,
  3625  			},
  3626  		},
  3627  		{
  3628  			name: "RetrieveFromValidatingAdmissionWebhook",
  3629  			initial: initial{
  3630  				csvs: []*v1alpha1.ClusterServiceVersion{
  3631  					csvWithValidatingAdmissionWebhook(csv("csv1",
  3632  						namespace,
  3633  						"0.0.0",
  3634  						"",
  3635  						installStrategy("csv1-dep1",
  3636  							nil,
  3637  							[]v1alpha1.StrategyDeploymentPermissions{},
  3638  						),
  3639  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3640  						[]*apiextensionsv1.CustomResourceDefinition{},
  3641  						v1alpha1.CSVPhaseInstalling,
  3642  					), "csv1-dep1", []string{"c1.g1"}),
  3643  				},
  3644  				objs: []runtime.Object{
  3645  					&admissionregistrationv1.ValidatingWebhookConfiguration{
  3646  						ObjectMeta: metav1.ObjectMeta{
  3647  							Name:      "webhook",
  3648  							Namespace: namespace,
  3649  							Labels: map[string]string{
  3650  								"olm.owner":                             "csv1",
  3651  								"olm.owner.namespace":                   namespace,
  3652  								"olm.owner.kind":                        v1alpha1.ClusterServiceVersionKind,
  3653  								"olm.webhook-description-generate-name": "webhook",
  3654  							},
  3655  						},
  3656  						Webhooks: []admissionregistrationv1.ValidatingWebhook{
  3657  							{
  3658  								Name: "Webhook",
  3659  								ClientConfig: admissionregistrationv1.WebhookClientConfig{
  3660  									CABundle: caBundle,
  3661  								},
  3662  							},
  3663  						},
  3664  					},
  3665  				},
  3666  				desc: v1alpha1.WebhookDescription{
  3667  					GenerateName: "webhook",
  3668  					Type:         v1alpha1.ValidatingAdmissionWebhook,
  3669  				},
  3670  			},
  3671  			expected: expected{
  3672  				caBundle: caBundle,
  3673  				err:      nil,
  3674  			},
  3675  		},
  3676  		{
  3677  			name: "RetrieveFromMutatingAdmissionWebhook",
  3678  			initial: initial{
  3679  				csvs: []*v1alpha1.ClusterServiceVersion{
  3680  					csvWithMutatingAdmissionWebhook(csv("csv1",
  3681  						namespace,
  3682  						"0.0.0",
  3683  						"",
  3684  						installStrategy("csv1-dep1",
  3685  							nil,
  3686  							[]v1alpha1.StrategyDeploymentPermissions{},
  3687  						),
  3688  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  3689  						[]*apiextensionsv1.CustomResourceDefinition{},
  3690  						v1alpha1.CSVPhaseInstalling,
  3691  					), "csv1-dep1", []string{"c1.g1"}),
  3692  				},
  3693  				objs: []runtime.Object{
  3694  					&admissionregistrationv1.MutatingWebhookConfiguration{
  3695  						ObjectMeta: metav1.ObjectMeta{
  3696  							Name:      "webhook",
  3697  							Namespace: namespace,
  3698  							Labels: map[string]string{
  3699  								"olm.owner":                             "csv1",
  3700  								"olm.owner.namespace":                   namespace,
  3701  								"olm.owner.kind":                        v1alpha1.ClusterServiceVersionKind,
  3702  								"olm.webhook-description-generate-name": "webhook",
  3703  							},
  3704  						},
  3705  						Webhooks: []admissionregistrationv1.MutatingWebhook{
  3706  							{
  3707  								Name: "Webhook",
  3708  								ClientConfig: admissionregistrationv1.WebhookClientConfig{
  3709  									CABundle: caBundle,
  3710  								},
  3711  							},
  3712  						},
  3713  					},
  3714  				},
  3715  				desc: v1alpha1.WebhookDescription{
  3716  					GenerateName: "webhook",
  3717  					Type:         v1alpha1.MutatingAdmissionWebhook,
  3718  				},
  3719  			},
  3720  			expected: expected{
  3721  				caBundle: caBundle,
  3722  				err:      nil,
  3723  			},
  3724  		},
  3725  	}
  3726  	for _, tt := range tests {
  3727  		t.Run(tt.name, func(t *testing.T) {
  3728  			// Create test operator
  3729  			ctx, cancel := context.WithCancel(context.TODO())
  3730  			defer cancel()
  3731  			var csvs []runtime.Object
  3732  			var partials []runtime.Object
  3733  			for _, csv := range tt.initial.csvs {
  3734  				csvs = append(csvs, csv)
  3735  				partials = append(partials, &metav1.PartialObjectMetadata{
  3736  					ObjectMeta: csv.ObjectMeta,
  3737  				})
  3738  			}
  3739  			op, err := NewFakeOperator(
  3740  				ctx,
  3741  				withNamespaces(namespace, "kube-system"),
  3742  				withClientObjs(csvs...),
  3743  				withK8sObjs(tt.initial.objs...),
  3744  				withExtObjs(tt.initial.crds...),
  3745  				withPartialMetadata(partials...),
  3746  				withOperatorNamespace(namespace),
  3747  			)
  3748  			require.NoError(t, err)
  3749  
  3750  			// run csv sync for each CSV
  3751  			for _, csv := range tt.initial.csvs {
  3752  				caBundle, err := op.getWebhookCABundle(csv, &tt.initial.desc)
  3753  				require.Equal(t, tt.expected.err, err)
  3754  				require.Equal(t, tt.expected.caBundle, caBundle)
  3755  			}
  3756  		})
  3757  	}
  3758  }
  3759  
  3760  // TestUpdates verifies that a set of expected phase transitions occur when multiple CSVs are present
  3761  // and that they do not depend on sync order or event order
  3762  func TestUpdates(t *testing.T) {
  3763  	t.Parallel()
  3764  
  3765  	// A - replacedby -> B - replacedby -> C
  3766  	namespace := "ns"
  3767  	defaultOperatorGroup := &operatorsv1.OperatorGroup{
  3768  		TypeMeta: metav1.TypeMeta{
  3769  			Kind:       "OperatorGroup",
  3770  			APIVersion: operatorsv1.SchemeGroupVersion.String(),
  3771  		},
  3772  		ObjectMeta: metav1.ObjectMeta{
  3773  			Name:      "default",
  3774  			Namespace: namespace,
  3775  		},
  3776  		Spec: operatorsv1.OperatorGroupSpec{
  3777  			TargetNamespaces: []string{namespace},
  3778  		},
  3779  		Status: operatorsv1.OperatorGroupStatus{
  3780  			Namespaces: []string{namespace},
  3781  		},
  3782  	}
  3783  	defaultTemplateAnnotations := map[string]string{
  3784  		operatorsv1.OperatorGroupTargetsAnnotationKey:   namespace,
  3785  		operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace,
  3786  		operatorsv1.OperatorGroupAnnotationKey:          defaultOperatorGroup.GetName(),
  3787  	}
  3788  	runningOperator := []runtime.Object{
  3789  		withLabels(
  3790  			deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations),
  3791  			map[string]string{
  3792  				ownerutil.OwnerKey:          "csv1",
  3793  				ownerutil.OwnerNamespaceKey: namespace,
  3794  				ownerutil.OwnerKind:         "ClusterServiceVersion",
  3795  			},
  3796  		),
  3797  	}
  3798  
  3799  	deleted := v1alpha1.ClusterServiceVersionPhase("deleted")
  3800  	deploymentName := "csv1-dep1"
  3801  	crd := crd("c1", "v1", "g1")
  3802  	a := csv("csvA",
  3803  		namespace,
  3804  		"0.0.0",
  3805  		"",
  3806  		installStrategy(deploymentName, nil, nil),
  3807  		[]*apiextensionsv1.CustomResourceDefinition{crd},
  3808  		[]*apiextensionsv1.CustomResourceDefinition{},
  3809  		v1alpha1.CSVPhaseNone)
  3810  	b := csv("csvB",
  3811  		namespace,
  3812  		"0.0.0",
  3813  		"csvA",
  3814  		installStrategy(deploymentName, nil, nil),
  3815  		[]*apiextensionsv1.CustomResourceDefinition{crd},
  3816  		[]*apiextensionsv1.CustomResourceDefinition{},
  3817  		v1alpha1.CSVPhaseNone)
  3818  	c := csv("csvC",
  3819  		namespace,
  3820  		"0.0.0",
  3821  		"csvB",
  3822  		installStrategy(deploymentName, nil, nil),
  3823  		[]*apiextensionsv1.CustomResourceDefinition{crd},
  3824  		[]*apiextensionsv1.CustomResourceDefinition{},
  3825  		v1alpha1.CSVPhaseNone)
  3826  
  3827  	simulateSuccessfulRollout := func(csv *v1alpha1.ClusterServiceVersion, client operatorclient.ClientInterface) {
  3828  		// get the deployment, which should exist
  3829  		dep, err := client.GetDeployment(namespace, deploymentName)
  3830  		require.NoError(t, err)
  3831  
  3832  		// force it healthy
  3833  		dep.Status.Replicas = 1
  3834  		dep.Status.UpdatedReplicas = 1
  3835  		dep.Status.AvailableReplicas = 1
  3836  		dep.Status.Conditions = []appsv1.DeploymentCondition{{
  3837  			Type:   appsv1.DeploymentAvailable,
  3838  			Status: corev1.ConditionTrue,
  3839  		}}
  3840  		_, err = client.KubernetesInterface().AppsV1().Deployments(namespace).UpdateStatus(context.TODO(), dep, metav1.UpdateOptions{})
  3841  		require.NoError(t, err)
  3842  	}
  3843  
  3844  	// when csv A is in phase, X, expect B and C to be in state Y
  3845  	type csvPhaseKey struct {
  3846  		name  string
  3847  		phase v1alpha1.ClusterServiceVersionPhase
  3848  	}
  3849  	type expectation struct {
  3850  		whenIn   csvPhaseKey
  3851  		shouldBe map[string]v1alpha1.ClusterServiceVersionPhase
  3852  	}
  3853  	// for a given CSV and phase, set the expected phases of the other CSVs
  3854  	expected := []expectation{
  3855  		{
  3856  			whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseNone},
  3857  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3858  				b.GetName(): v1alpha1.CSVPhaseNone,
  3859  				c.GetName(): v1alpha1.CSVPhaseNone,
  3860  			},
  3861  		},
  3862  		{
  3863  			whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhasePending},
  3864  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3865  				b.GetName(): v1alpha1.CSVPhasePending,
  3866  				c.GetName(): v1alpha1.CSVPhasePending,
  3867  			},
  3868  		},
  3869  		{
  3870  			whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseInstallReady},
  3871  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3872  				b.GetName(): v1alpha1.CSVPhasePending,
  3873  				c.GetName(): v1alpha1.CSVPhasePending,
  3874  			},
  3875  		},
  3876  		{
  3877  			whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseInstalling},
  3878  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3879  				b.GetName(): v1alpha1.CSVPhasePending,
  3880  				c.GetName(): v1alpha1.CSVPhasePending,
  3881  			},
  3882  		},
  3883  		{
  3884  			whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseSucceeded},
  3885  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3886  				b.GetName(): v1alpha1.CSVPhasePending,
  3887  				c.GetName(): v1alpha1.CSVPhasePending,
  3888  			},
  3889  		},
  3890  		{
  3891  			whenIn: csvPhaseKey{name: b.GetName(), phase: v1alpha1.CSVPhaseInstallReady},
  3892  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3893  				a.GetName(): v1alpha1.CSVPhaseReplacing,
  3894  				c.GetName(): v1alpha1.CSVPhasePending,
  3895  			},
  3896  		},
  3897  		{
  3898  			whenIn: csvPhaseKey{name: b.GetName(), phase: v1alpha1.CSVPhaseInstalling},
  3899  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3900  				a.GetName(): v1alpha1.CSVPhaseReplacing,
  3901  				c.GetName(): v1alpha1.CSVPhasePending,
  3902  			},
  3903  		},
  3904  		{
  3905  			whenIn: csvPhaseKey{name: b.GetName(), phase: v1alpha1.CSVPhaseSucceeded},
  3906  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3907  				a.GetName(): v1alpha1.CSVPhaseDeleting,
  3908  				c.GetName(): v1alpha1.CSVPhasePending,
  3909  			},
  3910  		},
  3911  		{
  3912  			whenIn: csvPhaseKey{name: c.GetName(), phase: v1alpha1.CSVPhaseInstallReady},
  3913  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3914  				a.GetName(): deleted,
  3915  				b.GetName(): v1alpha1.CSVPhaseReplacing,
  3916  			},
  3917  		},
  3918  		{
  3919  			whenIn: csvPhaseKey{name: c.GetName(), phase: v1alpha1.CSVPhaseInstalling},
  3920  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3921  				a.GetName(): deleted,
  3922  				b.GetName(): v1alpha1.CSVPhaseReplacing,
  3923  			},
  3924  		},
  3925  		{
  3926  			whenIn: csvPhaseKey{name: c.GetName(), phase: v1alpha1.CSVPhaseSucceeded},
  3927  			shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{
  3928  				a.GetName(): deleted,
  3929  				b.GetName(): deleted,
  3930  			},
  3931  		},
  3932  	}
  3933  	tests := []struct {
  3934  		name string
  3935  		in   []*v1alpha1.ClusterServiceVersion
  3936  	}{
  3937  		{
  3938  			name: "abc",
  3939  			in:   []*v1alpha1.ClusterServiceVersion{a, b, c},
  3940  		},
  3941  		{
  3942  			name: "acb",
  3943  			in:   []*v1alpha1.ClusterServiceVersion{a, c, b},
  3944  		},
  3945  		{
  3946  			name: "bac",
  3947  			in:   []*v1alpha1.ClusterServiceVersion{b, a, c},
  3948  		},
  3949  		{
  3950  			name: "bca",
  3951  			in:   []*v1alpha1.ClusterServiceVersion{b, c, a},
  3952  		},
  3953  		{
  3954  			name: "cba",
  3955  			in:   []*v1alpha1.ClusterServiceVersion{c, b, a},
  3956  		},
  3957  		{
  3958  			name: "cab",
  3959  			in:   []*v1alpha1.ClusterServiceVersion{c, a, b},
  3960  		},
  3961  	}
  3962  	for _, xt := range tests {
  3963  		tt := xt
  3964  		t.Run(tt.name, func(t *testing.T) {
  3965  			t.Parallel()
  3966  
  3967  			// Setup fake operator
  3968  			ctx, cancel := context.WithCancel(context.TODO())
  3969  			defer cancel()
  3970  			op, err := NewFakeOperator(
  3971  				ctx,
  3972  				withExtObjs(crd),
  3973  				withClientObjs(defaultOperatorGroup),
  3974  				withK8sObjs(runningOperator...),
  3975  				withNamespaces(namespace),
  3976  			)
  3977  			require.NoError(t, err)
  3978  
  3979  			// helper to get the latest view of a set of CSVs from the set - we only expect no errors if not deleted
  3980  			fetchLatestCSVs := func(csvsToSync map[string]*v1alpha1.ClusterServiceVersion, deleted map[string]struct{}) (out map[string]*v1alpha1.ClusterServiceVersion) {
  3981  				out = map[string]*v1alpha1.ClusterServiceVersion{}
  3982  				for name := range csvsToSync {
  3983  					fetched, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  3984  					if _, ok := deleted[name]; !ok {
  3985  						require.NoError(t, err)
  3986  						out[name] = fetched
  3987  					}
  3988  				}
  3989  				return out
  3990  			}
  3991  
  3992  			// helper to sync a set of csvs, in order, and return the latest view from the cluster
  3993  			syncCSVs := func(csvsToSync map[string]*v1alpha1.ClusterServiceVersion, deleted map[string]struct{}) (out map[string]*v1alpha1.ClusterServiceVersion) {
  3994  				for name, csv := range csvsToSync {
  3995  					_ = op.syncClusterServiceVersion(csv)
  3996  					if _, ok := deleted[name]; !ok {
  3997  						require.NoError(t, err)
  3998  					}
  3999  				}
  4000  				return fetchLatestCSVs(csvsToSync, deleted)
  4001  			}
  4002  
  4003  			// helper, given a set of expectations, pull out which entries we expect to have been deleted from the cluster
  4004  			deletedCSVs := func(shouldBe map[string]v1alpha1.ClusterServiceVersionPhase) map[string]struct{} {
  4005  				out := map[string]struct{}{}
  4006  				for name, phase := range shouldBe {
  4007  					if phase != deleted {
  4008  						continue
  4009  					}
  4010  					out[name] = struct{}{}
  4011  				}
  4012  				return out
  4013  			}
  4014  
  4015  			// Create input CSV set
  4016  			csvsToSync := map[string]*v1alpha1.ClusterServiceVersion{}
  4017  			for _, csv := range tt.in {
  4018  				_, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Create(context.TODO(), csv, metav1.CreateOptions{})
  4019  				require.NoError(t, err)
  4020  				csvsToSync[csv.GetName()] = csv
  4021  			}
  4022  
  4023  			for _, e := range expected {
  4024  				// get the latest view from the cluster
  4025  				csvsToSync = fetchLatestCSVs(csvsToSync, deletedCSVs(e.shouldBe))
  4026  
  4027  				// sync the current csv until it's reached the expected status
  4028  				current := csvsToSync[e.whenIn.name]
  4029  
  4030  				if current.Status.Phase == v1alpha1.CSVPhaseInstalling {
  4031  					simulateSuccessfulRollout(current, op.opClient)
  4032  				}
  4033  				for current.Status.Phase != e.whenIn.phase {
  4034  					csvsToSync = syncCSVs(csvsToSync, deletedCSVs(e.shouldBe))
  4035  					current = csvsToSync[e.whenIn.name]
  4036  					fmt.Printf("waiting for (when) %s to be %s\n", e.whenIn.name, e.whenIn.phase)
  4037  					time.Sleep(1 * time.Second)
  4038  				}
  4039  
  4040  				// sync the other csvs until they're in the expected status
  4041  				for name, phase := range e.shouldBe {
  4042  					if phase == deleted {
  4043  						// todo verify deleted
  4044  						continue
  4045  					}
  4046  					other := csvsToSync[name]
  4047  					for other.Status.Phase != phase {
  4048  						fmt.Printf("waiting for %s to be %s\n", name, phase)
  4049  						_ = op.syncClusterServiceVersion(other)
  4050  						other, err = op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  4051  						require.NoError(t, err)
  4052  					}
  4053  					csvsToSync[name] = other
  4054  				}
  4055  
  4056  				for name, phase := range e.shouldBe {
  4057  					if phase == deleted {
  4058  						continue
  4059  					}
  4060  					require.Equal(t, phase, csvsToSync[name].Status.Phase)
  4061  				}
  4062  			}
  4063  		})
  4064  	}
  4065  }
  4066  
  4067  type tDotLogWriter struct {
  4068  	*testing.T
  4069  }
  4070  
  4071  func (w tDotLogWriter) Write(p []byte) (int, error) {
  4072  	w.T.Logf("%s", string(p))
  4073  	return len(p), nil
  4074  }
  4075  
  4076  func testLogrusLogger(t *testing.T) *logrus.Logger {
  4077  	l := logrus.New()
  4078  	l.SetOutput(tDotLogWriter{t})
  4079  	return l
  4080  }
  4081  
  4082  func TestSyncNamespace(t *testing.T) {
  4083  	namespace := func(name string, labels map[string]string) corev1.Namespace {
  4084  		return corev1.Namespace{
  4085  			ObjectMeta: metav1.ObjectMeta{
  4086  				Name:   name,
  4087  				Labels: labels,
  4088  			},
  4089  		}
  4090  	}
  4091  
  4092  	operatorgroup := func(name string, targets []string) operatorsv1.OperatorGroup {
  4093  		return operatorsv1.OperatorGroup{
  4094  			ObjectMeta: metav1.ObjectMeta{
  4095  				Name: name,
  4096  				UID:  types.UID(fmt.Sprintf("%s-uid", name)),
  4097  			},
  4098  			Status: operatorsv1.OperatorGroupStatus{
  4099  				Namespaces: targets,
  4100  			},
  4101  		}
  4102  	}
  4103  
  4104  	for _, tc := range []struct {
  4105  		name           string
  4106  		before         corev1.Namespace
  4107  		operatorgroups []operatorsv1.OperatorGroup
  4108  		noop           bool
  4109  		expected       []string
  4110  	}{
  4111  		{
  4112  			name:   "adds missing labels",
  4113  			before: namespace("test-namespace", map[string]string{"unrelated": ""}),
  4114  			operatorgroups: []operatorsv1.OperatorGroup{
  4115  				operatorgroup("test-group-1", []string{"test-namespace"}),
  4116  				operatorgroup("test-group-2", []string{"test-namespace"}),
  4117  			},
  4118  			expected: []string{
  4119  				"olm.operatorgroup.uid/test-group-1-uid",
  4120  				"olm.operatorgroup.uid/test-group-2-uid",
  4121  				"unrelated",
  4122  			},
  4123  		},
  4124  		{
  4125  			name: "removes stale labels",
  4126  			before: namespace("test-namespace", map[string]string{
  4127  				"olm.operatorgroup.uid/test-group-1-uid": "",
  4128  				"olm.operatorgroup.uid/test-group-2-uid": "",
  4129  			}),
  4130  			operatorgroups: []operatorsv1.OperatorGroup{
  4131  				operatorgroup("test-group-2", []string{"test-namespace"}),
  4132  			},
  4133  			expected: []string{
  4134  				"olm.operatorgroup.uid/test-group-2-uid",
  4135  			},
  4136  		},
  4137  		{
  4138  			name:   "does not add label if namespace is not a target namespace",
  4139  			before: namespace("test-namespace", nil),
  4140  			operatorgroups: []operatorsv1.OperatorGroup{
  4141  				operatorgroup("test-group-1", []string{"test-namespace"}),
  4142  				operatorgroup("test-group-2", []string{"not-test-namespace"}),
  4143  			},
  4144  			expected: []string{
  4145  				"olm.operatorgroup.uid/test-group-1-uid",
  4146  			},
  4147  		},
  4148  		{
  4149  			name: "no update if labels are in sync",
  4150  			before: namespace("test-namespace", map[string]string{
  4151  				"olm.operatorgroup.uid/test-group-1-uid": "",
  4152  				"olm.operatorgroup.uid/test-group-2-uid": "",
  4153  			}),
  4154  			operatorgroups: []operatorsv1.OperatorGroup{
  4155  				operatorgroup("test-group-1", []string{"test-namespace"}),
  4156  				operatorgroup("test-group-2", []string{"test-namespace"}),
  4157  			},
  4158  			noop: true,
  4159  			expected: []string{
  4160  				"olm.operatorgroup.uid/test-group-1-uid",
  4161  				"olm.operatorgroup.uid/test-group-2-uid",
  4162  			},
  4163  		},
  4164  	} {
  4165  		t.Run(tc.name, func(t *testing.T) {
  4166  			ctx, cancel := context.WithCancel(context.Background())
  4167  			defer cancel()
  4168  
  4169  			var ogs []runtime.Object
  4170  			for i := range tc.operatorgroups {
  4171  				ogs = append(ogs, &tc.operatorgroups[i])
  4172  			}
  4173  
  4174  			var actions []clienttesting.Action
  4175  
  4176  			o, err := NewFakeOperator(
  4177  				ctx,
  4178  				withClientObjs(ogs...),
  4179  				withK8sObjs(&tc.before),
  4180  				withActionLog(&actions),
  4181  				withLogger(testLogrusLogger(t)),
  4182  			)
  4183  			if err != nil {
  4184  				t.Fatalf("setup failed: %v", err)
  4185  			}
  4186  
  4187  			actions = actions[:0]
  4188  
  4189  			err = o.syncNamespace(&tc.before)
  4190  			if err != nil {
  4191  				t.Fatalf("unexpected error: %v", err)
  4192  			}
  4193  
  4194  			if tc.noop {
  4195  				for _, action := range actions {
  4196  					if action.GetResource().Resource != "namespaces" {
  4197  						continue
  4198  					}
  4199  					if namer, ok := action.(interface{ GetName() string }); ok {
  4200  						if namer.GetName() != tc.before.Name {
  4201  							continue
  4202  						}
  4203  					} else if objer, ok := action.(interface{ GetObject() runtime.Object }); ok {
  4204  						if namer, ok := objer.GetObject().(interface{ GetName() string }); ok {
  4205  							if namer.GetName() != tc.before.Name {
  4206  								continue
  4207  							}
  4208  						}
  4209  					}
  4210  					t.Errorf("unexpected client operation: %v", action)
  4211  				}
  4212  			}
  4213  
  4214  			after, err := o.opClient.KubernetesInterface().CoreV1().Namespaces().Get(ctx, tc.before.Name, metav1.GetOptions{})
  4215  			if err != nil {
  4216  				t.Fatalf("unexpected error: %v", err)
  4217  			}
  4218  
  4219  			if len(after.Labels) != len(tc.expected) {
  4220  				t.Errorf("expected %d labels, got %d", len(tc.expected), len(after.Labels))
  4221  			}
  4222  
  4223  			for _, l := range tc.expected {
  4224  				if _, ok := after.Labels[l]; !ok {
  4225  					t.Errorf("missing expected label %q", l)
  4226  				}
  4227  			}
  4228  		})
  4229  	}
  4230  }
  4231  
  4232  func TestSyncOperatorGroups(t *testing.T) {
  4233  	logrus.SetLevel(logrus.WarnLevel)
  4234  	clockFake := utilclocktesting.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)))
  4235  	now := metav1.NewTime(clockFake.Now().UTC())
  4236  	const (
  4237  		timeout = 5 * time.Second
  4238  		tick    = 50 * time.Millisecond
  4239  	)
  4240  
  4241  	operatorNamespace := "operator-ns"
  4242  	targetNamespace := "target-ns"
  4243  
  4244  	serviceAccount := serviceAccount("sa", operatorNamespace)
  4245  
  4246  	permissions := []v1alpha1.StrategyDeploymentPermissions{
  4247  		{
  4248  			ServiceAccountName: serviceAccount.GetName(),
  4249  			Rules: []rbacv1.PolicyRule{
  4250  				{
  4251  					Verbs:     []string{"get"},
  4252  					APIGroups: []string{"my.api.group"},
  4253  					Resources: []string{"apis"},
  4254  				},
  4255  			},
  4256  		},
  4257  	}
  4258  	deploymentName := "csv1-dep1"
  4259  	crd := crd("c1", "v1", "fake.api.group")
  4260  	operatorCSV := csvWithLabels(csv("csv1",
  4261  		operatorNamespace,
  4262  		"0.0.0",
  4263  		"",
  4264  		installStrategy(deploymentName, permissions, nil),
  4265  		[]*apiextensionsv1.CustomResourceDefinition{crd},
  4266  		[]*apiextensionsv1.CustomResourceDefinition{},
  4267  		v1alpha1.CSVPhaseNone,
  4268  	), labels.Set{APILabelKeyPrefix + "9f4c46c37bdff8d0": "provided"})
  4269  
  4270  	operatorCSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{
  4271  		Name:  "OPERATOR_CONDITION_NAME",
  4272  		Value: operatorCSV.GetName(),
  4273  	}}
  4274  
  4275  	serverVersion := version.Get().String()
  4276  	// after state transitions from operatorgroups, this is the operator csv we expect
  4277  	operatorCSVFinal := operatorCSV.DeepCopy()
  4278  	operatorCSVFinal.Status.Phase = v1alpha1.CSVPhaseSucceeded
  4279  	operatorCSVFinal.Status.Message = "install strategy completed with no errors"
  4280  	operatorCSVFinal.Status.Reason = v1alpha1.CSVReasonInstallSuccessful
  4281  	operatorCSVFinal.Status.LastUpdateTime = &now
  4282  	operatorCSVFinal.Status.LastTransitionTime = &now
  4283  	operatorCSVFinal.Status.RequirementStatus = []v1alpha1.RequirementStatus{
  4284  		{
  4285  			Group:   "operators.coreos.com",
  4286  			Version: "v1alpha1",
  4287  			Kind:    "ClusterServiceVersion",
  4288  			Name:    "csv1",
  4289  			Status:  v1alpha1.RequirementStatusReasonPresent,
  4290  			Message: "CSV minKubeVersion (0.0.0) less than server version (" + serverVersion + ")",
  4291  		},
  4292  		{
  4293  			Group:   "apiextensions.k8s.io",
  4294  			Version: "v1",
  4295  			Kind:    "CustomResourceDefinition",
  4296  			Name:    crd.GetName(),
  4297  			Status:  v1alpha1.RequirementStatusReasonPresent,
  4298  			Message: "CRD is present and Established condition is true",
  4299  		},
  4300  		{
  4301  			Group:   "",
  4302  			Version: "v1",
  4303  			Kind:    "ServiceAccount",
  4304  			Name:    serviceAccount.GetName(),
  4305  			Status:  v1alpha1.RequirementStatusReasonPresent,
  4306  			Dependents: []v1alpha1.DependentStatus{
  4307  				{
  4308  					Group:   "rbac.authorization.k8s.io",
  4309  					Version: "v1",
  4310  					Kind:    "PolicyRule",
  4311  					Status:  "Satisfied",
  4312  					Message: "namespaced rule:{\"verbs\":[\"get\"],\"apiGroups\":[\"my.api.group\"],\"resources\":[\"apis\"]}",
  4313  				},
  4314  			},
  4315  		},
  4316  	}
  4317  	operatorCSVFinal.Status.Conditions = []v1alpha1.ClusterServiceVersionCondition{
  4318  		{
  4319  			Phase:              v1alpha1.CSVPhasePending,
  4320  			Reason:             v1alpha1.CSVReasonRequirementsUnknown,
  4321  			Message:            "requirements not yet checked",
  4322  			LastUpdateTime:     &now,
  4323  			LastTransitionTime: &now,
  4324  		},
  4325  		{
  4326  			Phase:              v1alpha1.CSVPhaseInstallReady,
  4327  			Reason:             v1alpha1.CSVReasonRequirementsMet,
  4328  			Message:            "all requirements found, attempting install",
  4329  			LastUpdateTime:     &now,
  4330  			LastTransitionTime: &now,
  4331  		},
  4332  		{
  4333  			Phase:              v1alpha1.CSVPhaseInstalling,
  4334  			Reason:             v1alpha1.CSVReasonInstallSuccessful,
  4335  			Message:            "waiting for install components to report healthy",
  4336  			LastUpdateTime:     &now,
  4337  			LastTransitionTime: &now,
  4338  		},
  4339  		{
  4340  			Phase:              v1alpha1.CSVPhaseSucceeded,
  4341  			Reason:             v1alpha1.CSVReasonInstallSuccessful,
  4342  			Message:            "install strategy completed with no errors",
  4343  			LastUpdateTime:     &now,
  4344  			LastTransitionTime: &now,
  4345  		},
  4346  	}
  4347  
  4348  	// Failed CSV due to operatorgroup namespace selector doesn't any existing namespaces
  4349  	operatorCSVFailedNoTargetNS := operatorCSV.DeepCopy()
  4350  	operatorCSVFailedNoTargetNS.Status.Phase = v1alpha1.CSVPhaseFailed
  4351  	operatorCSVFailedNoTargetNS.Status.Message = "no targetNamespaces are matched operatorgroups namespace selection"
  4352  	operatorCSVFailedNoTargetNS.Status.Reason = v1alpha1.CSVReasonNoTargetNamespaces
  4353  	operatorCSVFailedNoTargetNS.Status.LastUpdateTime = &now
  4354  	operatorCSVFailedNoTargetNS.Status.LastTransitionTime = &now
  4355  	operatorCSVFailedNoTargetNS.Status.Conditions = []v1alpha1.ClusterServiceVersionCondition{
  4356  		{
  4357  			Phase:              v1alpha1.CSVPhaseFailed,
  4358  			Reason:             v1alpha1.CSVReasonNoTargetNamespaces,
  4359  			Message:            "no targetNamespaces are matched operatorgroups namespace selection",
  4360  			LastUpdateTime:     &now,
  4361  			LastTransitionTime: &now,
  4362  		},
  4363  	}
  4364  
  4365  	targetCSV := operatorCSVFinal.DeepCopy()
  4366  	targetCSV.SetNamespace(targetNamespace)
  4367  	targetCSV.Status.Reason = v1alpha1.CSVReasonCopied
  4368  	targetCSV.Status.Message = "The operator is running in operator-ns but is managing this namespace"
  4369  	targetCSV.Status.LastUpdateTime = &now
  4370  
  4371  	ownerutil.AddNonBlockingOwner(serviceAccount, operatorCSV)
  4372  
  4373  	ownedDeployment := deployment(deploymentName, operatorNamespace, serviceAccount.GetName(), nil)
  4374  	ownedDeployment.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "OPERATOR_CONDITION_NAME", Value: "csv1"}}
  4375  	ownerutil.AddNonBlockingOwner(ownedDeployment, operatorCSV)
  4376  	deploymentSpec := installStrategy(deploymentName, permissions, nil).StrategySpec.DeploymentSpecs[0].Spec
  4377  	hash, err := hashutil.DeepHashObject(&deploymentSpec)
  4378  	if err != nil {
  4379  		t.Fatal(err)
  4380  	}
  4381  	ownedDeployment.SetLabels(map[string]string{
  4382  		install.DeploymentSpecHashLabelKey: hash,
  4383  	})
  4384  
  4385  	annotatedDeployment := ownedDeployment.DeepCopy()
  4386  	annotatedDeployment.Spec.Template.SetAnnotations(map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: operatorNamespace + "," + targetNamespace, operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace})
  4387  	hash, err = hashutil.DeepHashObject(&annotatedDeployment.Spec)
  4388  	if err != nil {
  4389  		t.Fatal(err)
  4390  	}
  4391  	annotatedDeployment.SetLabels(map[string]string{
  4392  		"olm.managed":                      "true",
  4393  		"olm.owner":                        "csv1",
  4394  		"olm.owner.namespace":              "operator-ns",
  4395  		"olm.owner.kind":                   "ClusterServiceVersion",
  4396  		install.DeploymentSpecHashLabelKey: hash,
  4397  	})
  4398  
  4399  	annotatedGlobalDeployment := ownedDeployment.DeepCopy()
  4400  	annotatedGlobalDeployment.Spec.Template.SetAnnotations(map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: "", operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace})
  4401  	hash, err = hashutil.DeepHashObject(&annotatedGlobalDeployment.Spec)
  4402  	if err != nil {
  4403  		t.Fatal(err)
  4404  	}
  4405  	annotatedGlobalDeployment.SetLabels(map[string]string{
  4406  		"olm.managed":                      "true",
  4407  		"olm.owner":                        "csv1",
  4408  		"olm.owner.namespace":              "operator-ns",
  4409  		"olm.owner.kind":                   "ClusterServiceVersion",
  4410  		install.DeploymentSpecHashLabelKey: hash,
  4411  	})
  4412  
  4413  	role := &rbacv1.Role{
  4414  		TypeMeta: metav1.TypeMeta{
  4415  			Kind:       "Role",
  4416  			APIVersion: rbacv1.GroupName,
  4417  		},
  4418  		ObjectMeta: metav1.ObjectMeta{
  4419  			Name:            "csv-role",
  4420  			Namespace:       operatorNamespace,
  4421  			Labels:          ownerutil.OwnerLabel(operatorCSV, v1alpha1.ClusterServiceVersionKind),
  4422  			OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(operatorCSV)},
  4423  		},
  4424  		Rules: permissions[0].Rules,
  4425  	}
  4426  	role.Labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue
  4427  
  4428  	roleBinding := &rbacv1.RoleBinding{
  4429  		TypeMeta: metav1.TypeMeta{
  4430  			Kind:       "RoleBinding",
  4431  			APIVersion: rbacv1.GroupName,
  4432  		},
  4433  		ObjectMeta: metav1.ObjectMeta{
  4434  			Name:            "csv-rolebinding",
  4435  			Namespace:       operatorNamespace,
  4436  			Labels:          ownerutil.OwnerLabel(operatorCSV, v1alpha1.ClusterServiceVersionKind),
  4437  			OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(operatorCSV)},
  4438  		},
  4439  		Subjects: []rbacv1.Subject{
  4440  			{
  4441  				Kind:      "ServiceAccount",
  4442  				APIGroup:  serviceAccount.GetObjectKind().GroupVersionKind().Group,
  4443  				Name:      serviceAccount.GetName(),
  4444  				Namespace: serviceAccount.GetNamespace(),
  4445  			},
  4446  		},
  4447  		RoleRef: rbacv1.RoleRef{
  4448  			APIGroup: rbacv1.GroupName,
  4449  			Kind:     role.GetObjectKind().GroupVersionKind().Kind,
  4450  			Name:     role.GetName(),
  4451  		},
  4452  	}
  4453  	roleBinding.Labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue
  4454  
  4455  	type initial struct {
  4456  		operatorGroup *operatorsv1.OperatorGroup
  4457  		csvs          []*v1alpha1.ClusterServiceVersion
  4458  		clientObjs    []runtime.Object
  4459  		crds          []*apiextensionsv1.CustomResourceDefinition
  4460  		k8sObjs       []runtime.Object
  4461  		apis          []runtime.Object
  4462  	}
  4463  	type final struct {
  4464  		objects map[string][]runtime.Object
  4465  	}
  4466  	tests := []struct {
  4467  		initial         initial
  4468  		name            string
  4469  		expectedEqual   bool
  4470  		expectedStatus  operatorsv1.OperatorGroupStatus
  4471  		final           final
  4472  		ignoreCopyError bool
  4473  	}{
  4474  		{
  4475  			name:          "NoMatchingNamespace/NoCSVs",
  4476  			expectedEqual: true,
  4477  			initial: initial{
  4478  				operatorGroup: &operatorsv1.OperatorGroup{
  4479  					ObjectMeta: metav1.ObjectMeta{
  4480  						Name:      "operator-group-1",
  4481  						Namespace: operatorNamespace,
  4482  					},
  4483  					Spec: operatorsv1.OperatorGroupSpec{
  4484  						Selector: &metav1.LabelSelector{
  4485  							MatchLabels: map[string]string{"a": "app-a"},
  4486  						},
  4487  					},
  4488  				},
  4489  				k8sObjs: []runtime.Object{
  4490  					&corev1.Namespace{
  4491  						ObjectMeta: metav1.ObjectMeta{
  4492  							Name: operatorNamespace,
  4493  						},
  4494  					},
  4495  					&corev1.Namespace{
  4496  						ObjectMeta: metav1.ObjectMeta{
  4497  							Name: targetNamespace,
  4498  						},
  4499  					},
  4500  				},
  4501  			},
  4502  			expectedStatus: operatorsv1.OperatorGroupStatus{},
  4503  		},
  4504  		{
  4505  			name:          "NoMatchingNamespace/CSVPresent",
  4506  			expectedEqual: true,
  4507  			initial: initial{
  4508  				operatorGroup: &operatorsv1.OperatorGroup{
  4509  					ObjectMeta: metav1.ObjectMeta{
  4510  						Name:      "operator-group-1",
  4511  						Namespace: operatorNamespace,
  4512  					},
  4513  					Spec: operatorsv1.OperatorGroupSpec{
  4514  						Selector: &metav1.LabelSelector{
  4515  							MatchLabels: map[string]string{"a": "app-a"},
  4516  						},
  4517  					},
  4518  				},
  4519  				clientObjs: []runtime.Object{operatorCSV},
  4520  				k8sObjs: []runtime.Object{
  4521  					&corev1.Namespace{
  4522  						ObjectMeta: metav1.ObjectMeta{
  4523  							Name: operatorNamespace,
  4524  						},
  4525  					},
  4526  					&corev1.Namespace{
  4527  						ObjectMeta: metav1.ObjectMeta{
  4528  							Name: targetNamespace,
  4529  						},
  4530  					},
  4531  					ownedDeployment,
  4532  					serviceAccount,
  4533  					role,
  4534  					roleBinding,
  4535  				},
  4536  				crds: []*apiextensionsv1.CustomResourceDefinition{crd},
  4537  			},
  4538  			expectedStatus: operatorsv1.OperatorGroupStatus{},
  4539  			final: final{objects: map[string][]runtime.Object{
  4540  				operatorNamespace: {
  4541  					withAnnotations(operatorCSVFailedNoTargetNS.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  4542  				},
  4543  			}},
  4544  			ignoreCopyError: true,
  4545  		},
  4546  		{
  4547  			name:          "MatchingNamespace/NoCSVs",
  4548  			expectedEqual: true,
  4549  			initial: initial{
  4550  				operatorGroup: &operatorsv1.OperatorGroup{
  4551  					ObjectMeta: metav1.ObjectMeta{
  4552  						Name:      "operator-group-1",
  4553  						Namespace: operatorNamespace,
  4554  					},
  4555  					Spec: operatorsv1.OperatorGroupSpec{
  4556  						Selector: &metav1.LabelSelector{
  4557  							MatchLabels: map[string]string{"app": "app-a"},
  4558  						},
  4559  					},
  4560  				},
  4561  				k8sObjs: []runtime.Object{
  4562  					&corev1.Namespace{
  4563  						ObjectMeta: metav1.ObjectMeta{
  4564  							Name: operatorNamespace,
  4565  						},
  4566  					},
  4567  					&corev1.Namespace{
  4568  						ObjectMeta: metav1.ObjectMeta{
  4569  							Name:   targetNamespace,
  4570  							Labels: map[string]string{"app": "app-a"},
  4571  						},
  4572  					},
  4573  				},
  4574  			},
  4575  			expectedStatus: operatorsv1.OperatorGroupStatus{
  4576  				Namespaces:  []string{targetNamespace},
  4577  				LastUpdated: &now,
  4578  			},
  4579  		},
  4580  		{
  4581  			name:          "MatchingNamespace/NoCSVs/CreatesClusterRoles",
  4582  			expectedEqual: true,
  4583  			initial: initial{
  4584  				operatorGroup: &operatorsv1.OperatorGroup{
  4585  					ObjectMeta: metav1.ObjectMeta{
  4586  						Name:      "operator-group-1",
  4587  						Namespace: operatorNamespace, Labels: map[string]string{"app": "app-a"},
  4588  					},
  4589  					Spec: operatorsv1.OperatorGroupSpec{
  4590  						Selector: &metav1.LabelSelector{
  4591  							MatchLabels: map[string]string{"app": "app-a"},
  4592  						},
  4593  					},
  4594  				},
  4595  				k8sObjs: []runtime.Object{
  4596  					&corev1.Namespace{
  4597  						ObjectMeta: metav1.ObjectMeta{
  4598  							Name: operatorNamespace,
  4599  						},
  4600  					},
  4601  					&corev1.Namespace{
  4602  						ObjectMeta: metav1.ObjectMeta{
  4603  							Name:   targetNamespace,
  4604  							Labels: map[string]string{"app": "app-a"},
  4605  						},
  4606  					},
  4607  				},
  4608  			},
  4609  			expectedStatus: operatorsv1.OperatorGroupStatus{
  4610  				Namespaces:  []string{targetNamespace},
  4611  				LastUpdated: &now,
  4612  			},
  4613  			final: final{objects: map[string][]runtime.Object{
  4614  				"": {
  4615  					&rbacv1.ClusterRole{
  4616  						ObjectMeta: metav1.ObjectMeta{
  4617  							Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU",
  4618  							Labels: map[string]string{
  4619  								"olm.managed":         "true",
  4620  								"olm.owner":           "operator-group-1",
  4621  								"olm.owner.namespace": "operator-ns",
  4622  								"olm.owner.kind":      "OperatorGroup",
  4623  							},
  4624  						},
  4625  					},
  4626  					&rbacv1.ClusterRole{
  4627  						ObjectMeta: metav1.ObjectMeta{
  4628  							Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od",
  4629  							Labels: map[string]string{
  4630  								"olm.managed":         "true",
  4631  								"olm.owner":           "operator-group-1",
  4632  								"olm.owner.namespace": "operator-ns",
  4633  								"olm.owner.kind":      "OperatorGroup",
  4634  							},
  4635  						},
  4636  					},
  4637  					&rbacv1.ClusterRole{
  4638  						ObjectMeta: metav1.ObjectMeta{
  4639  							Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr",
  4640  							Labels: map[string]string{
  4641  								"olm.managed":         "true",
  4642  								"olm.owner":           "operator-group-1",
  4643  								"olm.owner.namespace": "operator-ns",
  4644  								"olm.owner.kind":      "OperatorGroup",
  4645  							},
  4646  						},
  4647  					},
  4648  				},
  4649  			}},
  4650  		},
  4651  		{
  4652  			// check that even if cluster roles exist without the naming convention, we create the new ones and leave the old ones unchanged
  4653  			name:          "MatchingNamespace/NoCSVs/KeepOldClusterRoles",
  4654  			expectedEqual: true,
  4655  			initial: initial{
  4656  				operatorGroup: &operatorsv1.OperatorGroup{
  4657  					ObjectMeta: metav1.ObjectMeta{
  4658  						Name:      "operator-group-1",
  4659  						Namespace: operatorNamespace,
  4660  					},
  4661  					Spec: operatorsv1.OperatorGroupSpec{
  4662  						Selector: &metav1.LabelSelector{
  4663  							MatchLabels: map[string]string{"app": "app-a"},
  4664  						},
  4665  					},
  4666  				},
  4667  				k8sObjs: []runtime.Object{
  4668  					&corev1.Namespace{
  4669  						ObjectMeta: metav1.ObjectMeta{
  4670  							Name: operatorNamespace,
  4671  						},
  4672  					},
  4673  					&corev1.Namespace{
  4674  						ObjectMeta: metav1.ObjectMeta{
  4675  							Name:   targetNamespace,
  4676  							Labels: map[string]string{"app": "app-a"},
  4677  						},
  4678  					},
  4679  					&rbacv1.ClusterRole{
  4680  						ObjectMeta: metav1.ObjectMeta{
  4681  							Name: "operator-group-1-admin",
  4682  							Labels: map[string]string{
  4683  								"olm.managed":         "true",
  4684  								"olm.owner":           "operator-group-1",
  4685  								"olm.owner.namespace": "operator-ns",
  4686  								"olm.owner.kind":      "OperatorGroup",
  4687  							},
  4688  						},
  4689  					},
  4690  					&rbacv1.ClusterRole{
  4691  						ObjectMeta: metav1.ObjectMeta{
  4692  							Name: "operator-group-1-view",
  4693  							Labels: map[string]string{
  4694  								"olm.managed":         "true",
  4695  								"olm.owner":           "operator-group-1",
  4696  								"olm.owner.namespace": "operator-ns",
  4697  								"olm.owner.kind":      "OperatorGroup",
  4698  							},
  4699  						},
  4700  					},
  4701  					&rbacv1.ClusterRole{
  4702  						ObjectMeta: metav1.ObjectMeta{
  4703  							Name: "operator-group-1-edit",
  4704  							Labels: map[string]string{
  4705  								"olm.managed":         "true",
  4706  								"olm.owner":           "operator-group-1",
  4707  								"olm.owner.namespace": "operator-ns",
  4708  								"olm.owner.kind":      "OperatorGroup",
  4709  							},
  4710  						},
  4711  					},
  4712  				},
  4713  			},
  4714  			expectedStatus: operatorsv1.OperatorGroupStatus{
  4715  				Namespaces:  []string{targetNamespace},
  4716  				LastUpdated: &now,
  4717  			},
  4718  			final: final{objects: map[string][]runtime.Object{
  4719  				"": {
  4720  					&rbacv1.ClusterRole{
  4721  						ObjectMeta: metav1.ObjectMeta{
  4722  							Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU",
  4723  							Labels: map[string]string{
  4724  								"olm.managed":         "true",
  4725  								"olm.owner":           "operator-group-1",
  4726  								"olm.owner.namespace": "operator-ns",
  4727  								"olm.owner.kind":      "OperatorGroup",
  4728  							},
  4729  						},
  4730  					},
  4731  					&rbacv1.ClusterRole{
  4732  						ObjectMeta: metav1.ObjectMeta{
  4733  							Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od",
  4734  							Labels: map[string]string{
  4735  								"olm.managed":         "true",
  4736  								"olm.owner":           "operator-group-1",
  4737  								"olm.owner.namespace": "operator-ns",
  4738  								"olm.owner.kind":      "OperatorGroup",
  4739  							},
  4740  						},
  4741  					},
  4742  					&rbacv1.ClusterRole{
  4743  						ObjectMeta: metav1.ObjectMeta{
  4744  							Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr",
  4745  							Labels: map[string]string{
  4746  								"olm.managed":         "true",
  4747  								"olm.owner":           "operator-group-1",
  4748  								"olm.owner.namespace": "operator-ns",
  4749  								"olm.owner.kind":      "OperatorGroup",
  4750  							},
  4751  						},
  4752  					},
  4753  					&rbacv1.ClusterRole{
  4754  						ObjectMeta: metav1.ObjectMeta{
  4755  							Name: "operator-group-1-admin",
  4756  							Labels: map[string]string{
  4757  								"olm.managed":         "true",
  4758  								"olm.owner":           "operator-group-1",
  4759  								"olm.owner.namespace": "operator-ns",
  4760  								"olm.owner.kind":      "OperatorGroup",
  4761  							},
  4762  						},
  4763  					},
  4764  					&rbacv1.ClusterRole{
  4765  						ObjectMeta: metav1.ObjectMeta{
  4766  							Name: "operator-group-1-view",
  4767  							Labels: map[string]string{
  4768  								"olm.managed":         "true",
  4769  								"olm.owner":           "operator-group-1",
  4770  								"olm.owner.namespace": "operator-ns",
  4771  								"olm.owner.kind":      "OperatorGroup",
  4772  							},
  4773  						},
  4774  					},
  4775  					&rbacv1.ClusterRole{
  4776  						ObjectMeta: metav1.ObjectMeta{
  4777  							Name: "operator-group-1-edit",
  4778  							Labels: map[string]string{
  4779  								"olm.managed":         "true",
  4780  								"olm.owner":           "operator-group-1",
  4781  								"olm.owner.namespace": "operator-ns",
  4782  								"olm.owner.kind":      "OperatorGroup",
  4783  							},
  4784  						},
  4785  					},
  4786  				},
  4787  			}},
  4788  		},
  4789  		{
  4790  			// ensure that ownership labels are fixed but user labels are preserved
  4791  			name:          "MatchingNamespace/NoCSVs/ClusterRoleOwnershipLabels",
  4792  			expectedEqual: true,
  4793  			initial: initial{
  4794  				operatorGroup: &operatorsv1.OperatorGroup{
  4795  					ObjectMeta: metav1.ObjectMeta{
  4796  						Name:      "operator-group-1",
  4797  						Namespace: operatorNamespace,
  4798  					},
  4799  					Spec: operatorsv1.OperatorGroupSpec{
  4800  						Selector: &metav1.LabelSelector{
  4801  							MatchLabels: map[string]string{"app": "app-a"},
  4802  						},
  4803  					},
  4804  				},
  4805  				k8sObjs: []runtime.Object{
  4806  					&corev1.Namespace{
  4807  						ObjectMeta: metav1.ObjectMeta{
  4808  							Name: operatorNamespace,
  4809  						},
  4810  					},
  4811  					&corev1.Namespace{
  4812  						ObjectMeta: metav1.ObjectMeta{
  4813  							Name:   targetNamespace,
  4814  							Labels: map[string]string{"app": "app-a"},
  4815  						},
  4816  					},
  4817  					&rbacv1.ClusterRole{
  4818  						ObjectMeta: metav1.ObjectMeta{
  4819  							Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU",
  4820  							Labels: map[string]string{
  4821  								"olm.managed":         "true",
  4822  								"olm.owner":           "operator-group-1",
  4823  								"olm.owner.namespace": "operator-ns-bob",
  4824  								"olm.owner.kind":      "OperatorGroup",
  4825  								"not.an.olm.label":    "true",
  4826  							},
  4827  						},
  4828  					},
  4829  					&rbacv1.ClusterRole{
  4830  						ObjectMeta: metav1.ObjectMeta{
  4831  							Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr",
  4832  							Labels: map[string]string{
  4833  								"olm.managed":         "true",
  4834  								"olm.owner":           "operator-group-5",
  4835  								"olm.owner.namespace": "operator-ns",
  4836  								"olm.owner.kind":      "OperatorGroup",
  4837  								"not.an.olm.label":    "false",
  4838  								"another.olm.label":   "or maybe not",
  4839  							},
  4840  						},
  4841  					},
  4842  					&rbacv1.ClusterRole{
  4843  						ObjectMeta: metav1.ObjectMeta{
  4844  							Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od",
  4845  							Labels: map[string]string{
  4846  								"olm.managed":         "true",
  4847  								"olm.owner":           "operator-group-1",
  4848  								"olm.owner.namespace": "operator-ns",
  4849  								"olm.owner.kind":      "OperatorGroupKind",
  4850  							},
  4851  						},
  4852  					},
  4853  				},
  4854  			},
  4855  			expectedStatus: operatorsv1.OperatorGroupStatus{
  4856  				Namespaces:  []string{targetNamespace},
  4857  				LastUpdated: &now,
  4858  			},
  4859  			final: final{objects: map[string][]runtime.Object{
  4860  				"": {
  4861  					&rbacv1.ClusterRole{
  4862  						ObjectMeta: metav1.ObjectMeta{
  4863  							Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU",
  4864  							Labels: map[string]string{
  4865  								"olm.managed":         "true",
  4866  								"olm.owner":           "operator-group-1",
  4867  								"olm.owner.namespace": "operator-ns",
  4868  								"olm.owner.kind":      "OperatorGroup",
  4869  								"not.an.olm.label":    "true",
  4870  							},
  4871  						},
  4872  					},
  4873  					&rbacv1.ClusterRole{
  4874  						ObjectMeta: metav1.ObjectMeta{
  4875  							Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od",
  4876  							Labels: map[string]string{
  4877  								"olm.managed":         "true",
  4878  								"olm.owner":           "operator-group-1",
  4879  								"olm.owner.namespace": "operator-ns",
  4880  								"olm.owner.kind":      "OperatorGroup",
  4881  							},
  4882  						},
  4883  					},
  4884  					&rbacv1.ClusterRole{
  4885  						ObjectMeta: metav1.ObjectMeta{
  4886  							Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr",
  4887  							Labels: map[string]string{
  4888  								"olm.managed":         "true",
  4889  								"olm.owner":           "operator-group-1",
  4890  								"olm.owner.namespace": "operator-ns",
  4891  								"olm.owner.kind":      "OperatorGroup",
  4892  								"not.an.olm.label":    "false",
  4893  								"another.olm.label":   "or maybe not",
  4894  							},
  4895  						},
  4896  					},
  4897  				},
  4898  			}},
  4899  		},
  4900  		{
  4901  			// if a cluster role exists with the correct name, use that
  4902  			name:          "MatchingNamespace/NoCSVs/DoesNotUpdateClusterRoles",
  4903  			expectedEqual: true,
  4904  			initial: initial{
  4905  				operatorGroup: &operatorsv1.OperatorGroup{
  4906  					ObjectMeta: metav1.ObjectMeta{
  4907  						Name:      "operator-group-1",
  4908  						Namespace: operatorNamespace,
  4909  					},
  4910  					Spec: operatorsv1.OperatorGroupSpec{
  4911  						Selector: &metav1.LabelSelector{
  4912  							MatchLabels: map[string]string{"app": "app-a"},
  4913  						},
  4914  					},
  4915  				},
  4916  				k8sObjs: []runtime.Object{
  4917  					&corev1.Namespace{
  4918  						ObjectMeta: metav1.ObjectMeta{
  4919  							Name: operatorNamespace,
  4920  						},
  4921  					},
  4922  					&corev1.Namespace{
  4923  						ObjectMeta: metav1.ObjectMeta{
  4924  							Name:   targetNamespace,
  4925  							Labels: map[string]string{"app": "app-a"},
  4926  						},
  4927  					},
  4928  					&rbacv1.ClusterRole{
  4929  						ObjectMeta: metav1.ObjectMeta{
  4930  							Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU",
  4931  							Labels: map[string]string{
  4932  								"olm.managed":         "true",
  4933  								"olm.owner":           "operator-group-1",
  4934  								"olm.owner.namespace": "operator-ns",
  4935  								"olm.owner.kind":      "OperatorGroup",
  4936  							},
  4937  						},
  4938  					},
  4939  					&rbacv1.ClusterRole{
  4940  						ObjectMeta: metav1.ObjectMeta{
  4941  							Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od",
  4942  							Labels: map[string]string{
  4943  								"olm.managed":         "true",
  4944  								"olm.owner":           "operator-group-1",
  4945  								"olm.owner.namespace": "operator-ns",
  4946  								"olm.owner.kind":      "OperatorGroup",
  4947  							},
  4948  						},
  4949  					},
  4950  					&rbacv1.ClusterRole{
  4951  						ObjectMeta: metav1.ObjectMeta{
  4952  							Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr",
  4953  							Labels: map[string]string{
  4954  								"olm.managed":         "true",
  4955  								"olm.owner":           "operator-group-1",
  4956  								"olm.owner.namespace": "operator-ns",
  4957  								"olm.owner.kind":      "OperatorGroup",
  4958  							},
  4959  						},
  4960  					}},
  4961  			},
  4962  			expectedStatus: operatorsv1.OperatorGroupStatus{
  4963  				Namespaces:  []string{targetNamespace},
  4964  				LastUpdated: &now,
  4965  			},
  4966  			final: final{objects: map[string][]runtime.Object{
  4967  				"": {
  4968  					&rbacv1.ClusterRole{
  4969  						ObjectMeta: metav1.ObjectMeta{
  4970  							Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU",
  4971  							Labels: map[string]string{
  4972  								"olm.managed":         "true",
  4973  								"olm.owner":           "operator-group-1",
  4974  								"olm.owner.namespace": "operator-ns",
  4975  								"olm.owner.kind":      "OperatorGroup",
  4976  							},
  4977  						},
  4978  					},
  4979  					&rbacv1.ClusterRole{
  4980  						ObjectMeta: metav1.ObjectMeta{
  4981  							Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od",
  4982  							Labels: map[string]string{
  4983  								"olm.managed":         "true",
  4984  								"olm.owner":           "operator-group-1",
  4985  								"olm.owner.namespace": "operator-ns",
  4986  								"olm.owner.kind":      "OperatorGroup",
  4987  							},
  4988  						},
  4989  					},
  4990  					&rbacv1.ClusterRole{
  4991  						ObjectMeta: metav1.ObjectMeta{
  4992  							Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr",
  4993  							Labels: map[string]string{
  4994  								"olm.managed":         "true",
  4995  								"olm.owner":           "operator-group-1",
  4996  								"olm.owner.namespace": "operator-ns",
  4997  								"olm.owner.kind":      "OperatorGroup",
  4998  							},
  4999  						},
  5000  					},
  5001  				},
  5002  			},
  5003  			},
  5004  		},
  5005  		{
  5006  			name:          "MatchingNamespace/CSVPresent/Found",
  5007  			expectedEqual: true,
  5008  			initial: initial{
  5009  				operatorGroup: &operatorsv1.OperatorGroup{
  5010  					ObjectMeta: metav1.ObjectMeta{
  5011  						Name:      "operator-group-1",
  5012  						Namespace: operatorNamespace,
  5013  					},
  5014  					Spec: operatorsv1.OperatorGroupSpec{
  5015  						Selector: &metav1.LabelSelector{
  5016  							MatchLabels: map[string]string{"app": "app-a"},
  5017  						},
  5018  					},
  5019  				},
  5020  				clientObjs: []runtime.Object{},
  5021  				csvs:       []*v1alpha1.ClusterServiceVersion{operatorCSV},
  5022  				k8sObjs: []runtime.Object{
  5023  					&corev1.Namespace{
  5024  						ObjectMeta: metav1.ObjectMeta{
  5025  							Name:   operatorNamespace,
  5026  							Labels: map[string]string{"app": "app-a"},
  5027  						},
  5028  					},
  5029  					&corev1.Namespace{
  5030  						ObjectMeta: metav1.ObjectMeta{
  5031  							Name:   targetNamespace,
  5032  							Labels: map[string]string{"app": "app-a"},
  5033  						},
  5034  					},
  5035  					ownedDeployment,
  5036  					serviceAccount,
  5037  					role,
  5038  					roleBinding,
  5039  				},
  5040  				crds: []*apiextensionsv1.CustomResourceDefinition{crd},
  5041  			},
  5042  			expectedStatus: operatorsv1.OperatorGroupStatus{
  5043  				Namespaces:  []string{operatorNamespace, targetNamespace},
  5044  				LastUpdated: &now,
  5045  			},
  5046  			final: final{objects: map[string][]runtime.Object{
  5047  				operatorNamespace: {
  5048  					withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: operatorNamespace + "," + targetNamespace, operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  5049  					annotatedDeployment,
  5050  				},
  5051  				targetNamespace: {
  5052  					withLabels(
  5053  						withAnnotations(targetCSV.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  5054  						labels.Merge(targetCSV.GetLabels(), map[string]string{v1alpha1.CopiedLabelKey: operatorNamespace}),
  5055  					),
  5056  					&rbacv1.Role{
  5057  						TypeMeta: metav1.TypeMeta{
  5058  							Kind:       "Role",
  5059  							APIVersion: rbacv1.GroupName,
  5060  						},
  5061  						ObjectMeta: metav1.ObjectMeta{
  5062  							ResourceVersion: "0",
  5063  							Name:            "csv-role",
  5064  							Namespace:       targetNamespace,
  5065  							Labels: map[string]string{
  5066  								"olm.managed":         "true",
  5067  								"olm.copiedFrom":      "operator-ns",
  5068  								"olm.owner":           "csv1",
  5069  								"olm.owner.namespace": "target-ns",
  5070  								"olm.owner.kind":      "ClusterServiceVersion",
  5071  							},
  5072  							OwnerReferences: []metav1.OwnerReference{
  5073  								ownerutil.NonBlockingOwner(targetCSV),
  5074  							},
  5075  						},
  5076  						Rules: permissions[0].Rules,
  5077  					},
  5078  					&rbacv1.RoleBinding{
  5079  						TypeMeta: metav1.TypeMeta{
  5080  							Kind:       "RoleBinding",
  5081  							APIVersion: rbacv1.GroupName,
  5082  						},
  5083  						ObjectMeta: metav1.ObjectMeta{
  5084  							ResourceVersion: "0",
  5085  							Name:            "csv-rolebinding",
  5086  							Namespace:       targetNamespace,
  5087  							Labels: map[string]string{
  5088  								"olm.managed":         "true",
  5089  								"olm.copiedFrom":      "operator-ns",
  5090  								"olm.owner":           "csv1",
  5091  								"olm.owner.namespace": "target-ns",
  5092  								"olm.owner.kind":      "ClusterServiceVersion",
  5093  							},
  5094  							OwnerReferences: []metav1.OwnerReference{
  5095  								ownerutil.NonBlockingOwner(targetCSV),
  5096  							},
  5097  						},
  5098  						Subjects: []rbacv1.Subject{
  5099  							{
  5100  								Kind:      rbacv1.ServiceAccountKind,
  5101  								Name:      serviceAccount.GetName(),
  5102  								Namespace: operatorNamespace,
  5103  							},
  5104  						},
  5105  						RoleRef: rbacv1.RoleRef{
  5106  							APIGroup: rbacv1.GroupName,
  5107  							Kind:     role.GroupVersionKind().Kind,
  5108  							Name:     "csv-role",
  5109  						},
  5110  					},
  5111  				},
  5112  			}},
  5113  		},
  5114  		{
  5115  			name:          "MatchingNamespace/CSVPresent/Found/ExplicitTargetNamespaces",
  5116  			expectedEqual: true,
  5117  			initial: initial{
  5118  				operatorGroup: &operatorsv1.OperatorGroup{
  5119  					ObjectMeta: metav1.ObjectMeta{
  5120  						Name:      "operator-group-1",
  5121  						Namespace: operatorNamespace,
  5122  					},
  5123  					Spec: operatorsv1.OperatorGroupSpec{
  5124  						TargetNamespaces: []string{operatorNamespace, targetNamespace},
  5125  					},
  5126  				},
  5127  				clientObjs: []runtime.Object{},
  5128  				csvs:       []*v1alpha1.ClusterServiceVersion{operatorCSV},
  5129  				k8sObjs: []runtime.Object{
  5130  					&corev1.Namespace{
  5131  						ObjectMeta: metav1.ObjectMeta{
  5132  							Name: operatorNamespace,
  5133  						},
  5134  					},
  5135  					&corev1.Namespace{
  5136  						ObjectMeta: metav1.ObjectMeta{
  5137  							Name: targetNamespace,
  5138  						},
  5139  					},
  5140  					ownedDeployment,
  5141  					serviceAccount,
  5142  					role,
  5143  					roleBinding,
  5144  				},
  5145  				crds: []*apiextensionsv1.CustomResourceDefinition{crd},
  5146  			},
  5147  			expectedStatus: operatorsv1.OperatorGroupStatus{
  5148  				Namespaces:  []string{operatorNamespace, targetNamespace},
  5149  				LastUpdated: &now,
  5150  			},
  5151  			final: final{objects: map[string][]runtime.Object{
  5152  				operatorNamespace: {
  5153  					withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: operatorNamespace + "," + targetNamespace, operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  5154  					annotatedDeployment,
  5155  				},
  5156  				targetNamespace: {
  5157  					withLabels(
  5158  						withAnnotations(targetCSV.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  5159  						labels.Merge(targetCSV.GetLabels(), map[string]string{v1alpha1.CopiedLabelKey: operatorNamespace}),
  5160  					),
  5161  					&rbacv1.Role{
  5162  						TypeMeta: metav1.TypeMeta{
  5163  							Kind:       "Role",
  5164  							APIVersion: rbacv1.GroupName,
  5165  						},
  5166  						ObjectMeta: metav1.ObjectMeta{
  5167  							ResourceVersion: "0",
  5168  							Name:            "csv-role",
  5169  							Namespace:       targetNamespace,
  5170  							Labels: map[string]string{
  5171  								"olm.managed":         "true",
  5172  								"olm.copiedFrom":      "operator-ns",
  5173  								"olm.owner":           "csv1",
  5174  								"olm.owner.namespace": "target-ns",
  5175  								"olm.owner.kind":      "ClusterServiceVersion",
  5176  							},
  5177  							OwnerReferences: []metav1.OwnerReference{
  5178  								ownerutil.NonBlockingOwner(targetCSV),
  5179  							},
  5180  						},
  5181  						Rules: permissions[0].Rules,
  5182  					},
  5183  					&rbacv1.RoleBinding{
  5184  						TypeMeta: metav1.TypeMeta{
  5185  							Kind:       "RoleBinding",
  5186  							APIVersion: rbacv1.GroupName,
  5187  						},
  5188  						ObjectMeta: metav1.ObjectMeta{
  5189  							ResourceVersion: "0",
  5190  							Name:            "csv-rolebinding",
  5191  							Namespace:       targetNamespace,
  5192  							Labels: map[string]string{
  5193  								"olm.managed":         "true",
  5194  								"olm.copiedFrom":      "operator-ns",
  5195  								"olm.owner":           "csv1",
  5196  								"olm.owner.namespace": "target-ns",
  5197  								"olm.owner.kind":      "ClusterServiceVersion",
  5198  							},
  5199  							OwnerReferences: []metav1.OwnerReference{
  5200  								ownerutil.NonBlockingOwner(targetCSV),
  5201  							},
  5202  						},
  5203  						Subjects: []rbacv1.Subject{
  5204  							{
  5205  								Kind:      rbacv1.ServiceAccountKind,
  5206  								Name:      serviceAccount.GetName(),
  5207  								Namespace: operatorNamespace,
  5208  							},
  5209  						},
  5210  						RoleRef: rbacv1.RoleRef{
  5211  							APIGroup: rbacv1.GroupName,
  5212  							Kind:     role.GroupVersionKind().Kind,
  5213  							Name:     "csv-role",
  5214  						},
  5215  					},
  5216  				},
  5217  			}},
  5218  		},
  5219  		{
  5220  			name:          "AllNamespaces/CSVPresent/Found",
  5221  			expectedEqual: true,
  5222  			initial: initial{
  5223  				operatorGroup: &operatorsv1.OperatorGroup{
  5224  					ObjectMeta: metav1.ObjectMeta{
  5225  						Name:      "operator-group-1",
  5226  						Namespace: operatorNamespace,
  5227  						Labels:    map[string]string{"app": "app-a"},
  5228  					},
  5229  					Spec: operatorsv1.OperatorGroupSpec{},
  5230  				},
  5231  				clientObjs: []runtime.Object{},
  5232  				csvs:       []*v1alpha1.ClusterServiceVersion{operatorCSV},
  5233  				k8sObjs: []runtime.Object{
  5234  					&corev1.Namespace{
  5235  						ObjectMeta: metav1.ObjectMeta{
  5236  							Name:        operatorNamespace,
  5237  							Labels:      map[string]string{"app": "app-a"},
  5238  							Annotations: map[string]string{"test": "annotation"},
  5239  						},
  5240  					},
  5241  					&corev1.Namespace{
  5242  						ObjectMeta: metav1.ObjectMeta{
  5243  							Name:        targetNamespace,
  5244  							Labels:      map[string]string{"app": "app-a"},
  5245  							Annotations: map[string]string{"test": "annotation"},
  5246  						},
  5247  					},
  5248  					ownedDeployment,
  5249  					serviceAccount,
  5250  					role,
  5251  					roleBinding,
  5252  				},
  5253  				crds: []*apiextensionsv1.CustomResourceDefinition{crd},
  5254  			},
  5255  			expectedStatus: operatorsv1.OperatorGroupStatus{
  5256  				Namespaces:  []string{corev1.NamespaceAll},
  5257  				LastUpdated: &now,
  5258  			},
  5259  			final: final{objects: map[string][]runtime.Object{
  5260  				operatorNamespace: {
  5261  					withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: "", operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  5262  					annotatedGlobalDeployment,
  5263  				},
  5264  				"": {
  5265  					&rbacv1.ClusterRole{
  5266  						TypeMeta: metav1.TypeMeta{
  5267  							Kind:       "ClusterRole",
  5268  							APIVersion: rbacv1.GroupName,
  5269  						},
  5270  						ObjectMeta: metav1.ObjectMeta{
  5271  							Name: "csv-role",
  5272  							Labels: map[string]string{
  5273  								"olm.managed":         "true",
  5274  								"olm.owner":           "csv1",
  5275  								"olm.owner.namespace": "operator-ns",
  5276  								"olm.owner.kind":      "ClusterServiceVersion",
  5277  							},
  5278  						},
  5279  						Rules: append(permissions[0].Rules, rbacv1.PolicyRule{
  5280  							Verbs:     ViewVerbs,
  5281  							APIGroups: []string{corev1.GroupName},
  5282  							Resources: []string{"namespaces"},
  5283  						}),
  5284  					},
  5285  					&rbacv1.ClusterRoleBinding{
  5286  						TypeMeta: metav1.TypeMeta{
  5287  							Kind:       "ClusterRoleBinding",
  5288  							APIVersion: rbacv1.GroupName,
  5289  						},
  5290  						ObjectMeta: metav1.ObjectMeta{
  5291  							Name: "csv-rolebinding",
  5292  							Labels: map[string]string{
  5293  								"olm.managed":         "true",
  5294  								"olm.owner":           "csv1",
  5295  								"olm.owner.namespace": "operator-ns",
  5296  								"olm.owner.kind":      "ClusterServiceVersion",
  5297  							},
  5298  						},
  5299  						Subjects: []rbacv1.Subject{
  5300  							{
  5301  								Kind:      rbacv1.ServiceAccountKind,
  5302  								Name:      serviceAccount.GetName(),
  5303  								Namespace: operatorNamespace,
  5304  							},
  5305  						},
  5306  						RoleRef: rbacv1.RoleRef{
  5307  							APIGroup: rbacv1.GroupName,
  5308  							Kind:     "ClusterRole",
  5309  							Name:     "csv-role",
  5310  						},
  5311  					},
  5312  				},
  5313  				targetNamespace: {
  5314  					withLabels(
  5315  						withAnnotations(targetCSV.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}),
  5316  						labels.Merge(targetCSV.GetLabels(), map[string]string{v1alpha1.CopiedLabelKey: operatorNamespace}),
  5317  					),
  5318  				},
  5319  			}},
  5320  		},
  5321  		{
  5322  			name:          "AllNamespaces/CSVPresent/Found/PruneMissingProvidedAPI/StaticProvidedAPIs",
  5323  			expectedEqual: true,
  5324  			initial: initial{
  5325  				operatorGroup: &operatorsv1.OperatorGroup{
  5326  					TypeMeta: metav1.TypeMeta{
  5327  						Kind:       operatorsv1.OperatorGroupKind,
  5328  						APIVersion: operatorsv1.GroupVersion.String(),
  5329  					},
  5330  					ObjectMeta: metav1.ObjectMeta{
  5331  						Name:      "operator-group-1",
  5332  						Namespace: operatorNamespace,
  5333  						Labels:    map[string]string{"app": "app-a"},
  5334  						Annotations: map[string]string{
  5335  							operatorsv1.OperatorGroupProvidedAPIsAnnotationKey: "missing.fake.api.group",
  5336  						},
  5337  					},
  5338  					Spec: operatorsv1.OperatorGroupSpec{
  5339  						StaticProvidedAPIs: true,
  5340  					},
  5341  				},
  5342  				k8sObjs: []runtime.Object{
  5343  					&corev1.Namespace{
  5344  						ObjectMeta: metav1.ObjectMeta{
  5345  							Name:        operatorNamespace,
  5346  							Labels:      map[string]string{"app": "app-a"},
  5347  							Annotations: map[string]string{"test": "annotation"},
  5348  						},
  5349  					},
  5350  				},
  5351  			},
  5352  			expectedStatus: operatorsv1.OperatorGroupStatus{
  5353  				Namespaces:  []string{corev1.NamespaceAll},
  5354  				LastUpdated: &now,
  5355  			},
  5356  			final: final{objects: map[string][]runtime.Object{
  5357  				operatorNamespace: {
  5358  					&operatorsv1.OperatorGroup{
  5359  						TypeMeta: metav1.TypeMeta{
  5360  							Kind:       operatorsv1.OperatorGroupKind,
  5361  							APIVersion: operatorsv1.GroupVersion.String(),
  5362  						},
  5363  						ObjectMeta: metav1.ObjectMeta{
  5364  							Name:      "operator-group-1",
  5365  							Namespace: operatorNamespace,
  5366  							Labels:    map[string]string{"app": "app-a"},
  5367  							Annotations: map[string]string{
  5368  								operatorsv1.OperatorGroupProvidedAPIsAnnotationKey: "missing.fake.api.group",
  5369  							},
  5370  						},
  5371  						Spec: operatorsv1.OperatorGroupSpec{
  5372  							StaticProvidedAPIs: true,
  5373  						},
  5374  						Status: operatorsv1.OperatorGroupStatus{
  5375  							Namespaces:  []string{corev1.NamespaceAll},
  5376  							LastUpdated: &now,
  5377  						},
  5378  					},
  5379  				},
  5380  			}},
  5381  		},
  5382  		{
  5383  			name:          "AllNamespaces/CSVPresent/InstallModeNotSupported",
  5384  			expectedEqual: true,
  5385  			initial: initial{
  5386  				operatorGroup: &operatorsv1.OperatorGroup{
  5387  					ObjectMeta: metav1.ObjectMeta{
  5388  						Name:      "operator-group-1",
  5389  						Namespace: operatorNamespace,
  5390  					},
  5391  					Spec: operatorsv1.OperatorGroupSpec{},
  5392  				},
  5393  				clientObjs: []runtime.Object{},
  5394  				csvs: []*v1alpha1.ClusterServiceVersion{withInstallModes(operatorCSV.DeepCopy(), []v1alpha1.InstallMode{
  5395  					{
  5396  						Type:      v1alpha1.InstallModeTypeAllNamespaces,
  5397  						Supported: false,
  5398  					},
  5399  				})},
  5400  				k8sObjs: []runtime.Object{
  5401  					&corev1.Namespace{
  5402  						ObjectMeta: metav1.ObjectMeta{
  5403  							Name:        operatorNamespace,
  5404  							Annotations: map[string]string{"test": "annotation"},
  5405  						},
  5406  					},
  5407  					&corev1.Namespace{
  5408  						ObjectMeta: metav1.ObjectMeta{
  5409  							Name:        targetNamespace,
  5410  							Annotations: map[string]string{"test": "annotation"},
  5411  						},
  5412  					},
  5413  					ownedDeployment,
  5414  					serviceAccount,
  5415  					role,
  5416  					roleBinding,
  5417  				},
  5418  				crds: []*apiextensionsv1.CustomResourceDefinition{crd},
  5419  			},
  5420  			expectedStatus: operatorsv1.OperatorGroupStatus{
  5421  				Namespaces:  []string{corev1.NamespaceAll},
  5422  				LastUpdated: &now,
  5423  			},
  5424  			final: final{objects: map[string][]runtime.Object{
  5425  				operatorNamespace: {
  5426  					withPhase(
  5427  						withInstallModes(
  5428  							withAnnotations(operatorCSV.DeepCopy(), map[string]string{
  5429  								operatorsv1.OperatorGroupTargetsAnnotationKey:   "",
  5430  								operatorsv1.OperatorGroupAnnotationKey:          "operator-group-1",
  5431  								operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace,
  5432  							}).(*v1alpha1.ClusterServiceVersion),
  5433  							[]v1alpha1.InstallMode{
  5434  								{
  5435  									Type:      v1alpha1.InstallModeTypeAllNamespaces,
  5436  									Supported: false,
  5437  								},
  5438  							}), v1alpha1.CSVPhaseFailed,
  5439  						v1alpha1.CSVReasonUnsupportedOperatorGroup,
  5440  						"AllNamespaces InstallModeType not supported, cannot configure to watch all namespaces",
  5441  						now),
  5442  				},
  5443  			}},
  5444  		},
  5445  	}
  5446  
  5447  	copyObjs := func(objs []runtime.Object) []runtime.Object {
  5448  		if len(objs) < 1 {
  5449  			return nil
  5450  		}
  5451  
  5452  		copied := make([]runtime.Object, len(objs))
  5453  		for i, obj := range objs {
  5454  			copied[i] = obj.DeepCopyObject()
  5455  		}
  5456  
  5457  		return copied
  5458  	}
  5459  
  5460  	for _, tt := range tests {
  5461  		t.Run(tt.name, func(t *testing.T) {
  5462  			// Pick out Namespaces
  5463  			var namespaces []string
  5464  			for _, obj := range tt.initial.k8sObjs {
  5465  				if ns, ok := obj.(*corev1.Namespace); ok {
  5466  					namespaces = append(namespaces, ns.GetName())
  5467  				}
  5468  			}
  5469  
  5470  			// DeepCopy test fixtures to prevent test case pollution
  5471  			var (
  5472  				operatorGroup = tt.initial.operatorGroup.DeepCopy()
  5473  				clientObjs    = copyObjs(append(tt.initial.clientObjs, operatorGroup))
  5474  				k8sObjs       = copyObjs(tt.initial.k8sObjs)
  5475  				extObjs       []runtime.Object
  5476  				regObjs       = copyObjs(tt.initial.apis)
  5477  			)
  5478  
  5479  			// Create test operator
  5480  			ctx, cancel := context.WithCancel(context.Background())
  5481  			defer cancel()
  5482  
  5483  			var partials []runtime.Object
  5484  			for _, csv := range tt.initial.csvs {
  5485  				clientObjs = append(clientObjs, csv.DeepCopy())
  5486  				partials = append(partials, &metav1.PartialObjectMetadata{
  5487  					TypeMeta: metav1.TypeMeta{
  5488  						Kind:       "ClusterServiceVersion",
  5489  						APIVersion: v1alpha1.SchemeGroupVersion.String(),
  5490  					},
  5491  					ObjectMeta: csv.ObjectMeta,
  5492  				})
  5493  			}
  5494  			for _, crd := range tt.initial.crds {
  5495  				extObjs = append(extObjs, crd.DeepCopy())
  5496  				partials = append(partials, &metav1.PartialObjectMetadata{
  5497  					TypeMeta: metav1.TypeMeta{
  5498  						Kind:       "CustomResourceDefinition",
  5499  						APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
  5500  					},
  5501  					ObjectMeta: crd.ObjectMeta,
  5502  				})
  5503  			}
  5504  			l := logrus.New()
  5505  			l.SetLevel(logrus.DebugLevel)
  5506  			l = l.WithField("test", tt.name).Logger
  5507  			op, err := NewFakeOperator(
  5508  				ctx,
  5509  				withClock(clockFake),
  5510  				withNamespaces(namespaces...),
  5511  				withOperatorNamespace(operatorNamespace),
  5512  				withClientObjs(clientObjs...),
  5513  				withK8sObjs(k8sObjs...),
  5514  				withExtObjs(extObjs...),
  5515  				withRegObjs(regObjs...),
  5516  				withPartialMetadata(partials...),
  5517  				withLogger(l),
  5518  			)
  5519  			require.NoError(t, err)
  5520  
  5521  			simulateSuccessfulRollout := func(csv *v1alpha1.ClusterServiceVersion) {
  5522  				// Get the deployment, which should exist
  5523  				namespace := operatorGroup.GetNamespace()
  5524  				dep, err := op.opClient.GetDeployment(namespace, deploymentName)
  5525  				require.NoError(t, err)
  5526  
  5527  				// Force it healthy
  5528  				dep.Status.Replicas = 1
  5529  				dep.Status.UpdatedReplicas = 1
  5530  				dep.Status.AvailableReplicas = 1
  5531  				dep.Status.Conditions = []appsv1.DeploymentCondition{{
  5532  					Type:   appsv1.DeploymentAvailable,
  5533  					Status: corev1.ConditionTrue,
  5534  				}}
  5535  				_, err = op.opClient.KubernetesInterface().AppsV1().Deployments(namespace).UpdateStatus(ctx, dep, metav1.UpdateOptions{})
  5536  				require.NoError(t, err)
  5537  
  5538  				// Wait for the lister cache to catch up
  5539  				err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) {
  5540  					deployment, err := op.lister.AppsV1().DeploymentLister().Deployments(namespace).Get(dep.GetName())
  5541  					if err != nil || deployment == nil {
  5542  						return false, err
  5543  					}
  5544  
  5545  					for _, condition := range deployment.Status.Conditions {
  5546  						if condition.Type == appsv1.DeploymentAvailable {
  5547  							return condition.Status == corev1.ConditionTrue, nil
  5548  						}
  5549  					}
  5550  
  5551  					return false, nil
  5552  				})
  5553  				require.NoError(t, err)
  5554  			}
  5555  
  5556  			err = op.syncOperatorGroups(operatorGroup)
  5557  			require.NoError(t, err)
  5558  
  5559  			// Wait on operator group updated status to be in the cache as it is required for later CSV operations
  5560  			err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) {
  5561  				og, err := op.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(operatorGroup.GetNamespace()).Get(operatorGroup.GetName())
  5562  				if err != nil {
  5563  					return false, err
  5564  				}
  5565  				sort.Strings(tt.expectedStatus.Namespaces)
  5566  				sort.Strings(og.Status.Namespaces)
  5567  				if !reflect.DeepEqual(tt.expectedStatus, og.Status) {
  5568  					return false, err
  5569  				}
  5570  
  5571  				operatorGroup = og
  5572  
  5573  				return true, nil
  5574  			})
  5575  			require.NoError(t, err)
  5576  
  5577  			// This must be done (at least) twice to have annotateCSVs run in syncOperatorGroups and to catch provided API changes
  5578  			// syncOperatorGroups is eventually consistent and may return errors until the cache has caught up with the cluster (fake client here)
  5579  			err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) { // Throw away timeout errors since any timeout will coincide with err != nil anyway
  5580  				err = op.syncOperatorGroups(operatorGroup)
  5581  				return err == nil, nil
  5582  			})
  5583  			require.NoError(t, err)
  5584  
  5585  			var foundErr error
  5586  			// Sync csvs enough to get them back to a succeeded state
  5587  			err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) {
  5588  				csvs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(operatorNamespace).List(ctx, metav1.ListOptions{})
  5589  				if err != nil {
  5590  					return false, err
  5591  				}
  5592  
  5593  				for _, csv := range csvs.Items {
  5594  					if csv.Status.Phase == v1alpha1.CSVPhaseInstalling {
  5595  						simulateSuccessfulRollout(&csv)
  5596  					}
  5597  
  5598  					if err := op.syncClusterServiceVersion(&csv); err != nil {
  5599  						return false, fmt.Errorf("failed to syncClusterServiceVersion: %w", err)
  5600  					}
  5601  
  5602  					if err := op.syncCopyCSV(&csv); err != nil && !tt.ignoreCopyError {
  5603  						return false, fmt.Errorf("failed to syncCopyCSV: %w", err)
  5604  					}
  5605  				}
  5606  
  5607  				for namespace, objects := range tt.final.objects {
  5608  					if err := RequireObjectsInCache(t, op.lister, namespace, objects, true); err != nil {
  5609  						foundErr = err
  5610  						return false, nil
  5611  					}
  5612  				}
  5613  
  5614  				return true, nil
  5615  			})
  5616  			t.Log(foundErr)
  5617  			require.NoError(t, err)
  5618  
  5619  			operatorGroup, err = op.client.OperatorsV1().OperatorGroups(operatorGroup.GetNamespace()).Get(ctx, operatorGroup.GetName(), metav1.GetOptions{})
  5620  			require.NoError(t, err)
  5621  			sort.Strings(tt.expectedStatus.Namespaces)
  5622  			sort.Strings(operatorGroup.Status.Namespaces)
  5623  			assert.Equal(t, tt.expectedStatus, operatorGroup.Status)
  5624  
  5625  			for namespace, objects := range tt.final.objects {
  5626  				var foundErr error
  5627  				err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) {
  5628  					foundErr = CheckObjectsInNamespace(t, op.opClient, op.client, namespace, objects)
  5629  					return foundErr == nil, nil
  5630  				})
  5631  				t.Log(foundErr)
  5632  				require.NoError(t, err)
  5633  			}
  5634  		})
  5635  	}
  5636  }
  5637  
  5638  func TestOperatorGroupConditions(t *testing.T) {
  5639  	logrus.SetLevel(logrus.DebugLevel)
  5640  	clockFake := utilclocktesting.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)))
  5641  
  5642  	operatorNamespace := "operator-ns"
  5643  	serviceAccount := serviceAccount("sa", operatorNamespace)
  5644  
  5645  	type initial struct {
  5646  		operatorGroup *operatorsv1.OperatorGroup
  5647  		clientObjs    []runtime.Object
  5648  		k8sObjs       []runtime.Object
  5649  	}
  5650  
  5651  	tests := []struct {
  5652  		initial            initial
  5653  		name               string
  5654  		expectedConditions []metav1.Condition
  5655  		expectError        bool
  5656  	}{
  5657  		{
  5658  			name: "ValidOperatorGroup/NoServiceAccount",
  5659  			initial: initial{
  5660  				operatorGroup: &operatorsv1.OperatorGroup{
  5661  					ObjectMeta: metav1.ObjectMeta{
  5662  						Name:      "operator-group-1",
  5663  						Namespace: operatorNamespace,
  5664  						UID:       "135e02a5-a7e2-44e7-abaa-88c63838993c",
  5665  					},
  5666  					Spec: operatorsv1.OperatorGroupSpec{
  5667  						TargetNamespaces: []string{operatorNamespace},
  5668  					},
  5669  				},
  5670  				k8sObjs: []runtime.Object{
  5671  					&corev1.Namespace{
  5672  						ObjectMeta: metav1.ObjectMeta{
  5673  							Name: operatorNamespace,
  5674  						},
  5675  					},
  5676  				},
  5677  			},
  5678  			expectError:        false,
  5679  			expectedConditions: []metav1.Condition{},
  5680  		},
  5681  		{
  5682  			name: "ValidOperatorGroup/ValidServiceAccount",
  5683  			initial: initial{
  5684  				operatorGroup: &operatorsv1.OperatorGroup{
  5685  					ObjectMeta: metav1.ObjectMeta{
  5686  						Name:      "operator-group-1",
  5687  						Namespace: operatorNamespace,
  5688  						UID:       "135e02a5-a7e2-44e7-abaa-88c63838993c",
  5689  					},
  5690  					Spec: operatorsv1.OperatorGroupSpec{
  5691  						ServiceAccountName: "sa",
  5692  						TargetNamespaces:   []string{operatorNamespace},
  5693  					},
  5694  				},
  5695  				k8sObjs: []runtime.Object{
  5696  					&corev1.Namespace{
  5697  						ObjectMeta: metav1.ObjectMeta{
  5698  							Name: operatorNamespace,
  5699  						},
  5700  					},
  5701  					serviceAccount,
  5702  				},
  5703  			},
  5704  			expectError:        false,
  5705  			expectedConditions: []metav1.Condition{},
  5706  		},
  5707  		{
  5708  			name: "BadOperatorGroup/MissingServiceAccount",
  5709  			initial: initial{
  5710  				operatorGroup: &operatorsv1.OperatorGroup{
  5711  					ObjectMeta: metav1.ObjectMeta{
  5712  						Name:      "operator-group-1",
  5713  						Namespace: operatorNamespace,
  5714  						UID:       "135e02a5-a7e2-44e7-abaa-88c63838993c",
  5715  					},
  5716  					Spec: operatorsv1.OperatorGroupSpec{
  5717  						ServiceAccountName: "nonexistingSA",
  5718  						TargetNamespaces:   []string{operatorNamespace},
  5719  					},
  5720  				},
  5721  				k8sObjs: []runtime.Object{
  5722  					&corev1.Namespace{
  5723  						ObjectMeta: metav1.ObjectMeta{
  5724  							Name: operatorNamespace,
  5725  						},
  5726  					},
  5727  				},
  5728  			},
  5729  			expectError: true,
  5730  			expectedConditions: []metav1.Condition{
  5731  				{
  5732  					Type:    operatorsv1.OperatorGroupServiceAccountCondition,
  5733  					Status:  metav1.ConditionTrue,
  5734  					Reason:  operatorsv1.OperatorGroupServiceAccountReason,
  5735  					Message: "ServiceAccount nonexistingSA not found",
  5736  				},
  5737  			},
  5738  		},
  5739  		{
  5740  			name: "BadOperatorGroup/MultipleOperatorGroups",
  5741  			initial: initial{
  5742  				operatorGroup: &operatorsv1.OperatorGroup{
  5743  					ObjectMeta: metav1.ObjectMeta{
  5744  						Name:      "operator-group-1",
  5745  						Namespace: operatorNamespace,
  5746  						UID:       "135e02a5-a7e2-44e7-abaa-88c63838993c",
  5747  					},
  5748  					Spec: operatorsv1.OperatorGroupSpec{
  5749  						TargetNamespaces: []string{operatorNamespace},
  5750  					},
  5751  				},
  5752  				clientObjs: []runtime.Object{
  5753  					&operatorsv1.OperatorGroup{
  5754  						ObjectMeta: metav1.ObjectMeta{
  5755  							Name:      "operator-group-2",
  5756  							Namespace: operatorNamespace,
  5757  							UID:       "cdc9643e-7c52-4f7c-ae75-28ccb6aec97d",
  5758  						},
  5759  						Spec: operatorsv1.OperatorGroupSpec{
  5760  							TargetNamespaces: []string{operatorNamespace, "some-namespace"},
  5761  						},
  5762  					},
  5763  				},
  5764  				k8sObjs: []runtime.Object{
  5765  					&corev1.Namespace{
  5766  						ObjectMeta: metav1.ObjectMeta{
  5767  							Name: operatorNamespace,
  5768  						},
  5769  					},
  5770  				},
  5771  			},
  5772  			expectError: true,
  5773  			expectedConditions: []metav1.Condition{
  5774  				{
  5775  					Type:    operatorsv1.MutlipleOperatorGroupCondition,
  5776  					Status:  metav1.ConditionTrue,
  5777  					Reason:  operatorsv1.MultipleOperatorGroupsReason,
  5778  					Message: "Multiple OperatorGroup found in the same namespace",
  5779  				},
  5780  			},
  5781  		},
  5782  	}
  5783  
  5784  	for _, tt := range tests {
  5785  		t.Run(tt.name, func(t *testing.T) {
  5786  			namespaces := []string{}
  5787  			// Pick out Namespaces
  5788  			for _, obj := range tt.initial.k8sObjs {
  5789  				if ns, ok := obj.(*corev1.Namespace); ok {
  5790  					namespaces = append(namespaces, ns.GetName())
  5791  				}
  5792  			}
  5793  
  5794  			// Append operatorGroup to initialObjs
  5795  			tt.initial.clientObjs = append(tt.initial.clientObjs, tt.initial.operatorGroup)
  5796  
  5797  			// Create test operator
  5798  			ctx, cancel := context.WithCancel(context.TODO())
  5799  			defer cancel()
  5800  			op, err := NewFakeOperator(
  5801  				ctx,
  5802  				withClock(clockFake),
  5803  				withNamespaces(namespaces...),
  5804  				withOperatorNamespace(operatorNamespace),
  5805  				withClientObjs(tt.initial.clientObjs...),
  5806  				withK8sObjs(tt.initial.k8sObjs...),
  5807  			)
  5808  			require.NoError(t, err)
  5809  
  5810  			err = op.syncOperatorGroups(tt.initial.operatorGroup)
  5811  			if !tt.expectError {
  5812  				require.NoError(t, err)
  5813  			}
  5814  
  5815  			operatorGroup, err := op.client.OperatorsV1().OperatorGroups(tt.initial.operatorGroup.GetNamespace()).Get(context.TODO(), tt.initial.operatorGroup.GetName(), metav1.GetOptions{})
  5816  			require.NoError(t, err)
  5817  			assert.Equal(t, len(tt.expectedConditions), len(operatorGroup.Status.Conditions))
  5818  			if len(tt.expectedConditions) > 0 {
  5819  				for _, cond := range tt.expectedConditions {
  5820  					c := meta.FindStatusCondition(operatorGroup.Status.Conditions, cond.Type)
  5821  					assert.Equal(t, cond.Status, c.Status)
  5822  					assert.Equal(t, cond.Reason, c.Reason)
  5823  					assert.Equal(t, cond.Message, c.Message)
  5824  				}
  5825  			}
  5826  		})
  5827  	}
  5828  }
  5829  
  5830  func RequireObjectsInCache(t *testing.T, lister operatorlister.OperatorLister, namespace string, objects []runtime.Object, doCompare bool) error {
  5831  	for _, object := range objects {
  5832  		var err error
  5833  		var fetched runtime.Object
  5834  		switch o := object.(type) {
  5835  		case *appsv1.Deployment:
  5836  			fetched, err = lister.AppsV1().DeploymentLister().Deployments(namespace).Get(o.GetName())
  5837  		case *rbacv1.ClusterRole:
  5838  			fetched, err = lister.RbacV1().ClusterRoleLister().Get(o.GetName())
  5839  		case *rbacv1.Role:
  5840  			fetched, err = lister.RbacV1().RoleLister().Roles(namespace).Get(o.GetName())
  5841  		case *rbacv1.ClusterRoleBinding:
  5842  			fetched, err = lister.RbacV1().ClusterRoleBindingLister().Get(o.GetName())
  5843  		case *rbacv1.RoleBinding:
  5844  			fetched, err = lister.RbacV1().RoleBindingLister().RoleBindings(namespace).Get(o.GetName())
  5845  		case *v1alpha1.ClusterServiceVersion:
  5846  			fetched, err = lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(namespace).Get(o.GetName())
  5847  			// We don't care about finalizers
  5848  			object.(*v1alpha1.ClusterServiceVersion).Finalizers = nil
  5849  			fetched.(*v1alpha1.ClusterServiceVersion).Finalizers = nil
  5850  		case *operatorsv1.OperatorGroup:
  5851  			fetched, err = lister.OperatorsV1().OperatorGroupLister().OperatorGroups(namespace).Get(o.GetName())
  5852  		default:
  5853  			require.Failf(t, "couldn't find expected object", "%#v", object)
  5854  		}
  5855  		if err != nil {
  5856  			if apierrors.IsNotFound(err) {
  5857  				return err
  5858  			}
  5859  			return errors.Join(err, fmt.Errorf("namespace: %v, error: %v", namespace, err))
  5860  		}
  5861  		if doCompare {
  5862  			if !reflect.DeepEqual(object, fetched) {
  5863  				return fmt.Errorf("expected object didn't match: %s", cmp.Diff(object, fetched))
  5864  			}
  5865  		}
  5866  	}
  5867  	return nil
  5868  }
  5869  
  5870  func RequireObjectsInNamespace(t *testing.T, opClient operatorclient.ClientInterface, client versioned.Interface, namespace string, objects []runtime.Object) {
  5871  	require.NoError(t, CheckObjectsInNamespace(t, opClient, client, namespace, objects))
  5872  }
  5873  
  5874  func CheckObjectsInNamespace(t *testing.T, opClient operatorclient.ClientInterface, client versioned.Interface, namespace string, objects []runtime.Object) error {
  5875  	for _, object := range objects {
  5876  		var err error
  5877  		var fetched runtime.Object
  5878  		var name string
  5879  		switch o := object.(type) {
  5880  		case *appsv1.Deployment:
  5881  			name = o.GetName()
  5882  			fetched, err = opClient.GetDeployment(namespace, o.GetName())
  5883  		case *rbacv1.ClusterRole:
  5884  			name = o.GetName()
  5885  			fetched, err = opClient.GetClusterRole(o.GetName())
  5886  		case *rbacv1.Role:
  5887  			name = o.GetName()
  5888  			fetched, err = opClient.GetRole(namespace, o.GetName())
  5889  		case *rbacv1.ClusterRoleBinding:
  5890  			name = o.GetName()
  5891  			fetched, err = opClient.GetClusterRoleBinding(o.GetName())
  5892  		case *rbacv1.RoleBinding:
  5893  			name = o.GetName()
  5894  			fetched, err = opClient.GetRoleBinding(namespace, o.GetName())
  5895  		case *v1alpha1.ClusterServiceVersion:
  5896  			name = o.GetName()
  5897  			fetched, err = client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), o.GetName(), metav1.GetOptions{})
  5898  			// This protects against small timing issues in sync tests
  5899  			// We generally don't care about the conditions (state history in this case, unlike many kube resources)
  5900  			// and this will still check that the final state is correct
  5901  			object.(*v1alpha1.ClusterServiceVersion).Status.Conditions = nil
  5902  			fetched.(*v1alpha1.ClusterServiceVersion).Status.Conditions = nil
  5903  			object.(*v1alpha1.ClusterServiceVersion).Finalizers = nil
  5904  			fetched.(*v1alpha1.ClusterServiceVersion).Finalizers = nil
  5905  		case *operatorsv1.OperatorGroup:
  5906  			name = o.GetName()
  5907  			fetched, err = client.OperatorsV1().OperatorGroups(namespace).Get(context.TODO(), o.GetName(), metav1.GetOptions{})
  5908  		case *corev1.Secret:
  5909  			name = o.GetName()
  5910  			fetched, err = opClient.GetSecret(namespace, o.GetName())
  5911  		default:
  5912  			require.Failf(t, "couldn't find expected object", "%#v", object)
  5913  		}
  5914  		if err != nil {
  5915  			return fmt.Errorf("couldn't fetch %s/%s: %w", namespace, name, err)
  5916  		}
  5917  		if diff := cmp.Diff(object, fetched); diff != "" {
  5918  			return fmt.Errorf("incorrect object %s/%s: %v", namespace, name, diff)
  5919  		}
  5920  	}
  5921  	return nil
  5922  }
  5923  
  5924  func TestCARotation(t *testing.T) {
  5925  	logrus.SetLevel(logrus.DebugLevel)
  5926  	namespace := "ns"
  5927  
  5928  	defaultOperatorGroup := &operatorsv1.OperatorGroup{
  5929  		TypeMeta: metav1.TypeMeta{
  5930  			Kind:       "OperatorGroup",
  5931  			APIVersion: operatorsv1.SchemeGroupVersion.String(),
  5932  		},
  5933  		ObjectMeta: metav1.ObjectMeta{
  5934  			Name:      "default",
  5935  			Namespace: namespace,
  5936  		},
  5937  		Spec: operatorsv1.OperatorGroupSpec{},
  5938  		Status: operatorsv1.OperatorGroupStatus{
  5939  			Namespaces: []string{namespace},
  5940  		},
  5941  	}
  5942  
  5943  	defaultTemplateAnnotations := map[string]string{
  5944  		operatorsv1.OperatorGroupTargetsAnnotationKey:   namespace,
  5945  		operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace,
  5946  		operatorsv1.OperatorGroupAnnotationKey:          defaultOperatorGroup.GetName(),
  5947  	}
  5948  
  5949  	// Generate valid and expired CA fixtures
  5950  	expiresAt := metav1.NewTime(install.CalculateCertExpiration(time.Now()))
  5951  	rotateAt := metav1.NewTime(install.CalculateCertRotatesAt(expiresAt.Time))
  5952  
  5953  	lastUpdate := metav1.Time{Time: time.Now().UTC()}
  5954  
  5955  	validCA, err := generateCA(expiresAt.Time, install.Organization)
  5956  	require.NoError(t, err)
  5957  	validCAPEM, _, err := validCA.ToPEM()
  5958  	require.NoError(t, err)
  5959  	validCAHash := certs.PEMSHA256(validCAPEM)
  5960  
  5961  	ownerReference := metav1.OwnerReference{
  5962  		Kind: v1alpha1.ClusterServiceVersionKind,
  5963  		UID:  "csv-uid",
  5964  	}
  5965  
  5966  	type operatorConfig struct {
  5967  		apiReconciler APIIntersectionReconciler
  5968  		apiLabeler    labeler.Labeler
  5969  	}
  5970  	type initial struct {
  5971  		csvs       []*v1alpha1.ClusterServiceVersion
  5972  		clientObjs []runtime.Object
  5973  		crds       []runtime.Object
  5974  		objs       []runtime.Object
  5975  		apis       []runtime.Object
  5976  	}
  5977  	tests := []struct {
  5978  		name    string
  5979  		config  operatorConfig
  5980  		initial initial
  5981  	}{
  5982  		{
  5983  			// Happy path: cert is created and csv status contains the right cert dates
  5984  			name: "NoCertificate/CertificateCreated",
  5985  			initial: initial{
  5986  				csvs: []*v1alpha1.ClusterServiceVersion{
  5987  					withAPIServices(csvWithAnnotations(csv("csv1",
  5988  						namespace,
  5989  						"0.0.0",
  5990  						"",
  5991  						installStrategy("a1", nil, nil),
  5992  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  5993  						[]*apiextensionsv1.CustomResourceDefinition{},
  5994  						v1alpha1.CSVPhaseInstallReady,
  5995  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil),
  5996  				},
  5997  				clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")},
  5998  				// The rolebinding, service, and clusterRoleBinding have been added here as a workaround to fake client not supporting SSA
  5999  				objs: []runtime.Object{
  6000  					roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  6001  					service("a1-service", namespace, "a1", 80, ownerReference),
  6002  					clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  6003  				},
  6004  				crds: []runtime.Object{
  6005  					crd("c1", "v1", "g1"),
  6006  				},
  6007  			},
  6008  		}, {
  6009  			// If a CSV finds itself in the InstallReady phase with a valid certificate
  6010  			// it's likely that a deployment pod or other resource is gone and the installer will re-apply the
  6011  			// resources. If the certs exist and are valid, no need to rotate or update the csv status.
  6012  			name: "HasValidCertificate/ManagedPodDeleted/NoRotation",
  6013  			initial: initial{
  6014  				csvs: []*v1alpha1.ClusterServiceVersion{
  6015  					withUID(withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  6016  						namespace,
  6017  						"0.0.0",
  6018  						"",
  6019  						installStrategy("a1", nil, nil),
  6020  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  6021  						[]*apiextensionsv1.CustomResourceDefinition{},
  6022  						v1alpha1.CSVPhaseInstallReady,
  6023  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), rotateAt, lastUpdate), types.UID("csv-uid")).(*v1alpha1.ClusterServiceVersion),
  6024  				},
  6025  				clientObjs: []runtime.Object{defaultOperatorGroup},
  6026  				crds: []runtime.Object{
  6027  					crd("c1", "v1", "g1"),
  6028  				},
  6029  				apis: []runtime.Object{
  6030  					apiService("a1", "v1", "a1-service", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  6031  				},
  6032  				objs: []runtime.Object{
  6033  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  6034  						install.OLMCAHashAnnotationKey: validCAHash,
  6035  					})),
  6036  					withLabels(withAnnotations(withCA(keyPairToTLSSecret("a1-service-cert", namespace, signedServingPair(expiresAt.Time, validCA, []string{"a1-service.ns", "a1-service.ns.svc"})), validCAPEM), map[string]string{
  6037  						install.OLMCAHashAnnotationKey: validCAHash,
  6038  					}), map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}),
  6039  					service("a1-service", namespace, "a1", 80, ownerReference),
  6040  					serviceAccount("sa", namespace),
  6041  					role("a1-service-cert", namespace, []rbacv1.PolicyRule{
  6042  						{
  6043  							Verbs:         []string{"get"},
  6044  							APIGroups:     []string{""},
  6045  							Resources:     []string{"secrets"},
  6046  							ResourceNames: []string{"a1-service-cert"},
  6047  						},
  6048  					}),
  6049  					roleBinding("a1-service-cert", namespace, "a1-service-cert", "sa", namespace),
  6050  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  6051  						{
  6052  							Verbs:         []string{"get"},
  6053  							APIGroups:     []string{""},
  6054  							Resources:     []string{"configmaps"},
  6055  							ResourceNames: []string{"extension-apiserver-authentication"},
  6056  						},
  6057  					}),
  6058  					roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  6059  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  6060  						{
  6061  							Verbs:     []string{"create"},
  6062  							APIGroups: []string{"authentication.k8s.io"},
  6063  							Resources: []string{"tokenreviews"},
  6064  						},
  6065  						{
  6066  							Verbs:     []string{"create"},
  6067  							APIGroups: []string{"authentication.k8s.io"},
  6068  							Resources: []string{"subjectaccessreviews"},
  6069  						},
  6070  					}),
  6071  					// The clusterRoleBinding has been added here as a workaround to fake client not supporting SSA
  6072  					clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  6073  				},
  6074  			},
  6075  		}, {
  6076  			// If the cert secret is deleted, a new one is created
  6077  			name: "ValidCert/SecretMissing/NewCertCreated",
  6078  			initial: initial{
  6079  				csvs: []*v1alpha1.ClusterServiceVersion{
  6080  					withUID(withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1",
  6081  						namespace,
  6082  						"0.0.0",
  6083  						"",
  6084  						installStrategy("a1", nil, nil),
  6085  						[]*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")},
  6086  						[]*apiextensionsv1.CustomResourceDefinition{},
  6087  						v1alpha1.CSVPhaseInstallReady,
  6088  					), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), rotateAt, lastUpdate), types.UID("csv-uid")).(*v1alpha1.ClusterServiceVersion),
  6089  				},
  6090  				clientObjs: []runtime.Object{defaultOperatorGroup},
  6091  				crds: []runtime.Object{
  6092  					crd("c1", "v1", "g1"),
  6093  				},
  6094  				apis: []runtime.Object{
  6095  					apiService("a1", "v1", "a1-service", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
  6096  				},
  6097  				objs: []runtime.Object{
  6098  					deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
  6099  						install.OLMCAHashAnnotationKey: validCAHash,
  6100  					})),
  6101  					service("a1-service", namespace, "a1", 80, ownerReference),
  6102  					serviceAccount("sa", namespace),
  6103  					role("a1-service-cert", namespace, []rbacv1.PolicyRule{
  6104  						{
  6105  							Verbs:         []string{"get"},
  6106  							APIGroups:     []string{""},
  6107  							Resources:     []string{"secrets"},
  6108  							ResourceNames: []string{"a1-service-cert"},
  6109  						},
  6110  					}),
  6111  					roleBinding("a1-service-cert", namespace, "a1-service-cert", "sa", namespace),
  6112  					role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
  6113  						{
  6114  							Verbs:         []string{"get"},
  6115  							APIGroups:     []string{""},
  6116  							Resources:     []string{"configmaps"},
  6117  							ResourceNames: []string{"extension-apiserver-authentication"},
  6118  						},
  6119  					}),
  6120  					roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
  6121  					clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
  6122  						{
  6123  							Verbs:     []string{"create"},
  6124  							APIGroups: []string{"authentication.k8s.io"},
  6125  							Resources: []string{"tokenreviews"},
  6126  						},
  6127  						{
  6128  							Verbs:     []string{"create"},
  6129  							APIGroups: []string{"authentication.k8s.io"},
  6130  							Resources: []string{"subjectaccessreviews"},
  6131  						},
  6132  					}),
  6133  					// The clusterRoleBinding has been added here as a workaround to fake client not supporting SSA
  6134  					clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace),
  6135  				},
  6136  			},
  6137  		},
  6138  	}
  6139  
  6140  	for _, tt := range tests {
  6141  		t.Run(tt.name, func(t *testing.T) {
  6142  			// Create test operator
  6143  			ctx, cancel := context.WithCancel(context.TODO())
  6144  			defer cancel()
  6145  			clientObjects := tt.initial.clientObjs
  6146  			var partials []runtime.Object
  6147  			for _, csv := range tt.initial.csvs {
  6148  				clientObjects = append(clientObjects, csv)
  6149  				partials = append(partials, &metav1.PartialObjectMetadata{
  6150  					ObjectMeta: csv.ObjectMeta,
  6151  				})
  6152  			}
  6153  			op, err := NewFakeOperator(
  6154  				ctx,
  6155  				withNamespaces(namespace, "kube-system"),
  6156  				withClientObjs(clientObjects...),
  6157  				withK8sObjs(tt.initial.objs...),
  6158  				withExtObjs(tt.initial.crds...),
  6159  				withRegObjs(tt.initial.apis...),
  6160  				withPartialMetadata(partials...),
  6161  				withOperatorNamespace(namespace),
  6162  				withAPIReconciler(tt.config.apiReconciler),
  6163  				withAPILabeler(tt.config.apiLabeler),
  6164  			)
  6165  			require.NoError(t, err)
  6166  
  6167  			// run csv sync for each CSV
  6168  			for _, csv := range tt.initial.csvs {
  6169  				// sync works
  6170  				err := op.syncClusterServiceVersion(csv)
  6171  				require.NoError(t, err)
  6172  
  6173  				outCSV, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.Background(), csv.GetName(), metav1.GetOptions{})
  6174  				require.NoError(t, err)
  6175  
  6176  				require.Equal(t, outCSV.Status.Phase, v1alpha1.CSVPhaseInstalling)
  6177  
  6178  				for _, apiServiceDescriptor := range outCSV.GetAllAPIServiceDescriptions() {
  6179  					// Get secret with the certificate
  6180  					secretName := fmt.Sprintf("%s-service-cert", apiServiceDescriptor.DeploymentName)
  6181  					serviceSecret, err := op.opClient.GetSecret(csv.GetNamespace(), secretName)
  6182  					require.NoError(t, err)
  6183  					require.NotNil(t, serviceSecret)
  6184  
  6185  					// Extract certificate validity period
  6186  					start, end, err := GetServiceCertificaValidityPeriod(serviceSecret)
  6187  					require.NoError(t, err)
  6188  					require.NotNil(t, start)
  6189  					require.NotNil(t, end)
  6190  
  6191  					rotationTime := end.Add(-1 * install.DefaultCertMinFresh)
  6192  					// The csv status is updated after the certificate is created/rotated
  6193  					require.LessOrEqual(t, start.Unix(), outCSV.Status.CertsLastUpdated.Unix())
  6194  
  6195  					// Rotation time should always be the same between the certificate and the status
  6196  					require.Equal(t, rotationTime.Unix(), outCSV.Status.CertsRotateAt.Unix())
  6197  				}
  6198  			}
  6199  
  6200  			// get csvs in the cluster
  6201  			outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{}
  6202  			outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{})
  6203  			require.NoError(t, err)
  6204  			for _, csv := range outCSVs.Items {
  6205  				outCSVMap[csv.GetName()] = csv.DeepCopy()
  6206  			}
  6207  		})
  6208  	}
  6209  }
  6210  
  6211  func GetServiceCertificaValidityPeriod(serviceSecret *corev1.Secret) (start *time.Time, end *time.Time, err error) {
  6212  	// Extract certificate
  6213  	root := x509.NewCertPool()
  6214  	rootPEM, ok := serviceSecret.Data[install.OLMCAPEMKey]
  6215  	if !ok {
  6216  		return nil, nil, fmt.Errorf("could not find the service root certificate")
  6217  	}
  6218  
  6219  	ok = root.AppendCertsFromPEM(rootPEM)
  6220  	if !ok {
  6221  		return nil, nil, fmt.Errorf("could not append the service root certificate")
  6222  	}
  6223  
  6224  	certPEM, ok := serviceSecret.Data["tls.crt"]
  6225  	if !ok {
  6226  		return nil, nil, fmt.Errorf("could not find the service certificate")
  6227  	}
  6228  	block, _ := pem.Decode(certPEM)
  6229  
  6230  	cert, err := x509.ParseCertificate(block.Bytes)
  6231  	if err != nil {
  6232  		return nil, nil, err
  6233  	}
  6234  
  6235  	return &cert.NotBefore, &cert.NotAfter, nil
  6236  }
  6237  
  6238  func TestIsReplacing(t *testing.T) {
  6239  	logrus.SetLevel(logrus.DebugLevel)
  6240  	namespace := "ns"
  6241  
  6242  	type initial struct {
  6243  		csvs []runtime.Object
  6244  	}
  6245  	tests := []struct {
  6246  		name     string
  6247  		initial  initial
  6248  		in       *v1alpha1.ClusterServiceVersion
  6249  		expected *v1alpha1.ClusterServiceVersion
  6250  	}{
  6251  		{
  6252  			name: "QueryErr",
  6253  			in:   csv("name", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6254  			initial: initial{
  6255  				csvs: []runtime.Object{},
  6256  			},
  6257  			expected: nil,
  6258  		},
  6259  		{
  6260  			name: "CSVInCluster/NotReplacing",
  6261  			in:   csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6262  			initial: initial{
  6263  				csvs: []runtime.Object{
  6264  					csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6265  				},
  6266  			},
  6267  			expected: nil,
  6268  		},
  6269  		{
  6270  			name: "CSVInCluster/Replacing",
  6271  			in:   csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6272  			initial: initial{
  6273  				csvs: []runtime.Object{
  6274  					csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6275  				},
  6276  			},
  6277  			expected: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6278  		},
  6279  		{
  6280  			name: "CSVInCluster/ReplacingNotFound",
  6281  			in:   csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6282  			initial: initial{
  6283  				csvs: []runtime.Object{
  6284  					csv("csv3", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6285  				},
  6286  			},
  6287  			expected: nil,
  6288  		},
  6289  	}
  6290  	for _, tt := range tests {
  6291  		t.Run(tt.name, func(t *testing.T) {
  6292  			// Create test operator
  6293  			ctx, cancel := context.WithCancel(context.TODO())
  6294  			defer cancel()
  6295  			op, err := NewFakeOperator(ctx, withNamespaces(namespace), withClientObjs(tt.initial.csvs...))
  6296  			require.NoError(t, err)
  6297  
  6298  			require.Equal(t, tt.expected, op.isReplacing(tt.in))
  6299  		})
  6300  	}
  6301  }
  6302  
  6303  func TestIsBeingReplaced(t *testing.T) {
  6304  	namespace := "ns"
  6305  
  6306  	type initial struct {
  6307  		csvs map[string]*v1alpha1.ClusterServiceVersion
  6308  	}
  6309  	tests := []struct {
  6310  		name     string
  6311  		initial  initial
  6312  		in       *v1alpha1.ClusterServiceVersion
  6313  		expected *v1alpha1.ClusterServiceVersion
  6314  	}{
  6315  		{
  6316  			name:     "QueryErr",
  6317  			in:       csv("name", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6318  			expected: nil,
  6319  		},
  6320  		{
  6321  			name: "CSVInCluster/NotReplacing",
  6322  			in:   csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6323  			initial: initial{
  6324  				csvs: map[string]*v1alpha1.ClusterServiceVersion{
  6325  					"csv2": csv("csv2", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6326  				},
  6327  			},
  6328  			expected: nil,
  6329  		},
  6330  		{
  6331  			name: "CSVInCluster/Replacing",
  6332  			in:   csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6333  			initial: initial{
  6334  				csvs: map[string]*v1alpha1.ClusterServiceVersion{
  6335  					"csv2": csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6336  				},
  6337  			},
  6338  			expected: csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6339  		},
  6340  	}
  6341  	for _, tt := range tests {
  6342  		t.Run(tt.name, func(t *testing.T) {
  6343  			ctx, cancel := context.WithCancel(context.TODO())
  6344  			defer cancel()
  6345  			op, err := NewFakeOperator(ctx, withNamespaces(namespace))
  6346  			require.NoError(t, err)
  6347  
  6348  			require.Equal(t, tt.expected, op.isBeingReplaced(tt.in, tt.initial.csvs))
  6349  		})
  6350  	}
  6351  }
  6352  
  6353  func TestCheckReplacement(t *testing.T) {
  6354  	namespace := "ns"
  6355  
  6356  	type initial struct {
  6357  		csvs map[string]*v1alpha1.ClusterServiceVersion
  6358  	}
  6359  	tests := []struct {
  6360  		name     string
  6361  		initial  initial
  6362  		in       *v1alpha1.ClusterServiceVersion
  6363  		expected *v1alpha1.ClusterServiceVersion
  6364  	}{
  6365  		{
  6366  			name:     "QueryErr",
  6367  			in:       csv("name", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6368  			expected: nil,
  6369  		},
  6370  		{
  6371  			name: "CSVInCluster/NotReplacing",
  6372  			in:   csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6373  			initial: initial{
  6374  				csvs: map[string]*v1alpha1.ClusterServiceVersion{
  6375  					"csv2": csv("csv2", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6376  				},
  6377  			},
  6378  			expected: nil,
  6379  		},
  6380  		{
  6381  			name: "CSVInCluster/Replacing",
  6382  			in:   csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6383  			initial: initial{
  6384  				csvs: map[string]*v1alpha1.ClusterServiceVersion{
  6385  					"csv2": csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6386  				},
  6387  			},
  6388  			expected: csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded),
  6389  		},
  6390  	}
  6391  	for _, tt := range tests {
  6392  		t.Run(tt.name, func(t *testing.T) {
  6393  			ctx, cancel := context.WithCancel(context.TODO())
  6394  			defer cancel()
  6395  			op, err := NewFakeOperator(ctx, withNamespaces(namespace))
  6396  			require.NoError(t, err)
  6397  			require.Equal(t, tt.expected, op.isBeingReplaced(tt.in, tt.initial.csvs))
  6398  		})
  6399  	}
  6400  }
  6401  
  6402  func TestAPIServiceResourceErrorActionable(t *testing.T) {
  6403  	tests := []struct {
  6404  		name       string
  6405  		errs       []error
  6406  		actionable bool
  6407  	}{
  6408  		{
  6409  			name:       "Nil/Actionable",
  6410  			errs:       nil,
  6411  			actionable: true,
  6412  		},
  6413  		{
  6414  			name:       "Empty/Actionable",
  6415  			errs:       nil,
  6416  			actionable: true,
  6417  		},
  6418  		{
  6419  			name:       "Error/Actionable",
  6420  			errs:       []error{fmt.Errorf("err-a")},
  6421  			actionable: true,
  6422  		},
  6423  		{
  6424  			name:       "Errors/Actionable",
  6425  			errs:       []error{fmt.Errorf("err-a"), fmt.Errorf("err-b")},
  6426  			actionable: true,
  6427  		},
  6428  		{
  6429  			name:       "ContainsUnadoptable/NotActionable",
  6430  			errs:       []error{fmt.Errorf("err-a"), olmerrors.UnadoptableError{}},
  6431  			actionable: false,
  6432  		},
  6433  	}
  6434  
  6435  	for _, tt := range tests {
  6436  		t.Run(tt.name, func(t *testing.T) {
  6437  			op := &Operator{}
  6438  			aggregate := utilerrors.NewAggregate(tt.errs)
  6439  			require.Equal(t, tt.actionable, op.apiServiceResourceErrorActionable(aggregate))
  6440  		})
  6441  	}
  6442  }
  6443  
  6444  func crdWithConversionWebhook(crd *apiextensionsv1.CustomResourceDefinition, caBundle []byte) *apiextensionsv1.CustomResourceDefinition {
  6445  	crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
  6446  		Strategy: "Webhook",
  6447  		Webhook: &apiextensionsv1.WebhookConversion{
  6448  			ConversionReviewVersions: []string{"v1beta1"},
  6449  			ClientConfig: &apiextensionsv1.WebhookClientConfig{
  6450  				CABundle: caBundle,
  6451  			},
  6452  		},
  6453  	}
  6454  	return crd
  6455  }
  6456  
  6457  func csvWithConversionWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string) *v1alpha1.ClusterServiceVersion {
  6458  	return csvWithWebhook(csv, deploymentName, conversionCRDs, v1alpha1.ConversionWebhook)
  6459  }
  6460  
  6461  func csvWithValidatingAdmissionWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string) *v1alpha1.ClusterServiceVersion {
  6462  	return csvWithWebhook(csv, deploymentName, conversionCRDs, v1alpha1.ValidatingAdmissionWebhook)
  6463  }
  6464  
  6465  func csvWithMutatingAdmissionWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string) *v1alpha1.ClusterServiceVersion {
  6466  	return csvWithWebhook(csv, deploymentName, conversionCRDs, v1alpha1.MutatingAdmissionWebhook)
  6467  }
  6468  
  6469  func csvWithWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string, webhookType v1alpha1.WebhookAdmissionType) *v1alpha1.ClusterServiceVersion {
  6470  	sideEffectNone := admissionregistrationv1.SideEffectClassNone
  6471  	targetPort := intstr.FromInt(443)
  6472  	csv.Spec.WebhookDefinitions = []v1alpha1.WebhookDescription{
  6473  		{
  6474  			Type:                    webhookType,
  6475  			DeploymentName:          deploymentName,
  6476  			ContainerPort:           443,
  6477  			TargetPort:              &targetPort,
  6478  			SideEffects:             &sideEffectNone,
  6479  			ConversionCRDs:          conversionCRDs,
  6480  			AdmissionReviewVersions: []string{"v1beta1"},
  6481  		},
  6482  	}
  6483  	return csv
  6484  }
  6485  
  6486  func TestGetReplacementChain(t *testing.T) {
  6487  	CSV := func(name, replaces string) *v1alpha1.ClusterServiceVersion {
  6488  		return &v1alpha1.ClusterServiceVersion{
  6489  			ObjectMeta: metav1.ObjectMeta{
  6490  				Name: name,
  6491  			},
  6492  			Spec: v1alpha1.ClusterServiceVersionSpec{
  6493  				Replaces: replaces,
  6494  			},
  6495  		}
  6496  	}
  6497  
  6498  	for _, tc := range []struct {
  6499  		Name     string
  6500  		From     *v1alpha1.ClusterServiceVersion
  6501  		All      map[string]*v1alpha1.ClusterServiceVersion
  6502  		Expected []string
  6503  	}{
  6504  		{
  6505  			Name: "csv replaces itself",
  6506  			From: CSV("itself", "itself"),
  6507  			All: map[string]*v1alpha1.ClusterServiceVersion{
  6508  				"itself": CSV("itself", "itself"),
  6509  			},
  6510  			Expected: []string{"itself"},
  6511  		},
  6512  		{
  6513  			Name: "two csvs replace each other",
  6514  			From: CSV("a", "b"),
  6515  			All: map[string]*v1alpha1.ClusterServiceVersion{
  6516  				"a": CSV("a", "b"),
  6517  				"b": CSV("b", "a"),
  6518  			},
  6519  			Expected: []string{"a", "b"},
  6520  		},
  6521  		{
  6522  			Name: "starting from head of chain without cycles",
  6523  			From: CSV("a", "b"),
  6524  			All: map[string]*v1alpha1.ClusterServiceVersion{
  6525  				"a": CSV("a", "b"),
  6526  				"b": CSV("b", "c"),
  6527  				"c": CSV("c", ""),
  6528  			},
  6529  			Expected: []string{"a", "b", "c"},
  6530  		},
  6531  	} {
  6532  		t.Run(tc.Name, func(t *testing.T) {
  6533  			assert := assert.New(t)
  6534  			var actual []string
  6535  			for name := range (&Operator{}).getReplacementChain(tc.From, tc.All) {
  6536  				actual = append(actual, name)
  6537  			}
  6538  			assert.ElementsMatch(tc.Expected, actual)
  6539  		})
  6540  	}
  6541  }