From c60a2fcc2e825a8e03557e70d0ef8c9b5390b46c Mon Sep 17 00:00:00 2001 From: James Kwon <96548424+hongil0316@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:35:20 -0400 Subject: [PATCH] refactor elbv2 (#540) --- aws/aws.go | 2 +- aws/eks_utils_for_test.go | 46 +++++++ aws/elbv2.go | 50 +++---- aws/elbv2_test.go | 271 +++++++++++--------------------------- aws/elbv2_types.go | 2 +- 5 files changed, 137 insertions(+), 234 deletions(-) diff --git a/aws/aws.go b/aws/aws.go index c1b4aab3..8017f07b 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -428,7 +428,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp } if IsNukeable(loadBalancersV2.ResourceName(), resourceTypes) { start := time.Now() - elbv2Arns, err := getAllElbv2Instances(cloudNukeSession, region, excludeAfter, configObj) + elbv2Arns, err := loadBalancersV2.getAll(configObj) if err != nil { ge := report.GeneralError{ Error: err, diff --git a/aws/eks_utils_for_test.go b/aws/eks_utils_for_test.go index 6156cf45..e433ffd2 100644 --- a/aws/eks_utils_for_test.go +++ b/aws/eks_utils_for_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "github.com/gruntwork-io/go-commons/collections" "testing" "time" @@ -298,3 +299,48 @@ const eksNodeGroupAssumeRolePolicy = `{ } ] }` + +func getSubnetsInDifferentAZs(t *testing.T, session *session.Session) (*ec2.Subnet, *ec2.Subnet) { + subnetOutput, err := ec2.New(session).DescribeSubnets(&ec2.DescribeSubnetsInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: awsgo.String("default-for-az"), + Values: []*string{awsgo.String("true")}, + }, + }, + }) + require.NoError(t, err) + require.True(t, len(subnetOutput.Subnets) >= 2) + + subnet1Idx := -1 + for idx, subnet := range subnetOutput.Subnets { + if !collections.ListContainsElement(AvailabilityZoneBlackList, awsgo.StringValue(subnet.AvailabilityZone)) { + subnet1Idx = idx + break + } + } + if subnet1Idx == -1 { + require.Fail(t, "Unable to find a subnet in an availability zone that is not blacklisted.") + } + subnet1 := subnetOutput.Subnets[subnet1Idx] + az1 := awsgo.StringValue(subnet1.AvailabilityZone) + subnet1Id := awsgo.StringValue(subnet1.SubnetId) + subnet1VpcId := awsgo.StringValue(subnet1.VpcId) + + for i := subnet1Idx + 1; i < len(subnetOutput.Subnets); i++ { + subnet2 := subnetOutput.Subnets[i] + az2 := awsgo.StringValue(subnet2.AvailabilityZone) + if collections.ListContainsElement(AvailabilityZoneBlackList, az2) { + // Skip because subnet is in a blacklisted AZ + continue + } + subnet2Id := awsgo.StringValue(subnet2.SubnetId) + subnet2VpcId := awsgo.StringValue(subnet2.VpcId) + if az1 != az2 && subnet1Id != subnet2Id && subnet1VpcId == subnet2VpcId { + return subnet1, subnet2 + } + } + + require.Fail(t, "Unable to find 2 subnets in different Availability Zones") + return nil, nil +} diff --git a/aws/elbv2.go b/aws/elbv2.go index 3737e3dd..97b3674f 100644 --- a/aws/elbv2.go +++ b/aws/elbv2.go @@ -1,31 +1,29 @@ package aws import ( - "github.com/gruntwork-io/cloud-nuke/telemetry" - commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" - "time" - "github.com/aws/aws-sdk-go/aws" - awsgo "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/elbv2" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" + "github.com/gruntwork-io/cloud-nuke/telemetry" "github.com/gruntwork-io/go-commons/errors" + commonTelemetry "github.com/gruntwork-io/go-commons/telemetry" ) // Returns a formatted string of ELBv2 Arns -func getAllElbv2Instances(session *session.Session, region string, excludeAfter time.Time, configObj config.Config) ([]*string, error) { - svc := elbv2.New(session) - result, err := svc.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{}) +func (balancer LoadBalancersV2) getAll(configObj config.Config) ([]*string, error) { + result, err := balancer.Client.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{}) if err != nil { return nil, errors.WithStackTrace(err) } var arns []*string for _, balancer := range result.LoadBalancers { - if shouldIncludeELBv2(balancer, excludeAfter, configObj) { + if configObj.ELBv2.ShouldInclude(config.ResourceValue{ + Name: balancer.LoadBalancerName, + Time: balancer.CreatedTime, + }) { arns = append(arns, balancer.LoadBalancerArn) } } @@ -33,32 +31,14 @@ func getAllElbv2Instances(session *session.Session, region string, excludeAfter return arns, nil } -func shouldIncludeELBv2(balancer *elbv2.LoadBalancer, excludeAfter time.Time, configObj config.Config) bool { - if balancer == nil { - return false - } - - if balancer.CreatedTime != nil && excludeAfter.Before(*balancer.CreatedTime) { - return false - } - - return config.ShouldInclude( - awsgo.StringValue(balancer.LoadBalancerName), - configObj.ELBv2.IncludeRule.NamesRegExp, - configObj.ELBv2.ExcludeRule.NamesRegExp, - ) -} - // Deletes all Elastic Load Balancers -func nukeAllElbv2Instances(session *session.Session, arns []*string) error { - svc := elbv2.New(session) - +func (balancer LoadBalancersV2) nukeAll(arns []*string) error { if len(arns) == 0 { - logging.Logger.Debugf("No V2 Elastic Load Balancers to nuke in region %s", *session.Config.Region) + logging.Logger.Debugf("No V2 Elastic Load Balancers to nuke in region %s", balancer.Region) return nil } - logging.Logger.Debugf("Deleting all V2 Elastic Load Balancers in region %s", *session.Config.Region) + logging.Logger.Debugf("Deleting all V2 Elastic Load Balancers in region %s", balancer.Region) var deletedArns []*string for _, arn := range arns { @@ -66,7 +46,7 @@ func nukeAllElbv2Instances(session *session.Session, arns []*string) error { LoadBalancerArn: arn, } - _, err := svc.DeleteLoadBalancer(params) + _, err := balancer.Client.DeleteLoadBalancer(params) // Record status of this resource e := report.Entry{ @@ -81,7 +61,7 @@ func nukeAllElbv2Instances(session *session.Session, arns []*string) error { telemetry.TrackEvent(commonTelemetry.EventContext{ EventName: "Error Nuking Load Balancer V2", }, map[string]interface{}{ - "region": *session.Config.Region, + "region": balancer.Region, }) } else { deletedArns = append(deletedArns, arn) @@ -90,7 +70,7 @@ func nukeAllElbv2Instances(session *session.Session, arns []*string) error { } if len(deletedArns) > 0 { - err := svc.WaitUntilLoadBalancersDeleted(&elbv2.DescribeLoadBalancersInput{ + err := balancer.Client.WaitUntilLoadBalancersDeleted(&elbv2.DescribeLoadBalancersInput{ LoadBalancerArns: deletedArns, }) if err != nil { @@ -99,6 +79,6 @@ func nukeAllElbv2Instances(session *session.Session, arns []*string) error { } } - logging.Logger.Debugf("[OK] %d V2 Elastic Load Balancer(s) deleted in %s", len(deletedArns), *session.Config.Region) + logging.Logger.Debugf("[OK] %d V2 Elastic Load Balancer(s) deleted in %s", len(deletedArns), balancer.Region) return nil } diff --git a/aws/elbv2_test.go b/aws/elbv2_test.go index 4fdcd89d..01a15b05 100644 --- a/aws/elbv2_test.go +++ b/aws/elbv2_test.go @@ -1,233 +1,110 @@ package aws import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface" + "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/telemetry" + "github.com/stretchr/testify/require" "regexp" "testing" "time" - - awsgo "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/gruntwork-io/cloud-nuke/config" - "github.com/gruntwork-io/cloud-nuke/logging" - "github.com/gruntwork-io/cloud-nuke/util" - "github.com/gruntwork-io/go-commons/collections" - "github.com/gruntwork-io/go-commons/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func getSubnetsInDifferentAZs(t *testing.T, session *session.Session) (*ec2.Subnet, *ec2.Subnet) { - subnetOutput, err := ec2.New(session).DescribeSubnets(&ec2.DescribeSubnetsInput{ - Filters: []*ec2.Filter{ - &ec2.Filter{ - Name: awsgo.String("default-for-az"), - Values: []*string{awsgo.String("true")}, - }, - }, - }) - require.NoError(t, err) - require.True(t, len(subnetOutput.Subnets) >= 2) - - subnet1Idx := -1 - for idx, subnet := range subnetOutput.Subnets { - if !collections.ListContainsElement(AvailabilityZoneBlackList, awsgo.StringValue(subnet.AvailabilityZone)) { - subnet1Idx = idx - break - } - } - if subnet1Idx == -1 { - require.Fail(t, "Unable to find a subnet in an availability zone that is not blacklisted.") - } - subnet1 := subnetOutput.Subnets[subnet1Idx] - az1 := awsgo.StringValue(subnet1.AvailabilityZone) - subnet1Id := awsgo.StringValue(subnet1.SubnetId) - subnet1VpcId := awsgo.StringValue(subnet1.VpcId) - - for i := subnet1Idx + 1; i < len(subnetOutput.Subnets); i++ { - subnet2 := subnetOutput.Subnets[i] - az2 := awsgo.StringValue(subnet2.AvailabilityZone) - if collections.ListContainsElement(AvailabilityZoneBlackList, az2) { - // Skip because subnet is in a blacklisted AZ - continue - } - subnet2Id := awsgo.StringValue(subnet2.SubnetId) - subnet2VpcId := awsgo.StringValue(subnet2.VpcId) - if az1 != az2 && subnet1Id != subnet2Id && subnet1VpcId == subnet2VpcId { - return subnet1, subnet2 - } - } - - require.Fail(t, "Unable to find 2 subnets in different Availability Zones") - return nil, nil +type mockedElbV2 struct { + elbv2iface.ELBV2API + DescribeLoadBalancersOutput elbv2.DescribeLoadBalancersOutput + DeleteLoadBalancerOutput elbv2.DeleteLoadBalancerOutput } -func createTestELBv2(t *testing.T, session *session.Session, name string) elbv2.LoadBalancer { - svc := elbv2.New(session) - - subnet1, subnet2 := getSubnetsInDifferentAZs(t, session) - - param := &elbv2.CreateLoadBalancerInput{ - Name: awsgo.String(name), - Subnets: []*string{ - subnet1.SubnetId, - subnet2.SubnetId, - }, - } - - result, err := svc.CreateLoadBalancer(param) - require.NoError(t, err) - require.True(t, len(result.LoadBalancers) > 0, "Could not create test ELBv2") - - balancer := *result.LoadBalancers[0] - - err = svc.WaitUntilLoadBalancerAvailable(&elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: []*string{balancer.LoadBalancerArn}, - }) - require.NoError(t, err) - - return balancer +func (m mockedElbV2) DescribeLoadBalancers(input *elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) { + return &m.DescribeLoadBalancersOutput, nil } -func TestListELBv2(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String(region)}, - ) - require.NoError(t, err) - - elbName := "cloud-nuke-test-" + util.UniqueID() - balancer := createTestELBv2(t, session, elbName) - // clean up after this test - defer nukeAllElbv2Instances(session, []*string{balancer.LoadBalancerArn}) - - arns, err := getAllElbv2Instances(session, region, time.Now().Add(1*time.Hour*-1), config.Config{}) - require.NoError(t, err) - - assert.NotContains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) - - arns, err = getAllElbv2Instances(session, region, time.Now().Add(1*time.Hour), config.Config{}) - require.NoError(t, err) - - assert.Contains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) +func (m mockedElbV2) DeleteLoadBalancer(input *elbv2.DeleteLoadBalancerInput) (*elbv2.DeleteLoadBalancerOutput, error) { + return &m.DeleteLoadBalancerOutput, nil } -func TestNukeELBv2(t *testing.T) { - telemetry.InitTelemetry("cloud-nuke", "") - t.Parallel() - - region, err := getRandomRegion() - if err != nil { - assert.Fail(t, errors.WithStackTrace(err).Error()) - } - - session, err := session.NewSession(&awsgo.Config{ - Region: awsgo.String(region)}, - ) - require.NoError(t, err) - - svc := elbv2.New(session) - - elbName := "cloud-nuke-test-" + util.UniqueID() - balancer := createTestELBv2(t, session, elbName) - - _, err = svc.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: []*string{ - balancer.LoadBalancerArn, - }, - }) - require.NoError(t, err) - - err = nukeAllElbv2Instances(session, []*string{balancer.LoadBalancerArn}) - require.NoError(t, err) - - err = svc.WaitUntilLoadBalancersDeleted(&elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: []*string{balancer.LoadBalancerArn}, - }) - require.NoError(t, err) - - arns, err := getAllElbv2Instances(session, region, time.Now().Add(1*time.Hour), config.Config{}) - require.NoError(t, err) - - assert.NotContains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(balancer.LoadBalancerArn)) +func (m mockedElbV2) WaitUntilLoadBalancersDeleted(input *elbv2.DescribeLoadBalancersInput) error { + return nil } -// Test config file filtering works as expected -func TestShouldIncludeELBv2(t *testing.T) { +func TestElbV2_GetAll(t *testing.T) { telemetry.InitTelemetry("cloud-nuke", "") - mockELBv2 := &elbv2.LoadBalancer{ - LoadBalancerName: awsgo.String("cloud-nuke-test"), - CreatedTime: awsgo.Time(time.Now()), - } - - mockExpression, err := regexp.Compile("^cloud-nuke-*") - if err != nil { - logging.Logger.Fatalf("There was an error compiling regex expression %v", err) - } + t.Parallel() - mockExcludeConfig := config.Config{ - ELBv2: config.ResourceType{ - ExcludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{ + testName1 := "test-name-1" + testArn1 := "test-arn-1" + testName2 := "test-name-2" + testArn2 := "test-arn-2" + now := time.Now() + balancer := LoadBalancersV2{ + Client: mockedElbV2{ + DescribeLoadBalancersOutput: elbv2.DescribeLoadBalancersOutput{ + LoadBalancers: []*elbv2.LoadBalancer{ { - RE: *mockExpression, + LoadBalancerArn: aws.String(testArn1), + LoadBalancerName: aws.String(testName1), + CreatedTime: aws.Time(now), }, - }, - }, - }, - } - - mockIncludeConfig := config.Config{ - ELBv2: config.ResourceType{ - IncludeRule: config.FilterRule{ - NamesRegExp: []config.Expression{ { - RE: *mockExpression, + LoadBalancerArn: aws.String(testArn2), + LoadBalancerName: aws.String(testName2), + CreatedTime: aws.Time(now.Add(1)), }, }, }, }, } - cases := []struct { - Name string - ELBv2 *elbv2.LoadBalancer - Config config.Config - ExcludeAfter time.Time - Expected bool + tests := map[string]struct { + configObj config.ResourceType + expected []string }{ - { - Name: "ConfigExclude", - ELBv2: mockELBv2, - Config: mockExcludeConfig, - ExcludeAfter: time.Now().Add(1 * time.Hour), - Expected: false, + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testArn1, testArn2}, }, - { - Name: "ConfigInclude", - ELBv2: mockELBv2, - Config: mockIncludeConfig, - ExcludeAfter: time.Now().Add(1 * time.Hour), - Expected: true, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testArn2}, }, - { - Name: "NotOlderThan", - ELBv2: mockELBv2, - Config: config.Config{}, - ExcludeAfter: time.Now().Add(1 * time.Hour * -1), - Expected: false, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now), + }}, + expected: []string{testArn1}, }, } - - for _, c := range cases { - t.Run(c.Name, func(t *testing.T) { - result := shouldIncludeELBv2(c.ELBv2, c.ExcludeAfter, c.Config) - assert.Equal(t, c.Expected, result) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := balancer.getAll(config.Config{ + ELBv2: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) }) } + +} + +func TestElbV2_NukeAll(t *testing.T) { + telemetry.InitTelemetry("cloud-nuke", "") + t.Parallel() + + balancer := LoadBalancersV2{ + Client: mockedElbV2{ + DeleteLoadBalancerOutput: elbv2.DeleteLoadBalancerOutput{}, + }, + } + + err := balancer.nukeAll([]*string{aws.String("test-arn-1"), aws.String("test-arn-2")}) + require.NoError(t, err) } diff --git a/aws/elbv2_types.go b/aws/elbv2_types.go index 19cd98b5..d4208a80 100644 --- a/aws/elbv2_types.go +++ b/aws/elbv2_types.go @@ -31,7 +31,7 @@ func (balancer LoadBalancersV2) ResourceIdentifiers() []string { // Nuke - nuke 'em all!!! func (balancer LoadBalancersV2) Nuke(session *session.Session, identifiers []string) error { - if err := nukeAllElbv2Instances(session, awsgo.StringSlice(identifiers)); err != nil { + if err := balancer.nukeAll(awsgo.StringSlice(identifiers)); err != nil { return errors.WithStackTrace(err) }