Skip to content

Commit 978056a

Browse files
committed
feat(rds): add tag ignore prefix support
- Adds TagIgnorePrefixes to DBInstance spec - Filters out tags with ignored prefixes from diff logic - Adds unit test coverage - Builds on top of read replica support Signed-off-by: Riyad Ilyasov <ilyasov.2003@gmail.com> Signed-off-by: Riyad Ilyasov <riyad.ilyasov@swisscom.com>
1 parent 2bd3cf0 commit 978056a

File tree

6 files changed

+243
-1
lines changed

6 files changed

+243
-1
lines changed

apis/rds/v1alpha1/custom_types.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,14 @@ type CustomDBInstanceParameters struct {
807807
// deleted.
808808
// +optional
809809
DeleteAutomatedBackups *bool `json:"deleteAutomatedBackups,omitempty"`
810+
811+
// TagsIgnore contains rules that tell the reconciler to pretend matching
812+
// tags don't exist during diff/updates. A rule key supports either exact
813+
// match (e.g. "c7n:policy") or a simple prefix glob using a trailing *
814+
// (e.g. "c7n:*" or "prefix*"). In all cases tags starting with the
815+
// prefix "aws:" are always ignored (implicit rule "aws:*").
816+
// +optional
817+
TagsIgnore []TagIgnoreRule `json:"tagsIgnore,omitempty"`
810818
}
811819

812820
// CustomDBInstanceObservation includes the custom status fields of DBInstance.
@@ -816,6 +824,19 @@ type CustomDBInstanceObservation struct {
816824

817825
// The database role may be Standalone, Primary or Replica.
818826
DatabaseRole *string `json:"databaseRole,omitempty"`
827+
828+
// ObservedTags exposes the full, unfiltered set of external tags returned
829+
// by AWS for observability. These are never used for diffing directly.
830+
// +optional
831+
ObservedTags []*Tag `json:"observedTags,omitempty"`
832+
}
833+
834+
// TagIgnoreRule defines a single rule for ignoring a tag during diffing.
835+
// The Key may be an exact tag key or a prefix pattern that ends with *.
836+
type TagIgnoreRule struct {
837+
// Key of the ignore rule. Supports exact key match or prefix* glob.
838+
// +kubebuilder:validation:Required
839+
Key string `json:"key"`
819840
}
820841

821842
// CustomDBInstanceRoleAssociationParameters are custom parameters for the DBInstanceRoleAssociation

apis/rds/v1alpha1/zz_generated.deepcopy.go

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/crds/rds.aws.crossplane.io_dbinstances.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,6 +1980,26 @@ spec:
19801980
type: string
19811981
type: object
19821982
type: array
1983+
tagsIgnore:
1984+
description: |-
1985+
TagsIgnore contains rules that tell the reconciler to pretend matching
1986+
tags don't exist during diff/updates. A rule key supports either exact
1987+
match (e.g. "c7n:policy") or a simple prefix glob using a trailing *
1988+
(e.g. "c7n:*" or "prefix*"). In all cases tags starting with the
1989+
prefix "aws:" are always ignored (implicit rule "aws:*").
1990+
items:
1991+
description: |-
1992+
TagIgnoreRule defines a single rule for ignoring a tag during diffing.
1993+
The Key may be an exact tag key or a prefix pattern that ends with *.
1994+
properties:
1995+
key:
1996+
description: Key of the ignore rule. Supports exact key
1997+
match or prefix* glob.
1998+
type: string
1999+
required:
2000+
- key
2001+
type: object
2002+
type: array
19832003
tdeCredentialARN:
19842004
description: |-
19852005
The ARN from the key store with which to associate the instance for TDE encryption.
@@ -2595,6 +2615,18 @@ spec:
25952615
secretStatus:
25962616
type: string
25972617
type: object
2618+
observedTags:
2619+
description: |-
2620+
ObservedTags exposes the full, unfiltered set of external tags returned
2621+
by AWS for observability. These are never used for diffing directly.
2622+
items:
2623+
properties:
2624+
key:
2625+
type: string
2626+
value:
2627+
type: string
2628+
type: object
2629+
type: array
25982630
optionGroupMemberships:
25992631
description: The list of option group memberships for this DB
26002632
instance.

