github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/api/wrappers/deployment_install_client_test.go (about)

     1  package wrappers
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/golang/mock/gomock"
     9  	"github.com/pkg/errors"
    10  	"github.com/stretchr/testify/require"
    11  	corev1 "k8s.io/api/core/v1"
    12  	"k8s.io/apimachinery/pkg/api/equality"
    13  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/util/diff"
    16  
    17  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    18  	listerfakes "github.com/operator-framework/operator-lifecycle-manager/pkg/fakes/client-go/listers"
    19  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient/operatorclientmocks"
    20  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister/operatorlisterfakes"
    21  )
    22  
    23  var (
    24  	Controller         = false
    25  	BlockOwnerDeletion = false
    26  	WakeupInterval     = 5 * time.Second
    27  )
    28  
    29  func ownerReferenceFromCSV(csv *v1alpha1.ClusterServiceVersion) metav1.OwnerReference {
    30  	return metav1.OwnerReference{
    31  		APIVersion:         v1alpha1.SchemeGroupVersion.String(),
    32  		Kind:               v1alpha1.ClusterServiceVersionKind,
    33  		Name:               csv.GetName(),
    34  		UID:                csv.GetUID(),
    35  		Controller:         &Controller,
    36  		BlockOwnerDeletion: &BlockOwnerDeletion,
    37  	}
    38  }
    39  
    40  func TestEnsureServiceAccount(t *testing.T) {
    41  	testErr := errors.New("NaNaNaNaN") // used to ensure exact error returned
    42  	mockOwner := v1alpha1.ClusterServiceVersion{
    43  		TypeMeta: metav1.TypeMeta{
    44  			Kind:       v1alpha1.ClusterServiceVersionKind,
    45  			APIVersion: v1alpha1.ClusterServiceVersionAPIVersion,
    46  		},
    47  		ObjectMeta: metav1.ObjectMeta{
    48  			Name:      "csv-owner",
    49  			Namespace: "test-namespace",
    50  		},
    51  	}
    52  	type state struct {
    53  		namespace                  string
    54  		existingServiceAccount     *corev1.ServiceAccount
    55  		getServiceAccountError     error
    56  		createServiceAccountResult *corev1.ServiceAccount
    57  		createServiceAccountError  error
    58  		updateServiceAccountResult *corev1.ServiceAccount
    59  		updateServiceAccountError  error
    60  	}
    61  	type input struct {
    62  		serviceAccountName     string
    63  		serviceAccount         *corev1.ServiceAccount
    64  		serviceAccountToUpdate *corev1.ServiceAccount
    65  	}
    66  	type expect struct {
    67  		returnedServiceAccount *corev1.ServiceAccount
    68  		returnedError          error
    69  	}
    70  
    71  	tests := []struct {
    72  		name    string
    73  		subname string
    74  		state   state
    75  		input   input
    76  		expect  expect
    77  	}{
    78  		{
    79  			name:    "Bad ServiceAccount",
    80  			subname: "nil value",
    81  			expect: expect{
    82  				returnedError: ErrNilObject,
    83  			},
    84  		},
    85  		{
    86  			name:    "ServiceAccount already exists, owned by CSV",
    87  			subname: "returns existing SA when successfully fetched via Kubernetes API",
    88  			state: state{
    89  				namespace: "test-namespace",
    90  				existingServiceAccount: &corev1.ServiceAccount{
    91  					ObjectMeta: metav1.ObjectMeta{
    92  						Name: "test-service-account",
    93  						Labels: map[string]string{
    94  							"test": "existing-service-account-found",
    95  						},
    96  						OwnerReferences: []metav1.OwnerReference{
    97  							ownerReferenceFromCSV(&mockOwner),
    98  						},
    99  					},
   100  				},
   101  				getServiceAccountError:    nil,
   102  				createServiceAccountError: nil,
   103  			},
   104  			input: input{
   105  				serviceAccountName: "test-service-account",
   106  				serviceAccount: &corev1.ServiceAccount{
   107  					ObjectMeta: metav1.ObjectMeta{
   108  						Name: "test-service-account",
   109  					},
   110  				},
   111  			},
   112  			expect: expect{
   113  				returnedServiceAccount: &corev1.ServiceAccount{
   114  					ObjectMeta: metav1.ObjectMeta{
   115  						Name: "test-service-account",
   116  						Labels: map[string]string{
   117  							"test": "existing-service-account-found",
   118  						},
   119  						OwnerReferences: []metav1.OwnerReference{
   120  							ownerReferenceFromCSV(&mockOwner),
   121  						},
   122  					},
   123  				},
   124  				returnedError: nil,
   125  			},
   126  		},
   127  		{
   128  			name:    "ServiceAccount already exists, not owned by CSV",
   129  			subname: "returns existing SA when successfully fetched via Kubernetes API",
   130  			state: state{
   131  				namespace: "test-namespace",
   132  				existingServiceAccount: &corev1.ServiceAccount{
   133  					ObjectMeta: metav1.ObjectMeta{
   134  						Name:      "test-service-account",
   135  						Namespace: "test-namespace",
   136  						Labels: map[string]string{
   137  							"test": "existing-service-account-found",
   138  						},
   139  					},
   140  				},
   141  				updateServiceAccountResult: &corev1.ServiceAccount{
   142  					ObjectMeta: metav1.ObjectMeta{
   143  						Name: "test-service-account",
   144  						Labels: map[string]string{
   145  							"test": "existing-service-account-found",
   146  						},
   147  						OwnerReferences: []metav1.OwnerReference{
   148  							ownerReferenceFromCSV(&mockOwner),
   149  						},
   150  					},
   151  				},
   152  				getServiceAccountError:    nil,
   153  				createServiceAccountError: nil,
   154  				updateServiceAccountError: nil,
   155  			},
   156  			input: input{
   157  				serviceAccountName: "test-service-account",
   158  				serviceAccount: &corev1.ServiceAccount{
   159  					ObjectMeta: metav1.ObjectMeta{
   160  						Name: "test-service-account",
   161  					},
   162  				},
   163  				serviceAccountToUpdate: &corev1.ServiceAccount{
   164  					ObjectMeta: metav1.ObjectMeta{
   165  						Name:      "test-service-account",
   166  						Namespace: "test-namespace",
   167  						Labels: map[string]string{
   168  							"test": "existing-service-account-found",
   169  						},
   170  						OwnerReferences: []metav1.OwnerReference{
   171  							ownerReferenceFromCSV(&mockOwner),
   172  						},
   173  					},
   174  				},
   175  			},
   176  			expect: expect{
   177  				returnedServiceAccount: &corev1.ServiceAccount{
   178  					ObjectMeta: metav1.ObjectMeta{
   179  						Name: "test-service-account",
   180  						Labels: map[string]string{
   181  							"test": "existing-service-account-found",
   182  						},
   183  						OwnerReferences: []metav1.OwnerReference{
   184  							ownerReferenceFromCSV(&mockOwner),
   185  						},
   186  					},
   187  				},
   188  				returnedError: nil,
   189  			},
   190  		},
   191  		{
   192  			name:    "ServiceAccount already exists, not owned by CSV, update fails",
   193  			subname: "returns existing SA when successfully fetched via Kubernetes API",
   194  			state: state{
   195  				namespace: "test-namespace",
   196  				existingServiceAccount: &corev1.ServiceAccount{
   197  					ObjectMeta: metav1.ObjectMeta{
   198  						Name:      "test-service-account",
   199  						Namespace: "test-namespace",
   200  						Labels: map[string]string{
   201  							"test": "existing-service-account-found",
   202  						},
   203  					},
   204  				},
   205  				updateServiceAccountResult: nil,
   206  				getServiceAccountError:     nil,
   207  				createServiceAccountError:  nil,
   208  				updateServiceAccountError:  testErr,
   209  			},
   210  			input: input{
   211  				serviceAccountName: "test-service-account",
   212  				serviceAccount: &corev1.ServiceAccount{
   213  					ObjectMeta: metav1.ObjectMeta{
   214  						Name: "test-service-account",
   215  					},
   216  				},
   217  				serviceAccountToUpdate: &corev1.ServiceAccount{
   218  					ObjectMeta: metav1.ObjectMeta{
   219  						Name:      "test-service-account",
   220  						Namespace: "test-namespace",
   221  						Labels: map[string]string{
   222  							"test": "existing-service-account-found",
   223  						},
   224  						OwnerReferences: []metav1.OwnerReference{
   225  							ownerReferenceFromCSV(&mockOwner),
   226  						},
   227  					},
   228  				},
   229  			},
   230  			expect: expect{
   231  				returnedServiceAccount: nil,
   232  				returnedError:          testErr,
   233  			},
   234  		},
   235  		{
   236  			name:    "ServiceAccount already exists",
   237  			subname: "returns SA unmodified when fails to create it due to it already existing",
   238  			state: state{
   239  				namespace: "test-namespace",
   240  				existingServiceAccount: &corev1.ServiceAccount{
   241  					ObjectMeta: metav1.ObjectMeta{
   242  						Name:      "test-service-account",
   243  						Namespace: "test-namespace",
   244  						Labels: map[string]string{
   245  							"test": "existing-service-account-create-conflict",
   246  						},
   247  						OwnerReferences: []metav1.OwnerReference{
   248  							ownerReferenceFromCSV(&mockOwner),
   249  						},
   250  					},
   251  				},
   252  				getServiceAccountError: nil,
   253  				createServiceAccountError: apierrors.NewAlreadyExists(
   254  					corev1.Resource("serviceaccounts"), "test-service-account"),
   255  			},
   256  			input: input{
   257  				serviceAccountName: "test-service-account",
   258  				serviceAccount: &corev1.ServiceAccount{
   259  					ObjectMeta: metav1.ObjectMeta{
   260  						Name: "test-service-account",
   261  					},
   262  				},
   263  			},
   264  			expect: expect{
   265  				returnedServiceAccount: &corev1.ServiceAccount{
   266  					ObjectMeta: metav1.ObjectMeta{
   267  						Name:      "test-service-account",
   268  						Namespace: "test-namespace",
   269  						Labels: map[string]string{
   270  							"test": "existing-service-account-create-conflict",
   271  						},
   272  						OwnerReferences: []metav1.OwnerReference{
   273  							ownerReferenceFromCSV(&mockOwner),
   274  						},
   275  					},
   276  				},
   277  				returnedError: nil,
   278  			},
   279  		},
   280  		{
   281  			name:    "ServiceAccount doesn't already exist",
   282  			subname: "creates SA when no errors or existing SAs found",
   283  			state: state{
   284  				namespace: "test-namespace",
   285  				createServiceAccountResult: &corev1.ServiceAccount{
   286  					ObjectMeta: metav1.ObjectMeta{
   287  						Name: "test-service-account",
   288  						Labels: map[string]string{
   289  							"test": "successfully-created-serviceaccount",
   290  						},
   291  						OwnerReferences: []metav1.OwnerReference{
   292  							ownerReferenceFromCSV(&mockOwner),
   293  						},
   294  					},
   295  				},
   296  				createServiceAccountError: nil,
   297  				getServiceAccountError:    apierrors.NewNotFound(corev1.Resource("serviceaccounts"), "test-service-account"),
   298  			},
   299  			input: input{
   300  				serviceAccountName: "test-service-account",
   301  				serviceAccount: &corev1.ServiceAccount{
   302  					ObjectMeta: metav1.ObjectMeta{
   303  						Name: "test-service-account",
   304  					},
   305  				},
   306  			},
   307  			expect: expect{
   308  				returnedServiceAccount: &corev1.ServiceAccount{
   309  					ObjectMeta: metav1.ObjectMeta{
   310  						Name: "test-service-account",
   311  						Labels: map[string]string{
   312  							"test": "successfully-created-serviceaccount",
   313  						},
   314  						OwnerReferences: []metav1.OwnerReference{
   315  							ownerReferenceFromCSV(&mockOwner),
   316  						},
   317  					},
   318  				},
   319  				returnedError: nil,
   320  			},
   321  		},
   322  		{
   323  			name:    "ServiceAccount doesn't already exist",
   324  			subname: "creates SA successfully after getting NotFound error trying to fetch it",
   325  			state: state{
   326  				namespace: "test-namespace",
   327  				getServiceAccountError: apierrors.NewNotFound(
   328  					corev1.Resource("serviceaccounts"), "test-service-account"),
   329  				createServiceAccountResult: &corev1.ServiceAccount{
   330  					ObjectMeta: metav1.ObjectMeta{
   331  						Name: "test-service-account",
   332  						Labels: map[string]string{
   333  							"test": "successfully-created-serviceaccount-notfound-error",
   334  						},
   335  						OwnerReferences: []metav1.OwnerReference{
   336  							ownerReferenceFromCSV(&mockOwner),
   337  						},
   338  					},
   339  				},
   340  				createServiceAccountError: nil,
   341  			},
   342  			input: input{
   343  				serviceAccountName: "test-service-account",
   344  				serviceAccount: &corev1.ServiceAccount{
   345  					ObjectMeta: metav1.ObjectMeta{
   346  						Name: "test-service-account",
   347  					},
   348  				},
   349  			},
   350  			expect: expect{
   351  				returnedServiceAccount: &corev1.ServiceAccount{
   352  					ObjectMeta: metav1.ObjectMeta{
   353  						Name: "test-service-account",
   354  						Labels: map[string]string{
   355  							"test": "successfully-created-serviceaccount-notfound-error",
   356  						},
   357  						OwnerReferences: []metav1.OwnerReference{
   358  							ownerReferenceFromCSV(&mockOwner),
   359  						},
   360  					},
   361  				},
   362  				returnedError: nil,
   363  			},
   364  		},
   365  		{
   366  			name:    "Unknown errors",
   367  			subname: "returns unknown errors received trying to fetch SA from the kubernetes API",
   368  			state: state{
   369  				namespace:                 "test-namespace",
   370  				getServiceAccountError:    testErr,
   371  				createServiceAccountError: nil,
   372  			},
   373  			input: input{
   374  				serviceAccountName: "test-service-account",
   375  				serviceAccount: &corev1.ServiceAccount{
   376  					ObjectMeta: metav1.ObjectMeta{
   377  						Name: "test-service-account",
   378  						OwnerReferences: []metav1.OwnerReference{
   379  							ownerReferenceFromCSV(&mockOwner),
   380  						},
   381  					},
   382  				},
   383  			},
   384  			expect: expect{
   385  				returnedError: testErr,
   386  			},
   387  		},
   388  		{
   389  			name:    "Unknown errors",
   390  			subname: "returns unknown errors received trying to create SA",
   391  			state: state{
   392  				namespace: "test-namespace",
   393  				getServiceAccountError: apierrors.NewNotFound(
   394  					corev1.Resource("serviceaccounts"), "test-service-account"),
   395  				createServiceAccountError: testErr,
   396  			},
   397  			input: input{
   398  				serviceAccountName: "test-service-account",
   399  				serviceAccount: &corev1.ServiceAccount{
   400  					ObjectMeta: metav1.ObjectMeta{
   401  						Name: "test-service-account",
   402  						OwnerReferences: []metav1.OwnerReference{
   403  							ownerReferenceFromCSV(&mockOwner),
   404  						},
   405  					},
   406  				},
   407  			},
   408  			expect: expect{
   409  				returnedError: testErr,
   410  			},
   411  		},
   412  	}
   413  
   414  	for _, tt := range tests {
   415  		testName := fmt.Sprintf("%s: %s", tt.name, tt.subname)
   416  		t.Run(testName, func(t *testing.T) {
   417  			ctrl := gomock.NewController(t)
   418  			mockOpClient := operatorclientmocks.NewMockClientInterface(ctrl)
   419  			fakeLister := &operatorlisterfakes.FakeOperatorLister{}
   420  			fakeCoreV1Lister := &operatorlisterfakes.FakeCoreV1Lister{}
   421  			fakeServiceAccountLister := &listerfakes.FakeServiceAccountLister{}
   422  			fakeServiceAccountNamespacedLister := &listerfakes.FakeServiceAccountNamespaceLister{}
   423  			fakeServiceAccountNamespacedLister.GetReturns(tt.state.existingServiceAccount, tt.state.getServiceAccountError)
   424  			fakeServiceAccountLister.ServiceAccountsReturns(fakeServiceAccountNamespacedLister)
   425  			fakeCoreV1Lister.ServiceAccountListerReturns(fakeServiceAccountLister)
   426  			fakeLister.CoreV1Returns(fakeCoreV1Lister)
   427  
   428  			client := NewInstallStrategyDeploymentClient(mockOpClient, fakeLister, tt.state.namespace)
   429  
   430  			mockOpClient.EXPECT().
   431  				CreateServiceAccount(tt.input.serviceAccount).
   432  				Return(tt.state.createServiceAccountResult, tt.state.createServiceAccountError).
   433  				AnyTimes()
   434  
   435  			mockOpClient.EXPECT().
   436  				UpdateServiceAccount(tt.input.serviceAccountToUpdate).
   437  				Return(tt.state.updateServiceAccountResult, tt.state.updateServiceAccountError).
   438  				AnyTimes()
   439  
   440  			sa, err := client.EnsureServiceAccount(tt.input.serviceAccount, &mockOwner)
   441  
   442  			require.True(t, equality.Semantic.DeepEqual(tt.expect.returnedServiceAccount, sa),
   443  				"Resources do not match <expected, actual>: %s",
   444  				diff.ObjectDiff(tt.expect.returnedServiceAccount, sa))
   445  
   446  			require.EqualValues(t, tt.expect.returnedError, errors.Cause(err))
   447  
   448  			ctrl.Finish()
   449  		})
   450  	}
   451  }