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 }