pkg/controller/rds/dbinstance/setup.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,9 +614,29 @@ func (s *shared) isUpToDate(ctx context.Context, cr *svcapitypes.DBInstance, out
614614
cmpopts.IgnoreFields(svcapitypes.CustomDBInstanceParameters{},
615615
"SourceDBClusterID", "SourceDBClusterIDRef", "SourceDBClusterIDSelector",
616616
"SourceDBInstanceID", "SourceDBInstanceIDRef", "SourceDBInstanceIDSelector"),
617+
cmpopts.IgnoreFields(svcapitypes.CustomDBInstanceParameters{}, "TagsIgnore"),
617618
)
618619

619-
s.cache.addTags, s.cache.removeTags = utils.DiffTags(cr.Spec.ForProvider.Tags, db.TagList)
620+
// Build ignore rules: implicit aws:* + user supplied rules
621+
ignore := []string{"aws:*"}
622+
for _, r := range cr.Spec.ForProvider.TagsIgnore {
623+
ignore = append(ignore, r.Key)
624+
}
625+
// Reset observed tags slice to avoid accumulation across reconciles
626+
cr.Status.AtProvider.ObservedTags = nil
627+
var observedTags []*svcsdk.Tag
628+
if db.TagList != nil {
629+
for _, tag := range db.TagList { // index discarded with _
630+
// Capture all tags for observability
631+
cr.Status.AtProvider.ObservedTags = append(cr.Status.AtProvider.ObservedTags, &svcapitypes.Tag{Key: tag.Key, Value: tag.Value})
632+
// Filter only for diff purposes
633+
if utils.ShouldIgnore(pointer.StringValue(tag.Key), ignore) {
634+
continue
635+
}
636+
observedTags = append(observedTags, &svcsdk.Tag{Key: tag.Key, Value: tag.Value})
637+
}
638+
}
639+
s.cache.addTags, s.cache.removeTags = utils.DiffTags(cr.Spec.ForProvider.Tags, observedTags)
620640
tagsChanged := len(s.cache.addTags) != 0 || len(s.cache.removeTags) != 0
621641

