|
14 | 14 | package db_cluster
|
15 | 15 |
|
16 | 16 | import (
|
| 17 | + "context" |
17 | 18 | "errors"
|
18 | 19 | "fmt"
|
19 | 20 |
|
| 21 | + svcapitypes "github.com/aws-controllers-k8s/rds-controller/apis/v1alpha1" |
| 22 | + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" |
20 | 23 | ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue"
|
| 24 | + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" |
| 25 | + svcsdk "github.com/aws/aws-sdk-go/service/rds" |
21 | 26 | )
|
22 | 27 |
|
23 | 28 | // NOTE(jaypipes): The below list is derived from looking at the RDS control
|
@@ -145,3 +150,171 @@ func clusterDeleting(r *resource) bool {
|
145 | 150 | dbcs := *r.ko.Status.Status
|
146 | 151 | return dbcs == StatusDeleting
|
147 | 152 | }
|
| 153 | + |
| 154 | +// syncTags keeps the resource's tags in sync |
| 155 | +// |
| 156 | +// NOTE(jaypipes): RDS' Tagging APIs differ from other AWS APIs in the |
| 157 | +// following ways: |
| 158 | +// |
| 159 | +// 1. The names of the tagging API operations are different. Other APIs use the |
| 160 | +// Tagris `ListTagsForResource`, `TagResource` and `UntagResource` API |
| 161 | +// calls. RDS uses `ListTagsForResource`, `AddTagsToResource` and |
| 162 | +// `RemoveTagsFromResource`. |
| 163 | +// |
| 164 | +// 2. Even though the name of the `ListTagsForResource` API call is the same, |
| 165 | +// the structure of the input and the output are different from other APIs. |
| 166 | +// For the input, instead of a `ResourceArn` field, RDS names the field |
| 167 | +// `ResourceName`, but actually expects an ARN, not the cluster |
| 168 | +// name. This is the same for the `AddTagsToResource` and |
| 169 | +// `RemoveTagsFromResource` input shapes. For the output shape, the field is |
| 170 | +// called `TagList` instead of `Tags` but is otherwise the same struct with |
| 171 | +// a `Key` and `Value` member field. |
| 172 | +func (rm *resourceManager) syncTags( |
| 173 | + ctx context.Context, |
| 174 | + desired *resource, |
| 175 | + latest *resource, |
| 176 | +) (err error) { |
| 177 | + rlog := ackrtlog.FromContext(ctx) |
| 178 | + exit := rlog.Trace("rm.syncTags") |
| 179 | + defer func() { exit(err) }() |
| 180 | + |
| 181 | + arn := (*string)(latest.ko.Status.ACKResourceMetadata.ARN) |
| 182 | + |
| 183 | + toAdd, toDelete := computeTagsDelta( |
| 184 | + desired.ko.Spec.Tags, latest.ko.Spec.Tags, |
| 185 | + ) |
| 186 | + |
| 187 | + if len(toDelete) > 0 { |
| 188 | + rlog.Debug("removing tags from cluster", "tags", toDelete) |
| 189 | + _, err = rm.sdkapi.RemoveTagsFromResourceWithContext( |
| 190 | + ctx, |
| 191 | + &svcsdk.RemoveTagsFromResourceInput{ |
| 192 | + ResourceName: arn, |
| 193 | + TagKeys: toDelete, |
| 194 | + }, |
| 195 | + ) |
| 196 | + rm.metrics.RecordAPICall("UPDATE", "RemoveTagsFromResource", err) |
| 197 | + if err != nil { |
| 198 | + return err |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + // NOTE(jaypipes): According to the RDS API documentation, adding a tag |
| 203 | + // with a new value overwrites any existing tag with the same key. So, we |
| 204 | + // don't need to do anything to "update" a Tag. Simply including it in the |
| 205 | + // AddTagsToResource call is enough. |
| 206 | + if len(toAdd) > 0 { |
| 207 | + rlog.Debug("adding tags to cluster", "tags", toAdd) |
| 208 | + _, err = rm.sdkapi.AddTagsToResourceWithContext( |
| 209 | + ctx, |
| 210 | + &svcsdk.AddTagsToResourceInput{ |
| 211 | + ResourceName: arn, |
| 212 | + Tags: sdkTagsFromResourceTags(toAdd), |
| 213 | + }, |
| 214 | + ) |
| 215 | + rm.metrics.RecordAPICall("UPDATE", "AddTagsToResource", err) |
| 216 | + if err != nil { |
| 217 | + return err |
| 218 | + } |
| 219 | + } |
| 220 | + return nil |
| 221 | +} |
| 222 | + |
| 223 | +// getTags retrieves the resource's associated tags |
| 224 | +func (rm *resourceManager) getTags( |
| 225 | + ctx context.Context, |
| 226 | + resourceARN string, |
| 227 | +) ([]*svcapitypes.Tag, error) { |
| 228 | + resp, err := rm.sdkapi.ListTagsForResourceWithContext( |
| 229 | + ctx, |
| 230 | + &svcsdk.ListTagsForResourceInput{ |
| 231 | + ResourceName: &resourceARN, |
| 232 | + }, |
| 233 | + ) |
| 234 | + rm.metrics.RecordAPICall("GET", "ListTagsForResource", err) |
| 235 | + if err != nil { |
| 236 | + return nil, err |
| 237 | + } |
| 238 | + tags := make([]*svcapitypes.Tag, 0, len(resp.TagList)) |
| 239 | + for _, tag := range resp.TagList { |
| 240 | + tags = append(tags, &svcapitypes.Tag{ |
| 241 | + Key: tag.Key, |
| 242 | + Value: tag.Value, |
| 243 | + }) |
| 244 | + } |
| 245 | + return tags, nil |
| 246 | +} |
| 247 | + |
| 248 | +// compareTags adds a difference to the delta if the supplied resources have |
| 249 | +// different tag collections |
| 250 | +func compareTags( |
| 251 | + delta *ackcompare.Delta, |
| 252 | + a *resource, |
| 253 | + b *resource, |
| 254 | +) { |
| 255 | + if len(a.ko.Spec.Tags) != len(b.ko.Spec.Tags) { |
| 256 | + delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) |
| 257 | + } else if len(a.ko.Spec.Tags) > 0 { |
| 258 | + if !equalTags(a.ko.Spec.Tags, b.ko.Spec.Tags) { |
| 259 | + delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) |
| 260 | + } |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +// equalTags returns true if two Tag arrays are equal regardless of the order |
| 265 | +// of their elements. |
| 266 | +func equalTags( |
| 267 | + a []*svcapitypes.Tag, |
| 268 | + b []*svcapitypes.Tag, |
| 269 | +) bool { |
| 270 | + added, removed := computeTagsDelta(a, b) |
| 271 | + return len(added) == 0 && len(removed) == 0 |
| 272 | +} |
| 273 | + |
| 274 | +// computeTagsDelta compares two Tag arrays and returns the tags to add and the |
| 275 | +// tag keys to delete |
| 276 | +func computeTagsDelta( |
| 277 | + desired []*svcapitypes.Tag, |
| 278 | + latest []*svcapitypes.Tag, |
| 279 | +) (added []*svcapitypes.Tag, removed []*string) { |
| 280 | + toDelete := []*string{} |
| 281 | + toAdd := []*svcapitypes.Tag{} |
| 282 | + |
| 283 | + desiredTags := map[string]string{} |
| 284 | + for _, tag := range desired { |
| 285 | + desiredTags[*tag.Key] = *tag.Value |
| 286 | + } |
| 287 | + |
| 288 | + for _, tag := range desired { |
| 289 | + toAdd = append(toAdd, tag) |
| 290 | + } |
| 291 | + for _, tag := range latest { |
| 292 | + _, ok := desiredTags[*tag.Key] |
| 293 | + if !ok { |
| 294 | + toDelete = append(toDelete, tag.Key) |
| 295 | + } |
| 296 | + } |
| 297 | + return toAdd, toDelete |
| 298 | +} |
| 299 | + |
| 300 | +// sdkTagsFromResourceTags transforms a *svcapitypes.Tag array to a *svcsdk.Tag |
| 301 | +// array. |
| 302 | +func sdkTagsFromResourceTags( |
| 303 | + rTags []*svcapitypes.Tag, |
| 304 | +) []*svcsdk.Tag { |
| 305 | + tags := make([]*svcsdk.Tag, len(rTags)) |
| 306 | + for i := range rTags { |
| 307 | + tags[i] = &svcsdk.Tag{ |
| 308 | + Key: rTags[i].Key, |
| 309 | + Value: rTags[i].Value, |
| 310 | + } |
| 311 | + } |
| 312 | + return tags |
| 313 | +} |
| 314 | + |
| 315 | +func equalStrings(a, b *string) bool { |
| 316 | + if a == nil { |
| 317 | + return b == nil || *b == "" |
| 318 | + } |
| 319 | + return (*a == "" && b == nil) || *a == *b |
| 320 | +} |
0 commit comments