Skip to content

Commit e8ea84e

Browse files
authored
Always add finalizers after status updates (#609)
Make updating a resource's status more consistent for all secret controllers, by factoring out the Status.Update() calls to controller methods, and always setting the lastGeneraton from the reconciled object. Always call addFinalizer() after updateStatus() to avoid schema validation errors.
1 parent 6170409 commit e8ea84e

8 files changed

+250
-107
lines changed

controllers/common.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import (
1212
"github.com/go-logr/logr"
1313
"sigs.k8s.io/controller-runtime/pkg/client"
1414
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
15+
"sigs.k8s.io/controller-runtime/pkg/log"
1516

1617
secretsv1beta1 "github.com/hashicorp/vault-secrets-operator/api/v1beta1"
1718
"github.com/hashicorp/vault-secrets-operator/internal/common"
19+
"github.com/hashicorp/vault-secrets-operator/internal/consts"
1820
)
1921

2022
var (
@@ -233,3 +235,27 @@ func parseDurationString(duration, path string, min time.Duration) (time.Duratio
233235
func isInWindow(t1, t2 time.Time) bool {
234236
return t1.After(t2) || t1.Equal(t2)
235237
}
238+
239+
// maybeAddFinalizer updates client.Object with finalizer if it is not already
240+
// set. Return true if the object was updated, in which case the object's
241+
// ResourceVersion will have changed. This update should be handled by in the
242+
// caller.
243+
func maybeAddFinalizer(ctx context.Context, c client.Client, o client.Object, finalizer string) (bool, error) {
244+
if o.GetDeletionTimestamp() == nil && !controllerutil.ContainsFinalizer(o, finalizer) {
245+
// always call maybeAddFinalizer() after client.Client.Status.Update() to avoid
246+
// API validation errors due to changes to the status schema.
247+
logger := log.FromContext(ctx).WithValues("finalizer", finalizer)
248+
logger.V(consts.LogLevelTrace).Info("Adding finalizer",
249+
"finalizer", finalizer)
250+
controllerutil.AddFinalizer(o, finalizer)
251+
if err := c.Update(ctx, o); err != nil {
252+
logger.Error(err, "Failed to add finalizer")
253+
controllerutil.RemoveFinalizer(o, finalizer)
254+
return false, err
255+
}
256+
257+
return true, nil
258+
}
259+
260+
return false, nil
261+
}

controllers/common_test.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44
package controllers
55

66
import (
7+
"context"
78
"fmt"
89
"testing"
910
"time"
1011

1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
14+
apierrors "k8s.io/apimachinery/pkg/api/errors"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
18+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
19+
"sigs.k8s.io/controller-runtime/pkg/client"
20+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
21+
22+
secretsv1beta1 "github.com/hashicorp/vault-secrets-operator/api/v1beta1"
1323
)
1424

1525
func Test_dynamicHorizon(t *testing.T) {
@@ -202,3 +212,171 @@ func Test_isInWindow(t *testing.T) {
202212
})
203213
}
204214
}
215+
216+
func Test_maybeAddFinalizer(t *testing.T) {
217+
t.Parallel()
218+
219+
ctx := context.Background()
220+
clientBuilder := newClientBuilder()
221+
deletionTimestamp := metav1.NewTime(time.Now())
222+
223+
tests := []struct {
224+
name string
225+
o client.Object
226+
create bool
227+
finalizer string
228+
want bool
229+
wantFinalizers []string
230+
wantErr assert.ErrorAssertionFunc
231+
}{
232+
{
233+
name: "updated",
234+
o: &secretsv1beta1.VaultAuth{
235+
ObjectMeta: metav1.ObjectMeta{
236+
Namespace: "default",
237+
Name: "updated",
238+
},
239+
Spec: secretsv1beta1.VaultAuthSpec{
240+
Method: "kubernetes",
241+
},
242+
},
243+
create: true,
244+
finalizer: vaultAuthFinalizer,
245+
want: true,
246+
wantFinalizers: []string{vaultAuthFinalizer},
247+
wantErr: assert.NoError,
248+
},
249+
{
250+
name: "updated-with-multiple",
251+
o: &secretsv1beta1.VaultAuth{
252+
ObjectMeta: metav1.ObjectMeta{
253+
Namespace: "default",
254+
Name: "updated",
255+
Finalizers: []string{
256+
"other",
257+
},
258+
},
259+
Spec: secretsv1beta1.VaultAuthSpec{
260+
Method: "kubernetes",
261+
},
262+
},
263+
create: true,
264+
finalizer: vaultAuthFinalizer,
265+
want: true,
266+
wantFinalizers: []string{"other", vaultAuthFinalizer},
267+
wantErr: assert.NoError,
268+
},
269+
{
270+
name: "not-updated-exists-with-finalizer",
271+
o: &secretsv1beta1.VaultAuth{
272+
ObjectMeta: metav1.ObjectMeta{
273+
Namespace: "default",
274+
Name: "updated",
275+
Finalizers: []string{
276+
vaultAuthFinalizer,
277+
},
278+
},
279+
Spec: secretsv1beta1.VaultAuthSpec{
280+
Method: "kubernetes",
281+
},
282+
},
283+
create: true,
284+
finalizer: vaultAuthFinalizer,
285+
want: false,
286+
wantFinalizers: []string{vaultAuthFinalizer},
287+
wantErr: assert.NoError,
288+
},
289+
{
290+
name: "not-updated-inexistent-with-finalizer",
291+
o: &secretsv1beta1.VaultAuth{
292+
ObjectMeta: metav1.ObjectMeta{
293+
Namespace: "default",
294+
Name: "updated",
295+
Finalizers: []string{
296+
vaultAuthFinalizer,
297+
},
298+
},
299+
Spec: secretsv1beta1.VaultAuthSpec{
300+
Method: "kubernetes",
301+
},
302+
},
303+
finalizer: vaultAuthFinalizer,
304+
want: false,
305+
wantFinalizers: []string{vaultAuthFinalizer},
306+
wantErr: assert.NoError,
307+
},
308+
{
309+
name: "not-updated-has-deletion-timestamp",
310+
o: &secretsv1beta1.VaultAuth{
311+
ObjectMeta: metav1.ObjectMeta{
312+
Namespace: "default",
313+
Name: "updated",
314+
DeletionTimestamp: &deletionTimestamp,
315+
},
316+
Spec: secretsv1beta1.VaultAuthSpec{
317+
Method: "kubernetes",
318+
},
319+
},
320+
finalizer: vaultAuthFinalizer,
321+
want: false,
322+
wantFinalizers: []string(nil),
323+
wantErr: assert.NoError,
324+
},
325+
{
326+
name: "invalid-not-found",
327+
o: &secretsv1beta1.VaultAuth{
328+
ObjectMeta: metav1.ObjectMeta{
329+
Namespace: "default",
330+
Name: "updated",
331+
},
332+
Spec: secretsv1beta1.VaultAuthSpec{
333+
Method: "kubernetes",
334+
},
335+
},
336+
finalizer: vaultAuthFinalizer,
337+
want: false,
338+
wantFinalizers: []string{},
339+
wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool {
340+
return assert.True(t, apierrors.IsNotFound(err))
341+
},
342+
},
343+
}
344+
for _, tt := range tests {
345+
t.Run(tt.name, func(t *testing.T) {
346+
c := clientBuilder.Build()
347+
var origResourceVersion string
348+
if tt.create {
349+
require.NoError(t, c.Create(ctx, tt.o))
350+
origResourceVersion = tt.o.GetResourceVersion()
351+
}
352+
353+
got, err := maybeAddFinalizer(ctx, c, tt.o, tt.finalizer)
354+
if !tt.wantErr(t, err, fmt.Sprintf("maybeAddFinalizer(%v, %v, %v, %v)", ctx, c, tt.o, tt.finalizer)) {
355+
return
356+
}
357+
358+
assert.Equalf(t, tt.want, got, "maybeAddFinalizer(%v, %v, %v, %v)", ctx, c, tt.o, tt.finalizer)
359+
assert.Equalf(t, tt.wantFinalizers, tt.o.GetFinalizers(), "maybeAddFinalizer(%v, %v, %v, %v)", ctx, c, tt.o, tt.finalizer)
360+
361+
if tt.create {
362+
var updated secretsv1beta1.VaultAuth
363+
if assert.NoError(t, c.Get(ctx, client.ObjectKeyFromObject(tt.o), &updated)) {
364+
if tt.want {
365+
assert.NotEqual(t, origResourceVersion, tt.o.GetResourceVersion())
366+
} else {
367+
// ensure that the object was not updated.
368+
assert.Equal(t, origResourceVersion, tt.o.GetResourceVersion())
369+
}
370+
}
371+
}
372+
})
373+
}
374+
}
375+
376+
// newClientBuilder copied from helpers.
377+
func newClientBuilder() *fake.ClientBuilder {
378+
scheme := runtime.NewScheme()
379+
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
380+
utilruntime.Must(secretsv1beta1.AddToScheme(scheme))
381+
return fake.NewClientBuilder().WithScheme(scheme)
382+
}

controllers/hcpvaultsecretsapp_controller.go

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,7 @@ func (r *HCPVaultSecretsAppReconciler) Reconcile(ctx context.Context, req ctrl.R
7777
return ctrl.Result{}, err
7878
}
7979

80-
if o.GetDeletionTimestamp() == nil {
81-
if err := r.addFinalizer(ctx, o); err != nil {
82-
return ctrl.Result{}, err
83-
}
84-
} else {
80+
if o.GetDeletionTimestamp() != nil {
8581
logger.Info("Got deletion timestamp", "obj", o)
8682
return ctrl.Result{}, r.handleDeletion(ctx, o)
8783
}
@@ -185,8 +181,7 @@ func (r *HCPVaultSecretsAppReconciler) Reconcile(ctx context.Context, req ctrl.R
185181
r.Recorder.Event(o, corev1.EventTypeNormal, consts.ReasonSecretSync, "Secret sync not required")
186182
}
187183

188-
o.Status.LastGeneration = o.GetGeneration()
189-
if err := r.Status().Update(ctx, o); err != nil {
184+
if err := r.updateStatus(ctx, o); err != nil {
190185
return ctrl.Result{}, err
191186
}
192187

@@ -195,6 +190,17 @@ func (r *HCPVaultSecretsAppReconciler) Reconcile(ctx context.Context, req ctrl.R
195190
}, nil
196191
}
197192

193+
func (r *HCPVaultSecretsAppReconciler) updateStatus(ctx context.Context, o *secretsv1beta1.HCPVaultSecretsApp) error {
194+
o.Status.LastGeneration = o.GetGeneration()
195+
if err := r.Status().Update(ctx, o); err != nil {
196+
r.Recorder.Eventf(o, corev1.EventTypeWarning, consts.ReasonStatusUpdateError,
197+
"Failed to update the resource's status, err=%s", err)
198+
}
199+
200+
_, err := maybeAddFinalizer(ctx, r.Client, o, hcpVaultSecretsAppFinalizer)
201+
return err
202+
}
203+
198204
// SetupWithManager sets up the controller with the Manager.
199205
func (r *HCPVaultSecretsAppReconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
200206
r.referenceCache = newResourceReferenceCache()
@@ -258,16 +264,6 @@ func (r *HCPVaultSecretsAppReconciler) hvsClient(ctx context.Context, o *secrets
258264
return hvsclient.New(cl, nil), nil
259265
}
260266

261-
func (r *HCPVaultSecretsAppReconciler) addFinalizer(ctx context.Context, o client.Object) error {
262-
if !controllerutil.ContainsFinalizer(o, hcpVaultSecretsAppFinalizer) {
263-
controllerutil.AddFinalizer(o, hcpVaultSecretsAppFinalizer)
264-
if err := r.Client.Update(ctx, o); err != nil {
265-
return err
266-
}
267-
}
268-
return nil
269-
}
270-
271267
func (r *HCPVaultSecretsAppReconciler) handleDeletion(ctx context.Context, o client.Object) error {
272268
logger := log.FromContext(ctx)
273269
r.referenceCache.Remove(SecretTransformation, client.ObjectKeyFromObject(o))

controllers/vaultauth_controller.go

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,7 @@ func (r *VaultAuthReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
6262
return ctrl.Result{}, err
6363
}
6464

65-
if o.GetDeletionTimestamp() == nil {
66-
if err := r.addFinalizer(ctx, o); err != nil {
67-
return ctrl.Result{}, err
68-
}
69-
} else {
65+
if o.GetDeletionTimestamp() != nil {
7066
logger.Info("Got deletion timestamp", "obj", o)
7167
return r.handleFinalizer(ctx, o)
7268
}
@@ -131,25 +127,16 @@ func (r *VaultAuthReconciler) recordEvent(a *secretsv1beta1.VaultAuth, reason, m
131127
r.Recorder.Eventf(a, eventType, reason, msg, i...)
132128
}
133129

134-
func (r *VaultAuthReconciler) updateStatus(ctx context.Context, a *secretsv1beta1.VaultAuth) error {
130+
func (r *VaultAuthReconciler) updateStatus(ctx context.Context, o *secretsv1beta1.VaultAuth) error {
135131
logger := log.FromContext(ctx)
136-
metrics.SetResourceStatus("vaultauth", a, a.Status.Valid)
137-
if err := r.Status().Update(ctx, a); err != nil {
132+
metrics.SetResourceStatus("vaultauth", o, o.Status.Valid)
133+
if err := r.Status().Update(ctx, o); err != nil {
138134
logger.Error(err, "Failed to update the resource's status")
139135
return err
140136
}
141-
return nil
142-
}
143-
144-
func (r *VaultAuthReconciler) addFinalizer(ctx context.Context, o *secretsv1beta1.VaultAuth) error {
145-
if !controllerutil.ContainsFinalizer(o, vaultAuthFinalizer) {
146-
controllerutil.AddFinalizer(o, vaultAuthFinalizer)
147-
if err := r.Client.Update(ctx, o); err != nil {
148-
return err
149-
}
150-
}
151137

152-
return nil
138+
_, err := maybeAddFinalizer(ctx, r.Client, o, vaultAuthFinalizer)
139+
return err
153140
}
154141

155142
func (r *VaultAuthReconciler) handleFinalizer(ctx context.Context, o *secretsv1beta1.VaultAuth) (ctrl.Result, error) {

controllers/vaultconnection_controller.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ func (r *VaultConnectionReconciler) Reconcile(ctx context.Context, req ctrl.Requ
5959
return ctrl.Result{}, err
6060
}
6161

62-
if o.GetDeletionTimestamp() == nil {
63-
if err := r.addFinalizer(ctx, o); err != nil {
64-
return ctrl.Result{}, err
65-
}
66-
} else {
62+
if o.GetDeletionTimestamp() != nil {
6763
logger.Info("Got deletion timestamp", "obj", o)
6864
return r.handleFinalizer(ctx, o)
6965
}
@@ -124,25 +120,16 @@ func (r *VaultConnectionReconciler) Reconcile(ctx context.Context, req ctrl.Requ
124120
return ctrl.Result{}, nil
125121
}
126122

127-
func (r *VaultConnectionReconciler) addFinalizer(ctx context.Context, o *secretsv1beta1.VaultConnection) error {
128-
if !controllerutil.ContainsFinalizer(o, vaultConnectionFinalizer) {
129-
controllerutil.AddFinalizer(o, vaultConnectionFinalizer)
130-
if err := r.Client.Update(ctx, o); err != nil {
131-
return err
132-
}
133-
}
134-
135-
return nil
136-
}
137-
138123
func (r *VaultConnectionReconciler) updateStatus(ctx context.Context, o *secretsv1beta1.VaultConnection) error {
139124
logger := log.FromContext(ctx)
140125
metrics.SetResourceStatus("vaultconnection", o, o.Status.Valid)
141126
if err := r.Status().Update(ctx, o); err != nil {
142127
logger.Error(err, "Failed to update the resource's status")
143128
return err
144129
}
145-
return nil
130+
131+
_, err := maybeAddFinalizer(ctx, r.Client, o, vaultConnectionFinalizer)
132+
return err
146133
}
147134

148135
func (r *VaultConnectionReconciler) handleFinalizer(ctx context.Context, o *secretsv1beta1.VaultConnection) (ctrl.Result, error) {

0 commit comments

Comments
 (0)