From 6d90b15a9dd04d441c561ea2adfd451f158e58dc Mon Sep 17 00:00:00 2001 From: Richard Case <198425+richardcase@users.noreply.github.com> Date: Thu, 30 Jul 2020 11:41:54 +0100 Subject: [PATCH 1/2] refactor: tags package to support other services The existing tags package applies tags to EC2 only. This is a small refactor to allow a 'apply' function to be passed in and this function contains resource/service specific functionality to apply the tags. The tags package still keeps the diff functionality and this can be used by new services such as EKS. --- pkg/cloud/services/network/eips.go | 26 ++++++----- pkg/cloud/services/network/gateways.go | 6 +-- pkg/cloud/services/network/natgateways.go | 6 +-- pkg/cloud/services/network/routetables.go | 12 ++--- pkg/cloud/services/network/securitygroups.go | 6 +-- pkg/cloud/services/network/subnets.go | 6 +-- pkg/cloud/services/network/tags.go | 47 ++++++++++++++++++++ pkg/cloud/services/network/vpc.go | 6 +-- pkg/cloud/tags/tags.go | 45 +++++++++---------- 9 files changed, 96 insertions(+), 64 deletions(-) create mode 100644 pkg/cloud/services/network/tags.go diff --git a/pkg/cloud/services/network/eips.go b/pkg/cloud/services/network/eips.go index feb07c9370..5480aec5cb 100644 --- a/pkg/cloud/services/network/eips.go +++ b/pkg/cloud/services/network/eips.go @@ -64,17 +64,8 @@ func (s *Service) allocateAddress(role string) (string, error) { } if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Apply(&tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: infrav1.BuildParams{ - ClusterName: s.scope.Name(), - ResourceID: *out.AllocationId, - Lifecycle: infrav1.ResourceLifecycleOwned, - Name: aws.String(fmt.Sprintf("%s-eip-%s", s.scope.Name(), role)), - Role: aws.String(role), - Additional: s.scope.AdditionalTags(), - }, - }); err != nil { + buildParams := s.getEIPTagParams(*out.AllocationId, role) + if err := tags.Apply(&buildParams, s.applyTags); err != nil { return false, err } return true, nil @@ -159,3 +150,16 @@ func (s *Service) releaseAddresses() error { } return nil } + +func (s *Service) getEIPTagParams(allocationID, role string) infrav1.BuildParams { + name := fmt.Sprintf("%s-eip-%s", s.scope.Name(), role) + + return infrav1.BuildParams{ + ClusterName: s.scope.Name(), + ResourceID: allocationID, + Lifecycle: infrav1.ResourceLifecycleOwned, + Name: aws.String(name), + Role: aws.String(role), + Additional: s.scope.AdditionalTags(), + } +} diff --git a/pkg/cloud/services/network/gateways.go b/pkg/cloud/services/network/gateways.go index d24c6c75dd..3374366cf7 100644 --- a/pkg/cloud/services/network/gateways.go +++ b/pkg/cloud/services/network/gateways.go @@ -60,10 +60,8 @@ func (s *Service) reconcileInternetGateways() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Ensure(converters.TagsToMap(gateway.Tags), &tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getGatewayTagParams(*gateway.InternetGatewayId), - }); err != nil { + buildParams := s.getGatewayTagParams(*gateway.InternetGatewayId) + if err := tags.Ensure(converters.TagsToMap(gateway.Tags), &buildParams, s.applyTags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/natgateways.go b/pkg/cloud/services/network/natgateways.go index 1bd13acacb..65de71e576 100644 --- a/pkg/cloud/services/network/natgateways.go +++ b/pkg/cloud/services/network/natgateways.go @@ -77,10 +77,8 @@ func (s *Service) reconcileNatGateways() error { if ngw, ok := existing[sn.ID]; ok { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Ensure(converters.TagsToMap(ngw.Tags), &tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getNatGatewayTagParams(*ngw.NatGatewayId), - }); err != nil { + buildParams := s.getNatGatewayTagParams(*ngw.NatGatewayId) + if err := tags.Ensure(converters.TagsToMap(ngw.Tags), &buildParams, s.applyTags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/routetables.go b/pkg/cloud/services/network/routetables.go index 4a0e9bfb54..ae0727231d 100644 --- a/pkg/cloud/services/network/routetables.go +++ b/pkg/cloud/services/network/routetables.go @@ -102,10 +102,8 @@ func (s *Service) reconcileRouteTables() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Ensure(converters.TagsToMap(rt.Tags), &tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getRouteTableTagParams(*rt.RouteTableId, sn.IsPublic, sn.AvailabilityZone), - }); err != nil { + buildParams := s.getRouteTableTagParams(*rt.RouteTableId, sn.IsPublic, sn.AvailabilityZone) + if err := tags.Ensure(converters.TagsToMap(rt.Tags), &buildParams, s.applyTags); err != nil { return false, err } return true, nil @@ -234,10 +232,8 @@ func (s *Service) createRouteTableWithRoutes(routes []*ec2.Route, isPublic bool, record.Eventf(s.scope.InfraCluster(), "SuccessfulCreateRouteTable", "Created managed RouteTable %q", *out.RouteTable.RouteTableId) if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Apply(&tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getRouteTableTagParams(*out.RouteTable.RouteTableId, isPublic, zone), - }); err != nil { + buildParams := s.getRouteTableTagParams(*out.RouteTable.RouteTableId, isPublic, zone) + if err := tags.Apply(&buildParams, s.applyTags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/securitygroups.go b/pkg/cloud/services/network/securitygroups.go index dd1d52a63a..06b6b6f95d 100644 --- a/pkg/cloud/services/network/securitygroups.go +++ b/pkg/cloud/services/network/securitygroups.go @@ -93,10 +93,8 @@ func (s *Service) reconcileSecurityGroups() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Ensure(existing.Tags, &tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getSecurityGroupTagParams(existing.Name, existing.ID, role), - }); err != nil { + buildParams := s.getSecurityGroupTagParams(existing.Name, existing.ID, role) + if err := tags.Ensure(existing.Tags, &buildParams, s.applyTags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/subnets.go b/pkg/cloud/services/network/subnets.go index fc6d656fe2..4673427d67 100644 --- a/pkg/cloud/services/network/subnets.go +++ b/pkg/cloud/services/network/subnets.go @@ -81,10 +81,8 @@ func (s *Service) reconcileSubnets() error { subnetTags := sub.Tags // Make sure tags are up to date if we have a managed VPC. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Ensure(existingSubnet.Tags, &tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getSubnetTagParams(existingSubnet.ID, existingSubnet.IsPublic, existingSubnet.AvailabilityZone, subnetTags), - }); err != nil { + buildParams := s.getSubnetTagParams(existingSubnet.ID, existingSubnet.IsPublic, existingSubnet.AvailabilityZone, subnetTags) + if err := tags.Ensure(existingSubnet.Tags, &buildParams, s.applyTags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/tags.go b/pkg/cloud/services/network/tags.go new file mode 100644 index 0000000000..fce99f9554 --- /dev/null +++ b/pkg/cloud/services/network/tags.go @@ -0,0 +1,47 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package network + +import ( + "github.com/pkg/errors" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + + infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3" +) + +func (s *Service) applyTags(params *infrav1.BuildParams) error { + tags := infrav1.Build(*params) + + awsTags := make([]*ec2.Tag, 0, len(tags)) + for k, v := range tags { + tag := &ec2.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + awsTags = append(awsTags, tag) + } + + createTagsInput := &ec2.CreateTagsInput{ + Resources: aws.StringSlice([]string{params.ResourceID}), + Tags: awsTags, + } + + _, err := s.EC2Client.CreateTags(createTagsInput) + return errors.Wrapf(err, "failed to tag resource %q in cluster %q", params.ResourceID, params.ClusterName) +} diff --git a/pkg/cloud/services/network/vpc.go b/pkg/cloud/services/network/vpc.go index b9bce0edd0..8d9ec35de8 100644 --- a/pkg/cloud/services/network/vpc.go +++ b/pkg/cloud/services/network/vpc.go @@ -81,10 +81,8 @@ func (s *Service) reconcileVPC() error { // Make sure attributes are configured if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { - if err := tags.Ensure(vpc.Tags, &tags.ApplyParams{ - EC2Client: s.EC2Client, - BuildParams: s.getVPCTagParams(vpc.ID), - }); err != nil { + buildParams := s.getVPCTagParams(vpc.ID) + if err := tags.Ensure(vpc.Tags, &buildParams, s.applyTags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/tags/tags.go b/pkg/cloud/tags/tags.go index 12908217ab..f0ec881c1e 100644 --- a/pkg/cloud/tags/tags.go +++ b/pkg/cloud/tags/tags.go @@ -17,41 +17,36 @@ limitations under the License. package tags import ( + "fmt" "sort" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/pkg/errors" infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3" ) -// ApplyParams are function parameters used to apply tags on an aws resource. -type ApplyParams struct { - infrav1.BuildParams - EC2Client ec2iface.EC2API -} +var ( + ErrBuildParamsRequired = errors.New("no build params supplied") + ErrApplyFuncRequired = errors.New("no tags apply function supplied") +) + +// TagsApplyFunc is used to define a function that will apply tags +type TagsApplyFunc func(params *infrav1.BuildParams) error // Apply tags a resource with tags including the cluster tag. -func Apply(params *ApplyParams) error { - tags := infrav1.Build(params.BuildParams) - - awsTags := make([]*ec2.Tag, 0, len(tags)) - for k, v := range tags { - tag := &ec2.Tag{ - Key: aws.String(k), - Value: aws.String(v), - } - awsTags = append(awsTags, tag) +func Apply(params *infrav1.BuildParams, fn TagsApplyFunc) error { + if params == nil { + return ErrBuildParamsRequired } - - createTagsInput := &ec2.CreateTagsInput{ - Resources: aws.StringSlice([]string{params.ResourceID}), - Tags: awsTags, + if fn == nil { + return ErrApplyFuncRequired } - _, err := params.EC2Client.CreateTags(createTagsInput) - return errors.Wrapf(err, "failed to tag resource %q in cluster %q", params.ResourceID, params.ClusterName) + if err := fn(params); err != nil { + return fmt.Errorf("failed applying tags: %w", err) + } + return nil } // BuildParamsToTagSpecification builds a TagSpecification for the specified resource type @@ -79,10 +74,10 @@ func BuildParamsToTagSpecification(ec2ResourceType string, params infrav1.BuildP } // Ensure applies the tags if the current tags differ from the params. -func Ensure(current infrav1.Tags, params *ApplyParams) error { - diff := computeDiff(current, params.BuildParams) +func Ensure(current infrav1.Tags, params *infrav1.BuildParams, fn TagsApplyFunc) error { + diff := computeDiff(current, *params) if len(diff) > 0 { - return Apply(params) + return Apply(params, fn) } return nil } From aa11790780db8578729a60d6268bf6fd82a41bb1 Mon Sep 17 00:00:00 2001 From: Richard Case <198425+richardcase@users.noreply.github.com> Date: Mon, 3 Aug 2020 17:56:40 +0100 Subject: [PATCH 2/2] refactor: use builder with functional opts Changed the tags package to use a builder struct thats created with opts. Moved the EC2 tag setting back into the package. --- pkg/cloud/services/network/eips.go | 3 +- pkg/cloud/services/network/gateways.go | 3 +- pkg/cloud/services/network/natgateways.go | 3 +- pkg/cloud/services/network/routetables.go | 6 +- pkg/cloud/services/network/securitygroups.go | 3 +- pkg/cloud/services/network/subnets.go | 3 +- pkg/cloud/services/network/tags.go | 47 --------- pkg/cloud/services/network/vpc.go | 3 +- pkg/cloud/tags/tags.go | 102 ++++++++++++++----- 9 files changed, 91 insertions(+), 82 deletions(-) delete mode 100644 pkg/cloud/services/network/tags.go diff --git a/pkg/cloud/services/network/eips.go b/pkg/cloud/services/network/eips.go index 5480aec5cb..9028f73dbf 100644 --- a/pkg/cloud/services/network/eips.go +++ b/pkg/cloud/services/network/eips.go @@ -65,7 +65,8 @@ func (s *Service) allocateAddress(role string) (string, error) { if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getEIPTagParams(*out.AllocationId, role) - if err := tags.Apply(&buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Apply(); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/gateways.go b/pkg/cloud/services/network/gateways.go index 3374366cf7..9e2ecab9c8 100644 --- a/pkg/cloud/services/network/gateways.go +++ b/pkg/cloud/services/network/gateways.go @@ -61,7 +61,8 @@ func (s *Service) reconcileInternetGateways() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getGatewayTagParams(*gateway.InternetGatewayId) - if err := tags.Ensure(converters.TagsToMap(gateway.Tags), &buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Ensure(converters.TagsToMap(gateway.Tags)); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/natgateways.go b/pkg/cloud/services/network/natgateways.go index 65de71e576..8369931468 100644 --- a/pkg/cloud/services/network/natgateways.go +++ b/pkg/cloud/services/network/natgateways.go @@ -78,7 +78,8 @@ func (s *Service) reconcileNatGateways() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getNatGatewayTagParams(*ngw.NatGatewayId) - if err := tags.Ensure(converters.TagsToMap(ngw.Tags), &buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Ensure(converters.TagsToMap(ngw.Tags)); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/routetables.go b/pkg/cloud/services/network/routetables.go index ae0727231d..5d73b329b1 100644 --- a/pkg/cloud/services/network/routetables.go +++ b/pkg/cloud/services/network/routetables.go @@ -103,7 +103,8 @@ func (s *Service) reconcileRouteTables() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getRouteTableTagParams(*rt.RouteTableId, sn.IsPublic, sn.AvailabilityZone) - if err := tags.Ensure(converters.TagsToMap(rt.Tags), &buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Ensure(converters.TagsToMap(rt.Tags)); err != nil { return false, err } return true, nil @@ -233,7 +234,8 @@ func (s *Service) createRouteTableWithRoutes(routes []*ec2.Route, isPublic bool, if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getRouteTableTagParams(*out.RouteTable.RouteTableId, isPublic, zone) - if err := tags.Apply(&buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Apply(); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/securitygroups.go b/pkg/cloud/services/network/securitygroups.go index 06b6b6f95d..4874361caa 100644 --- a/pkg/cloud/services/network/securitygroups.go +++ b/pkg/cloud/services/network/securitygroups.go @@ -94,7 +94,8 @@ func (s *Service) reconcileSecurityGroups() error { // Make sure tags are up to date. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getSecurityGroupTagParams(existing.Name, existing.ID, role) - if err := tags.Ensure(existing.Tags, &buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Ensure(existing.Tags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/subnets.go b/pkg/cloud/services/network/subnets.go index 4673427d67..6f9a7c3271 100644 --- a/pkg/cloud/services/network/subnets.go +++ b/pkg/cloud/services/network/subnets.go @@ -82,7 +82,8 @@ func (s *Service) reconcileSubnets() error { // Make sure tags are up to date if we have a managed VPC. if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getSubnetTagParams(existingSubnet.ID, existingSubnet.IsPublic, existingSubnet.AvailabilityZone, subnetTags) - if err := tags.Ensure(existingSubnet.Tags, &buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Ensure(existingSubnet.Tags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/services/network/tags.go b/pkg/cloud/services/network/tags.go deleted file mode 100644 index fce99f9554..0000000000 --- a/pkg/cloud/services/network/tags.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package network - -import ( - "github.com/pkg/errors" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - - infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3" -) - -func (s *Service) applyTags(params *infrav1.BuildParams) error { - tags := infrav1.Build(*params) - - awsTags := make([]*ec2.Tag, 0, len(tags)) - for k, v := range tags { - tag := &ec2.Tag{ - Key: aws.String(k), - Value: aws.String(v), - } - awsTags = append(awsTags, tag) - } - - createTagsInput := &ec2.CreateTagsInput{ - Resources: aws.StringSlice([]string{params.ResourceID}), - Tags: awsTags, - } - - _, err := s.EC2Client.CreateTags(createTagsInput) - return errors.Wrapf(err, "failed to tag resource %q in cluster %q", params.ResourceID, params.ClusterName) -} diff --git a/pkg/cloud/services/network/vpc.go b/pkg/cloud/services/network/vpc.go index 8d9ec35de8..f22eca039b 100644 --- a/pkg/cloud/services/network/vpc.go +++ b/pkg/cloud/services/network/vpc.go @@ -82,7 +82,8 @@ func (s *Service) reconcileVPC() error { // Make sure attributes are configured if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { buildParams := s.getVPCTagParams(vpc.ID) - if err := tags.Ensure(vpc.Tags, &buildParams, s.applyTags); err != nil { + tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client)) + if err := tagsBuilder.Ensure(vpc.Tags); err != nil { return false, err } return true, nil diff --git a/pkg/cloud/tags/tags.go b/pkg/cloud/tags/tags.go index f0ec881c1e..8bf7976da5 100644 --- a/pkg/cloud/tags/tags.go +++ b/pkg/cloud/tags/tags.go @@ -22,7 +22,9 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/pkg/errors" + infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3" ) @@ -31,24 +33,91 @@ var ( ErrApplyFuncRequired = errors.New("no tags apply function supplied") ) -// TagsApplyFunc is used to define a function that will apply tags -type TagsApplyFunc func(params *infrav1.BuildParams) error +// BuilderOption represents an option when creating a tags builder +type BuilderOption func(*Builder) + +// Builder is the interface for a tags builder +type Builder struct { + params *infrav1.BuildParams + applyFunc func(params *infrav1.BuildParams) error +} + +// New creates a new TagsBuilder with the specified build parameters +// and with optional configuration +func New(params *infrav1.BuildParams, opts ...BuilderOption) *Builder { + builder := &Builder{ + params: params, + } + + for _, opt := range opts { + opt(builder) + } + + return builder +} // Apply tags a resource with tags including the cluster tag. -func Apply(params *infrav1.BuildParams, fn TagsApplyFunc) error { - if params == nil { +func (b *Builder) Apply() error { + if b.params == nil { return ErrBuildParamsRequired } - if fn == nil { + if b.applyFunc == nil { return ErrApplyFuncRequired } - if err := fn(params); err != nil { + if err := b.applyFunc(b.params); err != nil { return fmt.Errorf("failed applying tags: %w", err) } return nil } +// Ensure applies the tags if the current tags differ from the params. +func (b *Builder) Ensure(current infrav1.Tags) error { + diff := computeDiff(current, *b.params) + if len(diff) > 0 { + return b.Apply() + } + return nil +} + +// WithEC2 is used to denote that the tags builder will be using EC2 +func WithEC2(ec2client ec2iface.EC2API) BuilderOption { + return func(b *Builder) { + b.applyFunc = func(params *infrav1.BuildParams) error { + tags := infrav1.Build(*params) + + awsTags := make([]*ec2.Tag, 0, len(tags)) + for k, v := range tags { + tag := &ec2.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + awsTags = append(awsTags, tag) + } + + createTagsInput := &ec2.CreateTagsInput{ + Resources: aws.StringSlice([]string{params.ResourceID}), + Tags: awsTags, + } + + _, err := ec2client.CreateTags(createTagsInput) + return errors.Wrapf(err, "failed to tag resource %q in cluster %q", params.ResourceID, params.ClusterName) + } + } +} + +func computeDiff(current infrav1.Tags, buildParams infrav1.BuildParams) infrav1.Tags { + want := infrav1.Build(buildParams) + + // Some tags could be external set by some external entities + // and that means even if there is no change in cluster + // managed tags, tags would be updated as "current" and + // "want" would be different due to external tags. + // This fix makes sure that tags are updated only if + // there is a change in cluster managed tags. + return want.Difference(current) +} + // BuildParamsToTagSpecification builds a TagSpecification for the specified resource type func BuildParamsToTagSpecification(ec2ResourceType string, params infrav1.BuildParams) *ec2.TagSpecification { tags := infrav1.Build(params) @@ -72,24 +141,3 @@ func BuildParamsToTagSpecification(ec2ResourceType string, params infrav1.BuildP return tagSpec } - -// Ensure applies the tags if the current tags differ from the params. -func Ensure(current infrav1.Tags, params *infrav1.BuildParams, fn TagsApplyFunc) error { - diff := computeDiff(current, *params) - if len(diff) > 0 { - return Apply(params, fn) - } - return nil -} - -func computeDiff(current infrav1.Tags, buildParams infrav1.BuildParams) infrav1.Tags { - want := infrav1.Build(buildParams) - - // Some tags could be external set by some external entities - // and that means even if there is no change in cluster - // managed tags, tags would be updated as "current" and - // "want" would be different due to external tags. - // This fix makes sure that tags are updated only if - // there is a change in cluster managed tags. - return want.Difference(current) -}