622642
if diff == "" && !maintenanceWindowChanged && !backupWindowChanged && !iopsChanged && !storageThroughputChanged && !versionChanged && !vpcSGsChanged && !dbParameterGroupChanged && !optionGroupChanged && !tagsChanged {

pkg/controller/rds/dbinstance/setup_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,116 @@ func TestIsUpToDate(t *testing.T) {
209209
},
210210
},
211211
},
212+
"Ignores Tags with TagsIgnore prefix*": {
213+
args: args{
214+
cr: &svcapitypes.DBInstance{
215+
Spec: svcapitypes.DBInstanceSpec{
216+
ForProvider: svcapitypes.DBInstanceParameters{
217+
CustomDBInstanceParameters: svcapitypes.CustomDBInstanceParameters{
218+
TagsIgnore: []svcapitypes.TagIgnoreRule{{Key: "aws:*"}, {Key: "c7n:*"}},
219+
},
220+
Tags: []*svcapitypes.Tag{
221+
{Key: aws.String("env"), Value: aws.String("prod")},
222+
},
223+
DeletionProtection: aws.Bool(true),
224+
},
225+
},
226+
},
227+
out: &svcsdk.DescribeDBInstancesOutput{
228+
DBInstances: []*svcsdk.DBInstance{
229+
{
230+
DeletionProtection: aws.Bool(true),
231+
TagList: []*svcsdk.Tag{
232+
{Key: aws.String("aws:createdBy"), Value: aws.String("terraform")},
233+
{Key: aws.String("c7n:policy"), Value: aws.String("auto")},
234+
{Key: aws.String("env"), Value: aws.String("prod")},
235+
},
236+
},
237+
},
238+
},
239+
kube: test.NewMockClient(),
240+
},
241+
want: want{
242+
upToDate: true,
243+
err: nil,
244+
statusAtProvider: &svcapitypes.CustomDBInstanceObservation{
245+
DatabaseRole: aws.String(databaseRoleStandalone),
246+
},
247+
},
248+
},
249+
"Ignores Tags with TagsIgnore exact": {
250+
args: args{
251+
cr: &svcapitypes.DBInstance{
252+
Spec: svcapitypes.DBInstanceSpec{
253+
ForProvider: svcapitypes.DBInstanceParameters{
254+
CustomDBInstanceParameters: svcapitypes.CustomDBInstanceParameters{
255+
TagsIgnore: []svcapitypes.TagIgnoreRule{{Key: "aws:*"}, {Key: "c7n:policy"}},
256+
},
257+
Tags: []*svcapitypes.Tag{
258+
{Key: aws.String("env"), Value: aws.String("prod")},
259+
},
260+
DeletionProtection: aws.Bool(true),
261+
},
262+
},
263+
},
264+
out: &svcsdk.DescribeDBInstancesOutput{
265+
DBInstances: []*svcsdk.DBInstance{
266+
{
267+
DeletionProtection: aws.Bool(true),
268+
TagList: []*svcsdk.Tag{
269+
{Key: aws.String("aws:createdBy"), Value: aws.String("terraform")},
270+
{Key: aws.String("c7n:policy"), Value: aws.String("auto")},
271+
{Key: aws.String("c7n:other"), Value: aws.String("x")},
272+
{Key: aws.String("env"), Value: aws.String("prod")},
273+
},
274+
},
275+
},
276+
},
277+
kube: test.NewMockClient(),
278+
},
279+
want: want{
280+
upToDate: false, // c7n:other should be removed since not ignored and not in spec
281+
err: nil,
282+
statusAtProvider: &svcapitypes.CustomDBInstanceObservation{
283+
DatabaseRole: aws.String(databaseRoleStandalone),
284+
},
285+
},
286+
},
287+
"DoesNotIgnoreAllWithStarOnlyRule": {
288+
args: args{
289+
cr: &svcapitypes.DBInstance{
290+
Spec: svcapitypes.DBInstanceSpec{
291+
ForProvider: svcapitypes.DBInstanceParameters{
292+
CustomDBInstanceParameters: svcapitypes.CustomDBInstanceParameters{
293+
// User attempts to ignore all tags with a single "*" rule; guard should prevent this.
294+
TagsIgnore: []svcapitypes.TagIgnoreRule{{Key: "*"}},
295+
},
296+
// Desired spec has no tags.
297+
Tags: []*svcapitypes.Tag{},
298+
DeletionProtection: aws.Bool(true),
299+
},
300+
},
301+
},
302+
out: &svcsdk.DescribeDBInstancesOutput{
303+
DBInstances: []*svcsdk.DBInstance{
304+
{
305+
DeletionProtection: aws.Bool(true),
306+
TagList: []*svcsdk.Tag{
307+
{Key: aws.String("env"), Value: aws.String("prod")}, // Should not be ignored; will cause diff
308+
},
309+
},
310+
},
311+
},
312+
kube: test.NewMockClient(),
313+
},
314+
want: want{
315+
upToDate: false, // env tag should be scheduled for removal
316+
err: nil,
317+
statusAtProvider: &svcapitypes.CustomDBInstanceObservation{
318+
DatabaseRole: aws.String(databaseRoleStandalone),
319+
},
320+
},
321+
},
212322
}
213323

214324
for name, tc := range cases {

pkg/controller/rds/utils/tags.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package utils
1919
import (
2020
"context"
2121
"sort"
22+
"strings"
2223

2324
svcsdk "github.com/aws/aws-sdk-go/service/rds"
2425
"github.com/aws/aws-sdk-go/service/rds/rdsiface"
@@ -35,6 +36,33 @@ const (
3536
errCreateTags = "cannot create tags"
3637
)
3738

39+
// ShouldIgnore returns true if key matches any supplied rule. A rule may be:
40+
// - exact key match (e.g. "c7n:policy")
41+
// - prefix* glob where * is only allowed as the last character (e.g. "c7n:*" or "prefix*")
42+
//
43+
// No other wildcard forms are supported.
44+
func ShouldIgnore(key string, rules []string) bool { // intentionally simple, no regex
45+
for _, r := range rules {
46+
if r == "" { // skip empty
47+
continue
48+
}
49+
if strings.HasSuffix(r, "*") {
50+
prefix := strings.TrimSuffix(r, "*")
51+
if prefix == "" { // guard against blanket "*" wildcard
52+
continue
53+
}
54+
if strings.HasPrefix(key, prefix) {
55+
return true
56+
}
57+
continue
58+
}
59+
if key == r { // exact
60+
return true
61+
}
62+
}
63+
return false
64+
}
65+
3866
// AreTagsUpToDate for spec and resourceName
3967
func AreTagsUpToDate(ctx context.Context, client rdsiface.RDSAPI, spec []*svcapitypes.Tag, resourceName *string) (bool, []*svcsdk.Tag, []*string, error) {
4068
current, err := ListTagsForResource(ctx, client, resourceName)

0 commit comments

Comments
 (0)