Skip to content

Commit 45b9152

Browse files
Merge pull request #1044 from openshift-bot/synchronize-upstream
NO-ISSUE: Synchronize From Upstream Repositories
2 parents 3a0bba1 + de04e4b commit 45b9152

File tree

3 files changed

+258
-2
lines changed

3 files changed

+258
-2
lines changed

staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step_ensurer.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (o *StepEnsurer) EnsureServiceAccount(namespace string, sa *corev1.ServiceA
151151
return
152152
}
153153

154-
// Carrying secrets through the service account update.
154+
// Carrying secrets and annotations through the service account update.
155155
preSa, getErr := o.kubeClient.KubernetesInterface().CoreV1().ServiceAccounts(namespace).Get(context.TODO(),
156156
sa.Name,
157157
metav1.GetOptions{})
@@ -162,6 +162,16 @@ func (o *StepEnsurer) EnsureServiceAccount(namespace string, sa *corev1.ServiceA
162162
sa.Secrets = preSa.Secrets
163163
sa.OwnerReferences = mergedOwnerReferences(preSa.OwnerReferences, sa.OwnerReferences)
164164

165+
// Merge annotations, giving precedence to the new ones.
166+
if sa.Annotations == nil {
167+
sa.Annotations = make(map[string]string)
168+
}
169+
for k, v := range preSa.Annotations {
170+
if _, ok := sa.Annotations[k]; !ok {
171+
sa.Annotations[k] = v
172+
}
173+
}
174+
165175
sa.SetNamespace(namespace)
166176

167177
// Use DeepDerivative to check if new SA is the same as the old SA. If no field is changed, we skip the update call.

staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step_ensurer_test.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,21 @@ package catalog
33
import (
44
"testing"
55

6+
"github.com/golang/mock/gomock"
67
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
79

10+
corev1 "k8s.io/api/core/v1"
11+
apierrors "k8s.io/apimachinery/pkg/api/errors"
812
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
fakedynamic "k8s.io/client-go/dynamic/fake"
15+
k8sfake "k8s.io/client-go/kubernetes/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"
921
)
1022

1123
func TestMergedOwnerReferences(t *testing.T) {
@@ -188,3 +200,227 @@ func TestMergedOwnerReferences(t *testing.T) {
188200
})
189201
}
190202
}
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+
}

vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/step_ensurer.go

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)