Skip to content

Commit

Permalink
Merge pull request #1170 from haarchri/feature/ec2-vpc-subnet-tag-dif…
Browse files Browse the repository at this point in the history
…f-default

feat(ec2-tags): tags vpc/subnet default,add,delete
  • Loading branch information
haarchri committed Feb 28, 2022
2 parents 9586cfc + 3db430a commit dd6a918
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 13 deletions.
6 changes: 6 additions & 0 deletions pkg/clients/ec2/fake/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type MockSubnetClient struct {
MockDescribe func(ctx context.Context, input *ec2.DescribeSubnetsInput, opts []func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error)
MockModify func(ctx context.Context, input *ec2.ModifySubnetAttributeInput, opts []func(*ec2.Options)) (*ec2.ModifySubnetAttributeOutput, error)
MockCreateTags func(ctx context.Context, input *ec2.CreateTagsInput, opts []func(*ec2.Options)) (*ec2.CreateTagsOutput, error)
MockDeleteTags func(ctx context.Context, input *ec2.DeleteTagsInput, opts []func(*ec2.Options)) (*ec2.DeleteTagsOutput, error)
}

// CreateSubnet mocks CreateSubnet method
Expand All @@ -60,3 +61,8 @@ func (m *MockSubnetClient) ModifySubnetAttribute(ctx context.Context, input *ec2
func (m *MockSubnetClient) CreateTags(ctx context.Context, input *ec2.CreateTagsInput, opts ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error) {
return m.MockCreateTags(ctx, input, opts)
}

// DeleteTags mocks DeleteTags method
func (m *MockSubnetClient) DeleteTags(ctx context.Context, input *ec2.DeleteTagsInput, opts ...func(*ec2.Options)) (*ec2.DeleteTagsOutput, error) {
return m.MockDeleteTags(ctx, input, opts)
}
6 changes: 6 additions & 0 deletions pkg/clients/ec2/fake/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type MockVPCClient struct {
MockModifyAttribute func(ctx context.Context, input *ec2.ModifyVpcAttributeInput, opts []func(*ec2.Options)) (*ec2.ModifyVpcAttributeOutput, error)
MockModifyTenancy func(ctx context.Context, input *ec2.ModifyVpcTenancyInput, opts []func(*ec2.Options)) (*ec2.ModifyVpcTenancyOutput, error)
MockCreateTags func(ctx context.Context, input *ec2.CreateTagsInput, opts []func(*ec2.Options)) (*ec2.CreateTagsOutput, error)
MockDeleteTags func(ctx context.Context, input *ec2.DeleteTagsInput, opts []func(*ec2.Options)) (*ec2.DeleteTagsOutput, error)
MockDescribeVpcAttribute func(ctx context.Context, input *ec2.DescribeVpcAttributeInput, opts []func(*ec2.Options)) (*ec2.DescribeVpcAttributeOutput, error)
}

Expand Down Expand Up @@ -68,6 +69,11 @@ func (m *MockVPCClient) CreateTags(ctx context.Context, input *ec2.CreateTagsInp
return m.MockCreateTags(ctx, input, opts)
}

// DeleteTags mocks DeleteTags method
func (m *MockVPCClient) DeleteTags(ctx context.Context, input *ec2.DeleteTagsInput, opts ...func(*ec2.Options)) (*ec2.DeleteTagsOutput, error) {
return m.MockDeleteTags(ctx, input, opts)
}

// DescribeVpcAttribute mocks DescribeVpcAttribute method
func (m *MockVPCClient) DescribeVpcAttribute(ctx context.Context, input *ec2.DescribeVpcAttributeInput, opts ...func(*ec2.Options)) (*ec2.DescribeVpcAttributeOutput, error) {
return m.MockDescribeVpcAttribute(ctx, input, opts)
Expand Down
1 change: 1 addition & 0 deletions pkg/clients/ec2/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type SubnetClient interface {
DeleteSubnet(ctx context.Context, input *ec2.DeleteSubnetInput, opts ...func(*ec2.Options)) (*ec2.DeleteSubnetOutput, error)
ModifySubnetAttribute(ctx context.Context, input *ec2.ModifySubnetAttributeInput, opts ...func(*ec2.Options)) (*ec2.ModifySubnetAttributeOutput, error)
CreateTags(ctx context.Context, input *ec2.CreateTagsInput, opts ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error)
DeleteTags(ctx context.Context, input *ec2.DeleteTagsInput, opts ...func(*ec2.Options)) (*ec2.DeleteTagsOutput, error)
}

// NewSubnetClient returns a new client using AWS credentials as JSON encoded data.
Expand Down
1 change: 1 addition & 0 deletions pkg/clients/ec2/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type VPCClient interface {
DescribeVpcAttribute(ctx context.Context, input *ec2.DescribeVpcAttributeInput, opts ...func(*ec2.Options)) (*ec2.DescribeVpcAttributeOutput, error)
ModifyVpcAttribute(ctx context.Context, input *ec2.ModifyVpcAttributeInput, opts ...func(*ec2.Options)) (*ec2.ModifyVpcAttributeOutput, error)
CreateTags(ctx context.Context, input *ec2.CreateTagsInput, opts ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error)
DeleteTags(ctx context.Context, input *ec2.DeleteTagsInput, opts ...func(*ec2.Options)) (*ec2.DeleteTagsOutput, error)
ModifyVpcTenancy(ctx context.Context, input *ec2.ModifyVpcTenancyInput, opts ...func(*ec2.Options)) (*ec2.ModifyVpcTenancyOutput, error)
}

Expand Down
49 changes: 45 additions & 4 deletions pkg/controller/ec2/subnet/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package subnet

import (
"context"
"sort"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
Expand Down Expand Up @@ -45,13 +46,15 @@ import (

const (
errUnexpectedObject = "The managed resource is not an Subnet resource"
errKubeUpdateFailed = "cannot update Subnet custom resource"

errDescribe = "failed to describe Subnet"
errMultipleItems = "retrieved multiple Subnets"
errCreate = "failed to create the Subnet resource"
errDelete = "failed to delete the Subnet resource"
errUpdate = "failed to update the Subnet resource"
errCreateTags = "failed to create tags for the Subnet resource"
errDeleteTags = "failed to delete tags for the Subnet resource"
)

// SetupSubnet adds a controller that reconciles Subnets.
Expand All @@ -68,7 +71,7 @@ func SetupSubnet(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, p
managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), newClientFn: ec2.NewSubnetClient}),
managed.WithCreationGracePeriod(3*time.Minute),
managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())),
managed.WithInitializers(),
managed.WithInitializers(managed.NewDefaultProviderConfig(mgr.GetClient()), &tagger{kube: mgr.GetClient()}),
managed.WithConnectionPublishers(),
managed.WithPollInterval(poll),
managed.WithLogger(l.WithValues("controller", name)),
Expand Down Expand Up @@ -167,7 +170,7 @@ func (e *external) Create(ctx context.Context, mgd resource.Managed) (managed.Ex
return managed.ExternalCreation{}, nil
}

