From 4f31522cf4d8bb3750d253a86facbb7aa1ca7b11 Mon Sep 17 00:00:00 2001 From: ci-robot Date: Tue, 18 Feb 2025 00:52:30 +0000 Subject: [PATCH] Update to ACK runtime `v0.43.0`, code-generator `v0.43.0` --- apis/v1alpha1/ack-generate-metadata.yaml | 10 ++-- config/controller/kustomization.yaml | 2 +- go.mod | 2 +- go.sum | 4 +- helm/Chart.yaml | 4 +- helm/templates/NOTES.txt | 2 +- helm/values.yaml | 2 +- pkg/resource/capacity_reservation/manager.go | 44 +++++++++++++++++ pkg/resource/capacity_reservation/tags.go | 48 ++++++++++++++++++- pkg/resource/dhcp_options/manager.go | 44 +++++++++++++++++ pkg/resource/dhcp_options/tags.go | 48 ++++++++++++++++++- pkg/resource/elastic_ip_address/manager.go | 44 +++++++++++++++++ pkg/resource/elastic_ip_address/tags.go | 48 ++++++++++++++++++- pkg/resource/flow_log/manager.go | 44 +++++++++++++++++ pkg/resource/flow_log/tags.go | 48 ++++++++++++++++++- pkg/resource/instance/manager.go | 44 +++++++++++++++++ pkg/resource/instance/tags.go | 48 ++++++++++++++++++- pkg/resource/internet_gateway/manager.go | 44 +++++++++++++++++ pkg/resource/internet_gateway/tags.go | 48 ++++++++++++++++++- pkg/resource/nat_gateway/manager.go | 44 +++++++++++++++++ pkg/resource/nat_gateway/tags.go | 48 ++++++++++++++++++- pkg/resource/network_acl/manager.go | 44 +++++++++++++++++ pkg/resource/network_acl/tags.go | 48 ++++++++++++++++++- pkg/resource/route_table/manager.go | 44 +++++++++++++++++ pkg/resource/route_table/tags.go | 48 ++++++++++++++++++- pkg/resource/security_group/manager.go | 44 +++++++++++++++++ pkg/resource/security_group/tags.go | 48 ++++++++++++++++++- pkg/resource/subnet/manager.go | 44 +++++++++++++++++ pkg/resource/subnet/tags.go | 48 ++++++++++++++++++- pkg/resource/transit_gateway/manager.go | 44 +++++++++++++++++ pkg/resource/transit_gateway/tags.go | 48 ++++++++++++++++++- pkg/resource/vpc/manager.go | 44 +++++++++++++++++ pkg/resource/vpc/tags.go | 48 ++++++++++++++++++- pkg/resource/vpc_endpoint/hooks.go | 8 ++-- pkg/resource/vpc_endpoint/manager.go | 44 +++++++++++++++++ pkg/resource/vpc_endpoint/tags.go | 48 ++++++++++++++++++- .../manager.go | 44 +++++++++++++++++ .../tags.go | 48 ++++++++++++++++++- .../vpc_peering_connection/manager.go | 44 +++++++++++++++++ pkg/resource/vpc_peering_connection/tags.go | 48 ++++++++++++++++++- 40 files changed, 1457 insertions(+), 49 deletions(-) diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 4242b44f..813cb7ca 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,9 +1,9 @@ ack_generate_info: - build_date: "2025-02-09T23:26:36Z" - build_hash: 8762917215d9902b2011a2b0b1b0c776855a683e - go_version: go1.23.4 - version: v0.42.0-dirty -api_directory_checksum: 6da0928938d9fb2e79fb8f7ab09fa6da0c5f3049 + build_date: "2025-02-18T00:51:49Z" + build_hash: 66c0f840b0bcf6f552be46cf5ee0fb95ad57053e + go_version: go1.23.6 + version: v0.43.0 +api_directory_checksum: 3a83e3e04bb049345203b77de8f4bbf46f808b0a api_version: v1alpha1 aws_sdk_go_version: v1.32.6 generator_config_info: diff --git a/config/controller/kustomization.yaml b/config/controller/kustomization.yaml index f32102a3..3e9ecd78 100644 --- a/config/controller/kustomization.yaml +++ b/config/controller/kustomization.yaml @@ -6,4 +6,4 @@ kind: Kustomization images: - name: controller newName: public.ecr.aws/aws-controllers-k8s/ec2-controller - newTag: 1.3.4 + newTag: 1.3.5 diff --git a/go.mod b/go.mod index e5b337a2..020bd92a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 toolchain go1.22.6 require ( - github.com/aws-controllers-k8s/runtime v0.42.0 + github.com/aws-controllers-k8s/runtime v0.43.0 github.com/aws/aws-sdk-go v1.49.0 github.com/aws/aws-sdk-go-v2 v1.35.0 github.com/aws/aws-sdk-go-v2/service/ec2 v1.202.1 diff --git a/go.sum b/go.sum index 6e70d6c8..9c76fedd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aws-controllers-k8s/runtime v0.42.0 h1:fVb3cOwUtn0ZwTSedapES+Rspb97S8BTxMqXJt6R5uM= -github.com/aws-controllers-k8s/runtime v0.42.0/go.mod h1:Oy0JKvDxZMZ+SVupm4NZVqP00KLIIAMfk93KnOwlt5c= +github.com/aws-controllers-k8s/runtime v0.43.0 h1:mCtMHO0rew84VbqotquvBirnKysbao+y2G3QI8bKZxM= +github.com/aws-controllers-k8s/runtime v0.43.0/go.mod h1:Oy0JKvDxZMZ+SVupm4NZVqP00KLIIAMfk93KnOwlt5c= github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.35.0 h1:jTPxEJyzjSuuz0wB+302hr8Eu9KUI+Zv8zlujMGJpVI= diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 39c0f9a2..a3461c97 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 name: ec2-chart description: A Helm chart for the ACK service controller for Amazon Elastic Cloud Compute (EC2) -version: 1.3.4 -appVersion: 1.3.4 +version: 1.3.5 +appVersion: 1.3.5 home: https://github.com/aws-controllers-k8s/ec2-controller icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png sources: diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index 484d2fce..276d3ec8 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -1,5 +1,5 @@ {{ .Chart.Name }} has been installed. -This chart deploys "public.ecr.aws/aws-controllers-k8s/ec2-controller:1.3.4". +This chart deploys "public.ecr.aws/aws-controllers-k8s/ec2-controller:1.3.5". Check its status by running: kubectl --namespace {{ .Release.Namespace }} get pods -l "app.kubernetes.io/instance={{ .Release.Name }}" diff --git a/helm/values.yaml b/helm/values.yaml index 13db9884..070e7d8c 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -4,7 +4,7 @@ image: repository: public.ecr.aws/aws-controllers-k8s/ec2-controller - tag: 1.3.4 + tag: 1.3.5 pullPolicy: IfNotPresent pullSecrets: [] diff --git a/pkg/resource/capacity_reservation/manager.go b/pkg/resource/capacity_reservation/manager.go index fc7927d5..4d2c2d25 100644 --- a/pkg/resource/capacity_reservation/manager.go +++ b/pkg/resource/capacity_reservation/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/capacity_reservation/tags.go b/pkg/resource/capacity_reservation/tags.go index bf088e50..b69c3f17 100644 --- a/pkg/resource/capacity_reservation/tags.go +++ b/pkg/resource/capacity_reservation/tags.go @@ -16,14 +16,18 @@ package capacity_reservation import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.CapacityReservation{} - _ = acktags.NewTags() + _ = svcapitypes.CapacityReservation{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/dhcp_options/manager.go b/pkg/resource/dhcp_options/manager.go index 93968799..9558b72a 100644 --- a/pkg/resource/dhcp_options/manager.go +++ b/pkg/resource/dhcp_options/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/dhcp_options/tags.go b/pkg/resource/dhcp_options/tags.go index c31ceb18..0eea162b 100644 --- a/pkg/resource/dhcp_options/tags.go +++ b/pkg/resource/dhcp_options/tags.go @@ -16,14 +16,18 @@ package dhcp_options import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.DHCPOptions{} - _ = acktags.NewTags() + _ = svcapitypes.DHCPOptions{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/elastic_ip_address/manager.go b/pkg/resource/elastic_ip_address/manager.go index 56b28d79..d36b33c5 100644 --- a/pkg/resource/elastic_ip_address/manager.go +++ b/pkg/resource/elastic_ip_address/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/elastic_ip_address/tags.go b/pkg/resource/elastic_ip_address/tags.go index d3e20e2f..208d8845 100644 --- a/pkg/resource/elastic_ip_address/tags.go +++ b/pkg/resource/elastic_ip_address/tags.go @@ -16,14 +16,18 @@ package elastic_ip_address import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.ElasticIPAddress{} - _ = acktags.NewTags() + _ = svcapitypes.ElasticIPAddress{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/flow_log/manager.go b/pkg/resource/flow_log/manager.go index ec9b12b0..96eb13c7 100644 --- a/pkg/resource/flow_log/manager.go +++ b/pkg/resource/flow_log/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/flow_log/tags.go b/pkg/resource/flow_log/tags.go index 11ec1226..a2af7027 100644 --- a/pkg/resource/flow_log/tags.go +++ b/pkg/resource/flow_log/tags.go @@ -16,14 +16,18 @@ package flow_log import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.FlowLog{} - _ = acktags.NewTags() + _ = svcapitypes.FlowLog{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/instance/manager.go b/pkg/resource/instance/manager.go index 77740667..bf286644 100644 --- a/pkg/resource/instance/manager.go +++ b/pkg/resource/instance/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -305,6 +306,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/instance/tags.go b/pkg/resource/instance/tags.go index 7eb7f81c..54f62b17 100644 --- a/pkg/resource/instance/tags.go +++ b/pkg/resource/instance/tags.go @@ -16,14 +16,18 @@ package instance import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.Instance{} - _ = acktags.NewTags() + _ = svcapitypes.Instance{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/internet_gateway/manager.go b/pkg/resource/internet_gateway/manager.go index 8b773ac8..d1e3b1f0 100644 --- a/pkg/resource/internet_gateway/manager.go +++ b/pkg/resource/internet_gateway/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/internet_gateway/tags.go b/pkg/resource/internet_gateway/tags.go index 1c14ef83..58d28319 100644 --- a/pkg/resource/internet_gateway/tags.go +++ b/pkg/resource/internet_gateway/tags.go @@ -16,14 +16,18 @@ package internet_gateway import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.InternetGateway{} - _ = acktags.NewTags() + _ = svcapitypes.InternetGateway{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/nat_gateway/manager.go b/pkg/resource/nat_gateway/manager.go index 1de70691..ea7c7da8 100644 --- a/pkg/resource/nat_gateway/manager.go +++ b/pkg/resource/nat_gateway/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -304,6 +305,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/nat_gateway/tags.go b/pkg/resource/nat_gateway/tags.go index e328217e..65a272a9 100644 --- a/pkg/resource/nat_gateway/tags.go +++ b/pkg/resource/nat_gateway/tags.go @@ -16,14 +16,18 @@ package nat_gateway import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.NATGateway{} - _ = acktags.NewTags() + _ = svcapitypes.NATGateway{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/network_acl/manager.go b/pkg/resource/network_acl/manager.go index 19aa8322..22c6508b 100644 --- a/pkg/resource/network_acl/manager.go +++ b/pkg/resource/network_acl/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/network_acl/tags.go b/pkg/resource/network_acl/tags.go index c4dec12c..0466ee08 100644 --- a/pkg/resource/network_acl/tags.go +++ b/pkg/resource/network_acl/tags.go @@ -16,14 +16,18 @@ package network_acl import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.NetworkACL{} - _ = acktags.NewTags() + _ = svcapitypes.NetworkACL{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/route_table/manager.go b/pkg/resource/route_table/manager.go index c093072e..9351b298 100644 --- a/pkg/resource/route_table/manager.go +++ b/pkg/resource/route_table/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/route_table/tags.go b/pkg/resource/route_table/tags.go index 7b7c9186..71175ecb 100644 --- a/pkg/resource/route_table/tags.go +++ b/pkg/resource/route_table/tags.go @@ -16,14 +16,18 @@ package route_table import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.RouteTable{} - _ = acktags.NewTags() + _ = svcapitypes.RouteTable{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/security_group/manager.go b/pkg/resource/security_group/manager.go index ba7da041..d12bff66 100644 --- a/pkg/resource/security_group/manager.go +++ b/pkg/resource/security_group/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/security_group/tags.go b/pkg/resource/security_group/tags.go index 8e02c7c5..f6a772c0 100644 --- a/pkg/resource/security_group/tags.go +++ b/pkg/resource/security_group/tags.go @@ -16,14 +16,18 @@ package security_group import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.SecurityGroup{} - _ = acktags.NewTags() + _ = svcapitypes.SecurityGroup{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/subnet/manager.go b/pkg/resource/subnet/manager.go index 110a11ea..ea9aa1ae 100644 --- a/pkg/resource/subnet/manager.go +++ b/pkg/resource/subnet/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/subnet/tags.go b/pkg/resource/subnet/tags.go index 84bbf27f..9a2869b7 100644 --- a/pkg/resource/subnet/tags.go +++ b/pkg/resource/subnet/tags.go @@ -16,14 +16,18 @@ package subnet import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.Subnet{} - _ = acktags.NewTags() + _ = svcapitypes.Subnet{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/transit_gateway/manager.go b/pkg/resource/transit_gateway/manager.go index d43a89b2..27871f7a 100644 --- a/pkg/resource/transit_gateway/manager.go +++ b/pkg/resource/transit_gateway/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/transit_gateway/tags.go b/pkg/resource/transit_gateway/tags.go index 63285384..e4a40a68 100644 --- a/pkg/resource/transit_gateway/tags.go +++ b/pkg/resource/transit_gateway/tags.go @@ -16,14 +16,18 @@ package transit_gateway import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.TransitGateway{} - _ = acktags.NewTags() + _ = svcapitypes.TransitGateway{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/vpc/manager.go b/pkg/resource/vpc/manager.go index d1c03f7b..346f72ad 100644 --- a/pkg/resource/vpc/manager.go +++ b/pkg/resource/vpc/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/vpc/tags.go b/pkg/resource/vpc/tags.go index 2cec6b7d..8926edbd 100644 --- a/pkg/resource/vpc/tags.go +++ b/pkg/resource/vpc/tags.go @@ -16,14 +16,18 @@ package vpc import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.VPC{} - _ = acktags.NewTags() + _ = svcapitypes.VPC{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/vpc_endpoint/hooks.go b/pkg/resource/vpc_endpoint/hooks.go index fbb1e935..69dbda4f 100644 --- a/pkg/resource/vpc_endpoint/hooks.go +++ b/pkg/resource/vpc_endpoint/hooks.go @@ -83,8 +83,8 @@ func (rm *resourceManager) customUpdateVPCEndpoint( } if !delta.DifferentExcept("Spec.Tags") { - return desired, nil - } + return desired, nil + } // Handle modifications that require ModifyVpcEndpoint API call // avoid making the ModifyVpcEndpoint API call if only tags are modified @@ -140,8 +140,8 @@ func (rm *resourceManager) customUpdateVPCEndpoint( _, err = rm.sdkapi.ModifyVpcEndpoint(ctx, input) rm.metrics.RecordAPICall("UPDATE", "ModifyVpcEndpoint", err) - if err != nil { - return nil, err + if err != nil { + return nil, err } return updated, nil } diff --git a/pkg/resource/vpc_endpoint/manager.go b/pkg/resource/vpc_endpoint/manager.go index e588bbdf..7a64d69a 100644 --- a/pkg/resource/vpc_endpoint/manager.go +++ b/pkg/resource/vpc_endpoint/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -305,6 +306,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/vpc_endpoint/tags.go b/pkg/resource/vpc_endpoint/tags.go index 790d4d3c..0ce218cb 100644 --- a/pkg/resource/vpc_endpoint/tags.go +++ b/pkg/resource/vpc_endpoint/tags.go @@ -16,14 +16,18 @@ package vpc_endpoint import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.VPCEndpoint{} - _ = acktags.NewTags() + _ = svcapitypes.VPCEndpoint{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/vpc_endpoint_service_configuration/manager.go b/pkg/resource/vpc_endpoint_service_configuration/manager.go index 918cad0b..c4b3378b 100644 --- a/pkg/resource/vpc_endpoint_service_configuration/manager.go +++ b/pkg/resource/vpc_endpoint_service_configuration/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -304,6 +305,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/vpc_endpoint_service_configuration/tags.go b/pkg/resource/vpc_endpoint_service_configuration/tags.go index 6c1c4607..69c460df 100644 --- a/pkg/resource/vpc_endpoint_service_configuration/tags.go +++ b/pkg/resource/vpc_endpoint_service_configuration/tags.go @@ -16,14 +16,18 @@ package vpc_endpoint_service_configuration import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.VPCEndpointServiceConfiguration{} - _ = acktags.NewTags() + _ = svcapitypes.VPCEndpointServiceConfiguration{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/resource/vpc_peering_connection/manager.go b/pkg/resource/vpc_peering_connection/manager.go index e8e07fc3..62b40d9a 100644 --- a/pkg/resource/vpc_peering_connection/manager.go +++ b/pkg/resource/vpc_peering_connection/manager.go @@ -102,6 +102,7 @@ func (rm *resourceManager) ReadOne( panic("resource manager's ReadOne() method received resource with nil CR object") } observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) if err != nil { if observed != nil { return rm.onError(observed, err) @@ -296,6 +297,49 @@ func (rm *resourceManager) EnsureTags( return nil } +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = FromACKTags(resourceTags) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags := ToACKTags(existingDesiredTags) + latestTags := ToACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = FromACKTags(desiredTags) +} + // newResourceManager returns a new struct implementing // acktypes.AWSResourceManager // This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 diff --git a/pkg/resource/vpc_peering_connection/tags.go b/pkg/resource/vpc_peering_connection/tags.go index b5632f37..82026dc4 100644 --- a/pkg/resource/vpc_peering_connection/tags.go +++ b/pkg/resource/vpc_peering_connection/tags.go @@ -16,14 +16,18 @@ package vpc_peering_connection import ( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ) var ( - _ = svcapitypes.VPCPeeringConnection{} - _ = acktags.NewTags() + _ = svcapitypes.VPCPeeringConnection{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} ) // ToACKTags converts the tags parameter into 'acktags.Tags' shape. @@ -61,3 +65,43 @@ func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { } return result } + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +}