Repository: crossplane-contrib/provider-aws (go)

Issue:
database.RDSInstance does not update tags

### What happened?

`database.RDSInstance` seems to not trigger an update, when only the `spec.forProvider.tags` parameter is updated. (ignored in patch diff and no extra logic)
Only through changing another field, we can trigger an update, which then also changes a tag in AWS.

https://github.com/crossplane-contrib/provider-aws/blob/4124fb7ccb72938dcfe5ada372d910f81884a270/pkg/clients/database/rds.go#L722

Also the usage of  `RemoveTagsFromResource()`  seems to be missing.  Only `AddTagsToResource()` is called in `Update()`

https://github.com/crossplane-contrib/provider-aws/blob/4124fb7ccb72938dcfe5ada372d910f81884a270/pkg/controller/database/rdsinstance/rdsinstance.go#L281

I will try to provide a PR.

### How can we reproduce it?

Create `RDSInstance` and only change tags value afterwards. 

### What environment did it happen in?
Crossplane version: 1.14.3
provider-aws: v0.45.2 + master



Pull request:
fix(database): tag-only-change triggers update + add RemoveTag call
### Description of your changes

for `database.RDSInstance`:

- fix `isUpToDate()` logic to trigger update on tag-only-change (used cache scheme/solution from `rds.DBInstance`)
- add `RemoveTagsFromResource()` from AWS API to `update()` (removing tags was apparently not possible before...)
- adapt unit-tests

Fixes #1962

I have:

- [x] Read and followed Crossplane's [contribution process].
- [x] Run `make reviewable test` to ensure this PR is ready for review.

### How has this code been tested
manually (update tag-only, update other db parameter)


Patch:
diff --git a/pkg/clients/database/fake/fake.go b/pkg/clients/database/fake/fake.go
index d11b89457..d5094ef97 100644
--- a/pkg/clients/database/fake/fake.go
+++ b/pkg/clients/database/fake/fake.go
@@ -32,6 +32,7 @@ type MockRDSClient struct {
 	MockModify             func(context.Context, *rds.ModifyDBInstanceInput, []func(*rds.Options)) (*rds.ModifyDBInstanceOutput, error)
 	MockDelete             func(context.Context, *rds.DeleteDBInstanceInput, []func(*rds.Options)) (*rds.DeleteDBInstanceOutput, error)
 	MockAddTags            func(context.Context, *rds.AddTagsToResourceInput, []func(*rds.Options)) (*rds.AddTagsToResourceOutput, error)
+	MockRemoveTags         func(context.Context, *rds.RemoveTagsFromResourceInput, []func(*rds.Options)) (*rds.RemoveTagsFromResourceOutput, error)
 }
 
 // DescribeDBInstances finds RDS Instance by name