func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.ExternalUpdate, error) {
func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.ExternalUpdate, error) { // nolint:gocyclo
cr, ok := mgd.(*v1beta1.Subnet)
if !ok {
return managed.ExternalUpdate{}, errors.New(errUnexpectedObject)
Expand All @@ -187,10 +190,20 @@ func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.Ex

subnet := response.Subnets[0]

if !v1beta1.CompareTags(cr.Spec.ForProvider.Tags, subnet.Tags) {
add, remove := awsclient.DiffEC2Tags(v1beta1.GenerateEC2Tags(cr.Spec.ForProvider.Tags), subnet.Tags)
if len(remove) > 0 {
if _, err := e.client.DeleteTags(ctx, &awsec2.DeleteTagsInput{
Resources: []string{meta.GetExternalName(cr)},
Tags: remove,
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errDeleteTags)
}
}

if len(add) > 0 {
if _, err := e.client.CreateTags(ctx, &awsec2.CreateTagsInput{
Resources: []string{meta.GetExternalName(cr)},
Tags: v1beta1.GenerateEC2Tags(cr.Spec.ForProvider.Tags),
Tags: add,
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errCreateTags)
}
Expand Down Expand Up @@ -234,3 +247,31 @@ func (e *external) Delete(ctx context.Context, mgd resource.Managed) error {

return awsclient.Wrap(resource.Ignore(ec2.IsSubnetNotFoundErr, err), errDelete)
}

type tagger struct {
kube client.Client
}

func (t *tagger) Initialize(ctx context.Context, mgd resource.Managed) error {
cr, ok := mgd.(*v1beta1.Subnet)
if !ok {
return errors.New(errUnexpectedObject)
}
tagMap := map[string]string{}
for _, t := range cr.Spec.ForProvider.Tags {
tagMap[t.Key] = t.Value
}
for k, v := range resource.GetExternalTags(mgd) {
tagMap[k] = v
}
cr.Spec.ForProvider.Tags = make([]v1beta1.Tag, len(tagMap))
i := 0
for k, v := range tagMap {
cr.Spec.ForProvider.Tags[i] = v1beta1.Tag{Key: k, Value: v}
i++
}
sort.Slice(cr.Spec.ForProvider.Tags, func(i, j int) bool {
return cr.Spec.ForProvider.Tags[i].Key < cr.Spec.ForProvider.Tags[j].Key
})
return errors.Wrap(t.kube.Update(ctx, cr), errKubeUpdateFailed)
}
43 changes: 34 additions & 9 deletions pkg/controller/ec2/vpc/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
errUpdate = "failed to update VPC resource"
errModifyVPCAttributes = "failed to modify the VPC resource attributes"
errCreateTags = "failed to create tags for the VPC resource"
errDeleteTags = "failed to delete tags for the VPC resource"
errDelete = "failed to delete the VPC resource"
)

Expand Down Expand Up @@ -203,12 +204,26 @@ func (e *external) Create(ctx context.Context, mgd resource.Managed) (managed.Ex
return managed.ExternalCreation{}, nil
}

func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.ExternalUpdate, error) {
func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.ExternalUpdate, error) { // nolint:gocyclo
cr, ok := mgd.(*v1beta1.VPC)
if !ok {
return managed.ExternalUpdate{}, errors.New(errUnexpectedObject)
}

response, err := e.client.DescribeVpcs(ctx, &awsec2.DescribeVpcsInput{
VpcIds: []string{meta.GetExternalName(cr)},
})

if err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(resource.Ignore(ec2.IsSubnetNotFoundErr, err), errDescribe)
}

if response.Vpcs == nil {
return managed.ExternalUpdate{}, errors.New(errUpdate)
}

vpc := response.Vpcs[0]

if cr.Spec.ForProvider.EnableDNSSupport != nil {
modifyInput := &awsec2.ModifyVpcAttributeInput{
VpcId: aws.String(meta.GetExternalName(cr)),
Expand All @@ -229,16 +244,26 @@ func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.Ex
}
}

// NOTE(muvaf): VPCs can only be tagged after the creation and this request
// is idempotent.
if _, err := e.client.CreateTags(ctx, &awsec2.CreateTagsInput{
Resources: []string{meta.GetExternalName(cr)},
Tags: v1beta1.GenerateEC2Tags(cr.Spec.ForProvider.Tags),
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errCreateTags)
add, remove := awsclient.DiffEC2Tags(v1beta1.GenerateEC2Tags(cr.Spec.ForProvider.Tags), vpc.Tags)
if len(remove) > 0 {
if _, err := e.client.DeleteTags(ctx, &awsec2.DeleteTagsInput{
Resources: []string{meta.GetExternalName(cr)},
Tags: remove,
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errDeleteTags)
}
}

if len(add) > 0 {
if _, err := e.client.CreateTags(ctx, &awsec2.CreateTagsInput{
Resources: []string{meta.GetExternalName(cr)},
Tags: add,
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errCreateTags)
}
}

_, err := e.client.ModifyVpcTenancy(ctx, &awsec2.ModifyVpcTenancyInput{
_, err = e.client.ModifyVpcTenancy(ctx, &awsec2.ModifyVpcTenancyInput{
InstanceTenancy: awsec2types.VpcTenancy(aws.ToString(cr.Spec.ForProvider.InstanceTenancy)),
VpcId: aws.String(meta.GetExternalName(cr)),
})
Expand Down
14 changes: 14 additions & 0 deletions pkg/controller/ec2/vpc/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,13 @@ func TestUpdate(t *testing.T) {
"Successful": {
args: args{
vpc: &fake.MockVPCClient{
MockDescribe: func(ctx context.Context, input *awsec2.DescribeVpcsInput, opts []func(*awsec2.Options)) (*awsec2.DescribeVpcsOutput, error) {
return &awsec2.DescribeVpcsOutput{
Vpcs: []awsec2types.Vpc{{
VpcId: aws.String(vpcID),
}},
}, nil
},
MockModifyTenancy: func(ctx context.Context, input *awsec2.ModifyVpcTenancyInput, opts []func(*awsec2.Options)) (*awsec2.ModifyVpcTenancyOutput, error) {
return &awsec2.ModifyVpcTenancyOutput{}, nil
},
Expand All @@ -346,6 +353,13 @@ func TestUpdate(t *testing.T) {
"ModifyFailed": {
args: args{
vpc: &fake.MockVPCClient{
MockDescribe: func(ctx context.Context, input *awsec2.DescribeVpcsInput, opts []func(*awsec2.Options)) (*awsec2.DescribeVpcsOutput, error) {
return &awsec2.DescribeVpcsOutput{
Vpcs: []awsec2types.Vpc{{
VpcId: aws.String(vpcID),
}},
}, nil
},
MockModifyTenancy: func(ctx context.Context, input *awsec2.ModifyVpcTenancyInput, opts []func(*awsec2.Options)) (*awsec2.ModifyVpcTenancyOutput, error) {
return nil, errBoom
},
Expand Down

0 comments on commit dd6a918

Please sign in to comment.