Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌱 refactor tags package to support other services #1848

Merged
merged 2 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions pkg/cloud/services/network/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Apply(); err != nil {
return false, err
}
return true, nil
Expand Down Expand Up @@ -159,3 +151,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(),
}
}
7 changes: 3 additions & 4 deletions pkg/cloud/services/network/gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Ensure(converters.TagsToMap(gateway.Tags)); err != nil {
return false, err
}
return true, nil
Expand Down
7 changes: 3 additions & 4 deletions pkg/cloud/services/network/natgateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Ensure(converters.TagsToMap(ngw.Tags)); err != nil {
return false, err
}
return true, nil
Expand Down
14 changes: 6 additions & 8 deletions pkg/cloud/services/network/routetables.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Ensure(converters.TagsToMap(rt.Tags)); err != nil {
return false, err
}
return true, nil
Expand Down Expand Up @@ -234,10 +233,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Apply(); err != nil {
return false, err
}
return true, nil
Expand Down
7 changes: 3 additions & 4 deletions pkg/cloud/services/network/securitygroups.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Ensure(existing.Tags); err != nil {
return false, err
}
return true, nil
Expand Down
7 changes: 3 additions & 4 deletions pkg/cloud/services/network/subnets.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Ensure(existingSubnet.Tags); err != nil {
return false, err
}
return true, nil
Expand Down
7 changes: 3 additions & 4 deletions pkg/cloud/services/network/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ 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)
tagsBuilder := tags.New(&buildParams, tags.WithEC2(s.EC2Client))
if err := tagsBuilder.Ensure(vpc.Tags); err != nil {
return false, err
}
return true, nil
Expand Down
123 changes: 83 additions & 40 deletions pkg/cloud/tags/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,105 @@ 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")
)

// 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 *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 (b *Builder) Apply() error {
if b.params == nil {
return ErrBuildParamsRequired
}
if b.applyFunc == nil {
return ErrApplyFuncRequired
}

if err := b.applyFunc(b.params); err != nil {
return fmt.Errorf("failed applying tags: %w", err)
}
return nil
}

createTagsInput := &ec2.CreateTagsInput{
Resources: aws.StringSlice([]string{params.ResourceID}),
Tags: awsTags,
// 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)

_, err := params.EC2Client.CreateTags(createTagsInput)
return errors.Wrapf(err, "failed to tag resource %q in cluster %q", params.ResourceID, params.ClusterName)
// 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
Expand All @@ -77,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 *ApplyParams) error {
diff := computeDiff(current, params.BuildParams)
if len(diff) > 0 {
return Apply(params)
}
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)
}