@@ -73,3 +74,8 @@ func (m *MockRDSClient) DeleteDBInstance(ctx context.Context, i *rds.DeleteDBIns
 func (m *MockRDSClient) AddTagsToResource(ctx context.Context, i *rds.AddTagsToResourceInput, opts ...func(*rds.Options)) (*rds.AddTagsToResourceOutput, error) {
 	return m.MockAddTags(ctx, i, opts)
 }
+
+// RemoveTagsFromResource removes tags from RDS Instance.
+func (m *MockRDSClient) RemoveTagsFromResource(ctx context.Context, i *rds.RemoveTagsFromResourceInput, opts ...func(*rds.Options)) (*rds.RemoveTagsFromResourceOutput, error) {
+	return m.MockRemoveTags(ctx, i, opts)
+}
diff --git a/pkg/clients/database/rds.go b/pkg/clients/database/rds.go
index b1749c99b..45fecd369 100644
--- a/pkg/clients/database/rds.go
+++ b/pkg/clients/database/rds.go
@@ -19,7 +19,7 @@ package rds
 import (
 	"context"
 	"encoding/json"
-	"slices"
+	"fmt"
 	"strconv"
 	"strings"
 	"time"
@@ -59,6 +59,7 @@ type Client interface {
 	ModifyDBInstance(context.Context, *rds.ModifyDBInstanceInput, ...func(*rds.Options)) (*rds.ModifyDBInstanceOutput, error)
 	DeleteDBInstance(context.Context, *rds.DeleteDBInstanceInput, ...func(*rds.Options)) (*rds.DeleteDBInstanceOutput, error)
 	AddTagsToResource(context.Context, *rds.AddTagsToResourceInput, ...func(*rds.Options)) (*rds.AddTagsToResourceOutput, error)
+	RemoveTagsFromResource(context.Context, *rds.RemoveTagsFromResourceInput, ...func(*rds.Options)) (*rds.RemoveTagsFromResourceOutput, error)
 }
 
 // NewClient creates new RDS RDSClient with provided AWS Configurations/Credentials
@@ -320,18 +321,11 @@ func GenerateRestoreRDSInstanceToPointInTimeInput(name string, p *v1beta1.RDSIns
 // CreatePatch creates a *v1beta1.RDSInstanceParameters that has only the changed
 // values between the target *v1beta1.RDSInstanceParameters and the current
 // *rds.DBInstance
-func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (*v1beta1.RDSInstanceParameters, error) { //nolint:gocyclo
+func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (*v1beta1.RDSInstanceParameters, error) {
 	target := spec.DeepCopy()
 	currentParams := &v1beta1.RDSInstanceParameters{}
 	LateInitialize(currentParams, in)
 
-	for _, t := range in.TagList {
-		currentParams.Tags = append(currentParams.Tags, v1beta1.Tag{
-			Key:   ptr.Deref(t.Key, ""),
-			Value: ptr.Deref(t.Value, ""),
-		})
-	}
-
 	// AvailabilityZone parameters is not allowed for MultiAZ deployments.
 	// So set this to nil if that is the case to avoid unnecessary diffs.
 	if ptr.Deref(target.MultiAZ, false) {
@@ -367,9 +361,6 @@ func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (
 		}
 	}
 
-	slices.SortFunc(currentParams.Tags, compareTags)
-	slices.SortFunc(target.Tags, compareTags)
-
 	jsonPatch, err := jsonpatch.CreateJSONPatch(currentParams, target)
 	if err != nil {
 		return nil, err
@@ -381,10 +372,6 @@ func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (
 	return patch, nil
 }
 
-func compareTags(a, b v1beta1.Tag) int {
-	return strings.Compare(a.Key, b.Key)
-}
-
 // GenerateModifyDBInstanceInput from RDSInstanceSpec
 func GenerateModifyDBInstanceInput(name string, p *v1beta1.RDSInstanceParameters, db *rdstypes.DBInstance) *rds.ModifyDBInstanceInput {
 	// NOTE(muvaf): MasterUserPassword is not used here. So, password is set once
@@ -707,14 +694,18 @@ func lateInitializeOptionGroupName(inOptionGroupName *string, members []rdstypes
 }
 
 // IsUpToDate checks whether there is a change in any of the modifiable fields.
-func IsUpToDate(ctx context.Context, kube client.Client, r *v1beta1.RDSInstance, db rdstypes.DBInstance) (bool, string, error) {
+func IsUpToDate(ctx context.Context, kube client.Client, r *v1beta1.RDSInstance, db rdstypes.DBInstance) (bool, string, []rdstypes.Tag, []string, error) { //nolint:gocyclo
+
+	addTags := []rdstypes.Tag{}
+	removeTags := []string{}
+
 	_, pwdChanged, err := GetPassword(ctx, kube, r.Spec.ForProvider.MasterPasswordSecretRef, r.Spec.WriteConnectionSecretToReference)
 	if err != nil {
-		return false, "", err
+		return false, "", addTags, removeTags, err
 	}
 	patch, err := CreatePatch(&db, &r.Spec.ForProvider)
 	if err != nil {
-		return false, "", err
+		return false, "", addTags, removeTags, err
 	}
 	diff := cmp.Diff(&v1beta1.RDSInstanceParameters{}, patch, cmpopts.EquateEmpty(),
 		cmpopts.IgnoreTypes(&xpv1.Reference{}, &xpv1.Selector{}, []xpv1.Reference{}),
@@ -735,6 +726,9 @@ func IsUpToDate(ctx context.Context, kube client.Client, r *v1beta1.RDSInstance,
 		cmpopts.IgnoreFields(v1beta1.RDSInstanceParameters{}, "CloudwatchLogsExportConfiguration"),
 	)
 
+	addTags, removeTags = DiffTags(r.Spec.ForProvider.Tags, db.TagList)
+	tagsChanged := len(addTags) != 0 || len(removeTags) != 0
+
 	engineVersionChanged := !isEngineVersionUpToDate(r, db)
 
 	optionGroupChanged := !isOptionGroupUpToDate(r, db)
@@ -746,13 +740,17 @@ func IsUpToDate(ctx context.Context, kube client.Client, r *v1beta1.RDSInstance,
 		cloudwatchLogsExportChanged = !areSameElements(r.Spec.ForProvider.EnableCloudwatchLogsExports, db.EnabledCloudwatchLogsExports)
 	}
 
-	if diff == "" && !pwdChanged && !engineVersionChanged && !optionGroupChanged && !cloudwatchLogsExportChanged {
-		return true, "", nil
+	if diff == "" && !pwdChanged && !engineVersionChanged && !optionGroupChanged && !cloudwatchLogsExportChanged && !tagsChanged {
+		return true, "", addTags, removeTags, nil
 	}
 
 	diff = "Found observed difference in rds\n" + diff
 
-	return false, diff, nil
+	if tagsChanged {
+		diff += fmt.Sprintf("\nadd %d tag(s) and remove %d tag(s)", len(addTags), len(removeTags))
+	}
+
+	return false, diff, addTags, removeTags, nil
 }
 
 func isEngineVersionUpToDate(cr *v1beta1.RDSInstance, db rdstypes.DBInstance) bool {
@@ -891,3 +889,41 @@ func areSameElements(a1, a2 []string) bool {
 
 	return true
 }
+
+// DiffTags between spec and current
+func DiffTags(spec []v1beta1.Tag, current []rdstypes.Tag) (addTags []rdstypes.Tag, removeTags []string) {
+	currentMap := make(map[string]string, len(current))
+	for _, t := range current {
+		currentMap[pointer.StringValue(t.Key)] = pointer.StringValue(t.Value)
+	}
+
+	specMap := make(map[string]string, len(spec))
+	for _, t := range spec {
+		key := t.Key
+		val := t.Value
+		specMap[key] = t.Value
+
+		if currentVal, exists := currentMap[key]; exists {
+			if currentVal != val {
+				addTags = append(addTags, rdstypes.Tag{
+					Key:   pointer.ToOrNilIfZeroValue(key),
+					Value: pointer.ToOrNilIfZeroValue(val),
+				})
+			}
+		} else {
+			addTags = append(addTags, rdstypes.Tag{
+				Key:   pointer.ToOrNilIfZeroValue(key),
+				Value: pointer.ToOrNilIfZeroValue(val),
+			})
+		}
+	}
+
+	for _, t := range current {
+		key := pointer.StringValue(t.Key)
+		if _, exists := specMap[key]; !exists {
+			removeTags = append(removeTags, key)
+		}
+	}
+
+	return addTags, removeTags
+}
diff --git a/pkg/controller/database/rdsinstance/rdsinstance.go b/pkg/controller/database/rdsinstance/rdsinstance.go
index 9b7dfc9ed..82777131f 100644
--- a/pkg/controller/database/rdsinstance/rdsinstance.go
+++ b/pkg/controller/database/rdsinstance/rdsinstance.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/aws/aws-sdk-go-v2/aws"
 	awsrds "github.com/aws/aws-sdk-go-v2/service/rds"
-	awsrdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
+	rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types"
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	"github.com/crossplane/crossplane-runtime/pkg/connection"
 	"github.com/crossplane/crossplane-runtime/pkg/controller"
@@ -55,6 +55,7 @@ const (
 	errUnknownRestoreSource               = "unknown RDS restore source"
 	errModifyFailed                       = "cannot modify RDS instance"
 	errAddTagsFailed                      = "cannot add tags to RDS instance"
+	errRemoveTagsFailed                   = "cannot remove tags from  RDS instance"
 	errDeleteFailed                       = "cannot delete RDS instance"
 	errDescribeFailed                     = "cannot describe RDS instance"
 	errPatchCreationFailed                = "cannot create a patch object"
@@ -112,12 +113,19 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
 	if err != nil {
 		return nil, err
 	}
-	return &external{c.newClientFn(cfg), c.kube}, nil
+	return &external{c.newClientFn(cfg), c.kube, Cache{}}, nil
+}
+
+type Cache struct {
+	AddTags    []rdstypes.Tag
+	RemoveTags []string
 }
 
 type external struct {
 	client rds.Client
 	kube   client.Client
+
+	cache Cache
 }
 
 func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) {
@@ -152,7 +160,12 @@ func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
 	default:
 		cr.Status.SetConditions(xpv1.Unavailable())
 	}
-	upToDate, diff, err := rds.IsUpToDate(ctx, e.kube, cr, instance)
+
+	var upToDate bool
+	var diff string
+
+	upToDate, diff, e.cache.AddTags, e.cache.RemoveTags, err = rds.IsUpToDate(ctx, e.kube, cr, instance)
+
 	if err != nil {
 		return managed.ExternalObservation{}, errorutils.Wrap(err, errUpToDateFailed)
 	}
@@ -273,14 +286,22 @@ func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext
 	if _, err = e.client.ModifyDBInstance(ctx, modify); err != nil {
 		return managed.ExternalUpdate{}, errorutils.Wrap(err, errModifyFailed)
 	}
-	if len(patch.Tags) > 0 {
-		tags := make([]awsrdstypes.Tag, len(patch.Tags))
-		for i, t := range patch.Tags {
-			tags[i] = awsrdstypes.Tag{Key: aws.String(t.Key), Value: aws.String(t.Value)}
+
+	// Update tags if necessary
+	if len(e.cache.RemoveTags) > 0 {
+		_, err := e.client.RemoveTagsFromResource(ctx, &awsrds.RemoveTagsFromResourceInput{
+			ResourceName: aws.String(cr.Status.AtProvider.DBInstanceArn),
+			TagKeys:      e.cache.RemoveTags,
+		})
+		if err != nil {
+			return managed.ExternalUpdate{}, errorutils.Wrap(err, errRemoveTagsFailed)
 		}
-		_, err = e.client.AddTagsToResource(ctx, &awsrds.AddTagsToResourceInput{
+	}
+	// remove before add for case where we just simply update a tag value
+	if len(e.cache.AddTags) > 0 {
+		_, err := e.client.AddTagsToResource(ctx, &awsrds.AddTagsToResourceInput{
 			ResourceName: aws.String(cr.Status.AtProvider.DBInstanceArn),
-			Tags:         tags,
+			Tags:         e.cache.AddTags,
 		})
 		if err != nil {
 			return managed.ExternalUpdate{}, errorutils.Wrap(err, errAddTagsFailed)


Test patch:
diff --git a/pkg/clients/database/rds_test.go b/pkg/clients/database/rds_test.go
index 52d0385a2..dd23a83d1 100644
--- a/pkg/clients/database/rds_test.go
+++ b/pkg/clients/database/rds_test.go
@@ -148,75 +148,6 @@ func TestCreatePatch(t *testing.T) {
 				},
 			},
 		},
-		"DifferentTags": {
-			args: args{
-				db: &rdstypes.DBInstance{
-					TagList: []rdstypes.Tag{
-						{Key: ptr.To("tag1")},
-						{Key: ptr.To("tag2")},
-						{Key: ptr.To("tag3")},
-					},
-				},
-				p: &v1beta1.RDSInstanceParameters{
-					Tags: []v1beta1.Tag{
-						{Key: "tag1"},
-						{Key: "tag5"},
-						{Key: "tag6"},
-					},
-				},
-			},
-			want: want{
-				patch: &v1beta1.RDSInstanceParameters{
-					Tags: []v1beta1.Tag{
-						{Key: "tag1"},
-						{Key: "tag5"},
-						{Key: "tag6"},
-					},
-				},
-			},
-		},
-		"SameTags": {
-			args: args{
-				db: &rdstypes.DBInstance{
-					TagList: []rdstypes.Tag{
-						{Key: ptr.To("tag1")},
-						{Key: ptr.To("tag2")},
-						{Key: ptr.To("tag3")},
-					},
-				},
-				p: &v1beta1.RDSInstanceParameters{
-					Tags: []v1beta1.Tag{
-						{Key: "tag1"},
-						{Key: "tag2"},
-						{Key: "tag3"},
-					},
-				},
-			},
-			want: want{
-				patch: &v1beta1.RDSInstanceParameters{},
-			},
-		},
-		"SameTagsDifferentOrder": {
-			args: args{
-				db: &rdstypes.DBInstance{
-					TagList: []rdstypes.Tag{
-						{Key: ptr.To("tag1"), Value: ptr.To("val")},
-						{Key: ptr.To("tag2"), Value: ptr.To("val")},
-						{Key: ptr.To("tag3"), Value: ptr.To("val")},
-					},
-				},
-				p: &v1beta1.RDSInstanceParameters{
-					Tags: []v1beta1.Tag{
-						{Key: "tag3", Value: "val"},
-						{Key: "tag2", Value: "val"},
-						{Key: "tag1", Value: "val"},
-					},
-				},
-			},
-			want: want{
-				patch: &v1beta1.RDSInstanceParameters{},
-			},
-		},
 		"IgnoreDifferentAvailabilityZoneForMultiAZ": {
 			args: args{
 				db: &rdstypes.DBInstance{
@@ -527,12 +458,81 @@ func TestIsUpToDate(t *testing.T) {
 			},
 			want: true,
 		},
+		"SameTags": {
+			args: args{
+				db: rdstypes.DBInstance{
+					TagList: []rdstypes.Tag{
+						{Key: ptr.To("tag1")},
+						{Key: ptr.To("tag2")},
+						{Key: ptr.To("tag3")},
+					},
+				},
+				r: v1beta1.RDSInstance{
+					Spec: v1beta1.RDSInstanceSpec{
+						ForProvider: v1beta1.RDSInstanceParameters{
+							Tags: []v1beta1.Tag{
+								{Key: "tag1"},
+								{Key: "tag2"},
+								{Key: "tag3"},
+							},
+						},
+					},
+				},
+			},
+			want: true,
+		},
+		"SameTagsDifferentOrder": {
+			args: args{
+				db: rdstypes.DBInstance{
+					TagList: []rdstypes.Tag{
+						{Key: ptr.To("tag1"), Value: ptr.To("val")},
+						{Key: ptr.To("tag2"), Value: ptr.To("val")},
+						{Key: ptr.To("tag3"), Value: ptr.To("val")},
+					},
+				},
+				r: v1beta1.RDSInstance{
+					Spec: v1beta1.RDSInstanceSpec{
+						ForProvider: v1beta1.RDSInstanceParameters{
+							Tags: []v1beta1.Tag{
+								{Key: "tag3", Value: "val"},
+								{Key: "tag2", Value: "val"},
+								{Key: "tag1", Value: "val"},
+							},
+						},
+					},
+				},
+			},
+			want: true,
+		},
+		"DifferentTags": {
+			args: args{
+				db: rdstypes.DBInstance{
+					TagList: []rdstypes.Tag{
+						{Key: ptr.To("tag1")},
+						{Key: ptr.To("tag2")},
+						{Key: ptr.To("tag3")},
+					},
+				},
+				r: v1beta1.RDSInstance{
+					Spec: v1beta1.RDSInstanceSpec{
+						ForProvider: v1beta1.RDSInstanceParameters{
+							Tags: []v1beta1.Tag{
+								{Key: "tag1"},
+								{Key: "tag5"},
+								{Key: "tag6"},
+							},
+						},
+					},
+				},
+			},
+			want: false,
+		},
 	}
 
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
 			ctx := context.Background()
-			got, _, _ := IsUpToDate(ctx, tc.args.kube, &tc.args.r, tc.args.db)
+			got, _, _, _, _ := IsUpToDate(ctx, tc.args.kube, &tc.args.r, tc.args.db)
 			if diff := cmp.Diff(tc.want, got); diff != "" {
 				t.Errorf("r: -want, +got:\n%s", diff)
 			}
diff --git a/pkg/controller/database/rdsinstance/rdsinstance_test.go b/pkg/controller/database/rdsinstance/rdsinstance_test.go
index bae6aeac2..fce4b36a9 100644
--- a/pkg/controller/database/rdsinstance/rdsinstance_test.go
+++ b/pkg/controller/database/rdsinstance/rdsinstance_test.go
@@ -37,6 +37,7 @@ import (
 	rds "github.com/crossplane-contrib/provider-aws/pkg/clients/database"
 	"github.com/crossplane-contrib/provider-aws/pkg/clients/database/fake"
 	errorutils "github.com/crossplane-contrib/provider-aws/pkg/utils/errors"
+	"github.com/crossplane-contrib/provider-aws/pkg/utils/pointer"
 )
 
 const (
@@ -97,9 +98,10 @@ func FromTimePtr(t *time.Time) *metav1.Time {
 }
 
 type args struct {
-	rds  rds.Client
-	kube client.Client
-	cr   *v1beta1.RDSInstance
+	rds   rds.Client
+	kube  client.Client
+	cache Cache
+	cr    *v1beta1.RDSInstance
 }
 
 type rdsModifier func(*v1beta1.RDSInstance)
@@ -192,6 +194,17 @@ func instance(m ...rdsModifier) *v1beta1.RDSInstance {
 	return cr
 }
 
+func cache(addTagsMap map[string]string, removeTagsMap map[string]string) Cache {
+	c := Cache{}
+	for k, v := range addTagsMap {
+		c.AddTags = append(c.AddTags, awsrdstypes.Tag{Key: pointer.ToOrNilIfZeroValue(k), Value: pointer.ToOrNilIfZeroValue(v)})
+	}
+	for k := range removeTagsMap {
+		c.RemoveTags = append(c.RemoveTags, k)
+	}
+	return c
+}
+
 var _ managed.ExternalClient = &external{}
 var _ managed.ExternalConnecter = &connector{}
 
@@ -841,18 +854,42 @@ func TestUpdate(t *testing.T) {
 						return nil, errBoom
 					},
 				},
-				cr: instance(withTags(map[string]string{"foo": "bar"})),
+				cr:    instance(withTags(map[string]string{"foo": "bar"})),
+				cache: cache(map[string]string{"foo": "bar"}, map[string]string{}),
 			},
 			want: want{
 				cr:  instance(withTags(map[string]string{"foo": "bar"})),
 				err: errorutils.Wrap(errBoom, errAddTagsFailed),
 			},
 		},
+		"FailedRemoveTags": {
+			args: args{
+				rds: &fake.MockRDSClient{
+					MockModify: func(ctx context.Context, input *awsrds.ModifyDBInstanceInput, opts []func(*awsrds.Options)) (*awsrds.ModifyDBInstanceOutput, error) {
+						return &awsrds.ModifyDBInstanceOutput{}, nil
+					},
+					MockDescribe: func(ctx context.Context, input *awsrds.DescribeDBInstancesInput, opts []func(*awsrds.Options)) (*awsrds.DescribeDBInstancesOutput, error) {
+						return &awsrds.DescribeDBInstancesOutput{
+							DBInstances: []awsrdstypes.DBInstance{{}},
+						}, nil
+					},
+					MockRemoveTags: func(ctx context.Context, input *awsrds.RemoveTagsFromResourceInput, opts []func(*awsrds.Options)) (*awsrds.RemoveTagsFromResourceOutput, error) {
+						return nil, errBoom
+					},
+				},
+				cr:    instance(withTags(map[string]string{"foo": "bar"})),
+				cache: cache(map[string]string{}, map[string]string{"foo": "bar"}),
+			},
+			want: want{
+				cr:  instance(withTags(map[string]string{"foo": "bar"})),
+				err: errorutils.Wrap(errBoom, errRemoveTagsFailed),
+			},
+		},
 	}
 
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
-			e := &external{kube: tc.kube, client: tc.rds}
+			e := &external{kube: tc.kube, client: tc.rds, cache: tc.cache}
 			u, err := e.Update(context.Background(), tc.args.cr)
 
 			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {