@@ -3,9 +3,21 @@ package catalog
3
3
import (
4
4
"testing"
5
5
6
+ "github.com/golang/mock/gomock"
6
7
"github.com/stretchr/testify/assert"
8
+ "github.com/stretchr/testify/require"
7
9
10
+ corev1 "k8s.io/api/core/v1"
11
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
8
12
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13
+ "k8s.io/apimachinery/pkg/runtime"
14
+ k8sfake "k8s.io/client-go/kubernetes/fake"
15
+ fakedynamic "k8s.io/client-go/dynamic/fake"
16
+ k8stesting "k8s.io/client-go/testing"
17
+
18
+ "github.com/operator-framework/api/pkg/operators/v1alpha1"
19
+ "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
20
+ "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient/operatorclientmocks"
9
21
)
10
22
11
23
func TestMergedOwnerReferences (t * testing.T ) {
@@ -188,3 +200,227 @@ func TestMergedOwnerReferences(t *testing.T) {
188
200
})
189
201
}
190
202
}
203
+
204
+ func TestEnsureServiceAccount (t * testing.T ) {
205
+ namespace := "test-namespace"
206
+ saName := "test-sa"
207
+
208
+ tests := []struct {
209
+ name string
210
+ existingServiceAccount * corev1.ServiceAccount
211
+ newServiceAccount * corev1.ServiceAccount
212
+ expectedAnnotations map [string ]string
213
+ expectedStatus v1alpha1.StepStatus
214
+ expectError bool
215
+ createError error
216
+ getError error
217
+ updateError error
218
+ }{
219
+ {
220
+ name : "create new service account" ,
221
+ newServiceAccount : & corev1.ServiceAccount {
222
+ ObjectMeta : metav1.ObjectMeta {
223
+ Name : saName ,
224
+ Namespace : namespace ,
225
+ Annotations : map [string ]string {
226
+ "new-annotation" : "new-value" ,
227
+ },
228
+ },
229
+ },
230
+ expectedAnnotations : map [string ]string {
231
+ "new-annotation" : "new-value" ,
232
+ },
233
+ expectedStatus : v1alpha1 .StepStatusCreated ,
234
+ },
235
+ {
236
+ name : "update existing service account - preserve existing annotations" ,
237
+ existingServiceAccount : & corev1.ServiceAccount {
238
+ ObjectMeta : metav1.ObjectMeta {
239
+ Name : saName ,
240
+ Namespace : namespace ,
241
+ Annotations : map [string ]string {
242
+ "existing-annotation" : "existing-value" ,
243
+ "override-annotation" : "old-value" ,
244
+ },
245
+ },
246
+ Secrets : []corev1.ObjectReference {
247
+ {Name : "existing-secret" },
248
+ },
249
+ },
250
+ newServiceAccount : & corev1.ServiceAccount {
251
+ ObjectMeta : metav1.ObjectMeta {
252
+ Name : saName ,
253
+ Namespace : namespace ,
254
+ Annotations : map [string ]string {
255
+ "new-annotation" : "new-value" ,
256
+ "override-annotation" : "new-value" ,
257
+ },
258
+ },
259
+ },
260
+ expectedAnnotations : map [string ]string {
261
+ "existing-annotation" : "existing-value" ,
262
+ "new-annotation" : "new-value" ,
263
+ "override-annotation" : "new-value" ,
264
+ },
265
+ expectedStatus : v1alpha1 .StepStatusPresent ,
266
+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
267
+ },
268
+ {
269
+ name : "update existing service account - no annotations on new SA" ,
270
+ existingServiceAccount : & corev1.ServiceAccount {
271
+ ObjectMeta : metav1.ObjectMeta {
272
+ Name : saName ,
273
+ Namespace : namespace ,
274
+ Annotations : map [string ]string {
275
+ "existing-annotation" : "existing-value" ,
276
+ },
277
+ },
278
+ },
279
+ newServiceAccount : & corev1.ServiceAccount {
280
+ ObjectMeta : metav1.ObjectMeta {
281
+ Name : saName ,
282
+ Namespace : namespace ,
283
+ },
284
+ },
285
+ expectedAnnotations : map [string ]string {
286
+ "existing-annotation" : "existing-value" ,
287
+ },
288
+ expectedStatus : v1alpha1 .StepStatusPresent ,
289
+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
290
+ },
291
+ {
292
+ name : "update existing service account - preserve secrets" ,
293
+ existingServiceAccount : & corev1.ServiceAccount {
294
+ ObjectMeta : metav1.ObjectMeta {
295
+ Name : saName ,
296
+ Namespace : namespace ,
297
+ },
298
+ Secrets : []corev1.ObjectReference {
299
+ {Name : "secret-1" },
300
+ {Name : "secret-2" },
301
+ },
302
+ },
303
+ newServiceAccount : & corev1.ServiceAccount {
304
+ ObjectMeta : metav1.ObjectMeta {
305
+ Name : saName ,
306
+ Namespace : namespace ,
307
+ },
308
+ },
309
+ expectedAnnotations : map [string ]string {},
310
+ expectedStatus : v1alpha1 .StepStatusPresent ,
311
+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
312
+ },
313
+ {
314
+ name : "create error - not already exists" ,
315
+ newServiceAccount : & corev1.ServiceAccount {
316
+ ObjectMeta : metav1.ObjectMeta {
317
+ Name : saName ,
318
+ Namespace : namespace ,
319
+ },
320
+ },
321
+ createError : apierrors .NewInternalError (assert .AnError ),
322
+ expectError : true ,
323
+ },
324
+ {
325
+ name : "update error - get existing fails" ,
326
+ existingServiceAccount : & corev1.ServiceAccount {
327
+ ObjectMeta : metav1.ObjectMeta {
328
+ Name : saName ,
329
+ Namespace : namespace ,
330
+ },
331
+ },
332
+ newServiceAccount : & corev1.ServiceAccount {
333
+ ObjectMeta : metav1.ObjectMeta {
334
+ Name : saName ,
335
+ Namespace : namespace ,
336
+ },
337
+ },
338
+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
339
+ getError : apierrors .NewInternalError (assert .AnError ),
340
+ expectError : true ,
341
+ },
342
+ }
343
+
344
+ for _ , tc := range tests {
345
+ t .Run (tc .name , func (t * testing.T ) {
346
+ ctrl := gomock .NewController (t )
347
+ defer ctrl .Finish ()
348
+
349
+ // Create mock client
350
+ mockClient := operatorclientmocks .NewMockClientInterface (ctrl )
351
+
352
+ // Create fake kubernetes client
353
+ var objects []runtime.Object
354
+ if tc .existingServiceAccount != nil {
355
+ objects = append (objects , tc .existingServiceAccount )
356
+ }
357
+
358
+ fakeClient := k8sfake .NewSimpleClientset (objects ... )
359
+
360
+ // Setup expectations
361
+ mockClient .EXPECT ().KubernetesInterface ().Return (fakeClient ).AnyTimes ()
362
+
363
+ // Mock the create call
364
+ if tc .createError != nil {
365
+ // We need to intercept the create call and return the error
366
+ fakeClient .PrependReactor ("create" , "serviceaccounts" , func (action k8stesting.Action ) (handled bool , ret runtime.Object , err error ) {
367
+ return true , nil , tc .createError
368
+ })
369
+ }
370
+
371
+ // Mock the get call if needed
372
+ if tc .getError != nil {
373
+ fakeClient .PrependReactor ("get" , "serviceaccounts" , func (action k8stesting.Action ) (handled bool , ret runtime.Object , err error ) {
374
+ return true , nil , tc .getError
375
+ })
376
+ }
377
+
378
+ // Mock UpdateServiceAccount if the test expects an update
379
+ if tc .createError != nil && apierrors .IsAlreadyExists (tc .createError ) && tc .getError == nil {
380
+ // Calculate expected SA after merge
381
+ expectedSA := tc .newServiceAccount .DeepCopy ()
382
+ if tc .existingServiceAccount != nil {
383
+ expectedSA .Secrets = tc .existingServiceAccount .Secrets
384
+ // Merge annotations
385
+ if expectedSA .Annotations == nil {
386
+ expectedSA .Annotations = make (map [string ]string )
387
+ }
388
+ for k , v := range tc .existingServiceAccount .Annotations {
389
+ if _ , ok := expectedSA .Annotations [k ]; ! ok {
390
+ expectedSA .Annotations [k ] = v
391
+ }
392
+ }
393
+ }
394
+ expectedSA .SetNamespace (namespace )
395
+
396
+ mockClient .EXPECT ().UpdateServiceAccount (gomock .Any ()).DoAndReturn (func (sa * corev1.ServiceAccount ) (* corev1.ServiceAccount , error ) {
397
+ // Verify the merged service account has the expected annotations
398
+ assert .Equal (t , tc .expectedAnnotations , sa .Annotations )
399
+ // Verify secrets were preserved if they existed
400
+ if tc .existingServiceAccount != nil && len (tc .existingServiceAccount .Secrets ) > 0 {
401
+ assert .Equal (t , tc .existingServiceAccount .Secrets , sa .Secrets )
402
+ }
403
+ return sa , tc .updateError
404
+ }).MaxTimes (1 )
405
+ }
406
+
407
+ // Create StepEnsurer
408
+ ensurer := & StepEnsurer {
409
+ kubeClient : mockClient ,
410
+ crClient : fake .NewSimpleClientset (),
411
+ dynamicClient : fakedynamic .NewSimpleDynamicClient (runtime .NewScheme ()),
412
+ }
413
+
414
+ // Execute EnsureServiceAccount
415
+ status , err := ensurer .EnsureServiceAccount (namespace , tc .newServiceAccount )
416
+
417
+ // Verify results
418
+ if tc .expectError {
419
+ require .Error (t , err )
420
+ } else {
421
+ require .NoError (t , err )
422
+ assert .Equal (t , tc .expectedStatus , status )
423
+ }
424
+ })
425
+ }
426
+ }
0 commit comments