From 2fd44181e3b069f56a7ae3a74fd6466932f93e01 Mon Sep 17 00:00:00 2001 From: James Kwon <96548424+hongil0316@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:07:03 -0400 Subject: [PATCH 1/3] fix: transit gateway nuke failure --- aws/resources/transit_gateway.go | 137 ++++++++++++++++++++++++-- aws/resources/transit_gateway_test.go | 13 +++ 2 files changed, 142 insertions(+), 8 deletions(-) diff --git a/aws/resources/transit_gateway.go b/aws/resources/transit_gateway.go index f93152b9..fac72c3f 100644 --- a/aws/resources/transit_gateway.go +++ b/aws/resources/transit_gateway.go @@ -3,6 +3,7 @@ package resources import ( "context" "time" + "fmt" "github.com/aws/aws-sdk-go/aws" awsgo "github.com/aws/aws-sdk-go/aws" @@ -15,6 +16,10 @@ import ( "github.com/gruntwork-io/go-commons/errors" ) +const ( + TransitGatewayAttachmentTypePeering = "peering" +) + // [Note 1] : NOTE on the Apporach used:-Using the `dry run` approach on verifying the nuking permission in case of a scoped IAM role. // IAM:simulateCustomPolicy : could also be used but the IAM role itself needs permission for simulateCustomPolicy method //else this would not get the desired result. Also in case of multiple t-gateway, if only some has permssion to be nuked, @@ -33,7 +38,12 @@ func (tgw *TransitGateways) getAll(c context.Context, configObj config.Config) ( currentOwner := c.Value(util.AccountIdKey) var ids []*string for _, transitGateway := range result.TransitGateways { - if configObj.TransitGateway.ShouldInclude(config.ResourceValue{Time: transitGateway.CreationTime}) && + hostNameTagValue := util.GetEC2ResourceNameTagValue(transitGateway.Tags) + + if configObj.TransitGateway.ShouldInclude(config.ResourceValue{ + Time: transitGateway.CreationTime, + Name: hostNameTagValue, + }) && awsgo.StringValue(transitGateway.State) != "deleted" && awsgo.StringValue(transitGateway.State) != "deleting" { ids = append(ids, transitGateway.TransitGatewayId) } @@ -60,6 +70,122 @@ func (tgw *TransitGateways) getAll(c context.Context, configObj config.Config) ( return ids, nil } +func (tgw *TransitGateways) nuke(id *string) error { + // check the transit gateway has attachments and nuke them before + if err := tgw.nukeAttachments(id); err != nil { + return errors.WithStackTrace(err) + } + + if _, err := tgw.Client.DeleteTransitGatewayWithContext(tgw.Context, &ec2.DeleteTransitGatewayInput{ + TransitGatewayId: id, + }); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} + +func (tgw *TransitGateways) nukeAttachments(id *string) error { + logging.Debugf("nuking transit gateway attachments for %v", awsgo.StringValue(id)) + output, err := tgw.Client.DescribeTransitGatewayAttachmentsWithContext(tgw.Context,&ec2.DescribeTransitGatewayAttachmentsInput{ + Filters: []*ec2.Filter{ + { + Name: awsgo.String("transit-gateway-id"), + Values: []*string{ + id, + }, + }, + { + Name: awsgo.String("state"), + Values: []*string{ + awsgo.String("available"), + }, + }, + }, + }) + if err != nil { + logging.Errorf("[Failed] unable to describe the transit gateway attachments for %v : %s", awsgo.StringValue(id), err) + return errors.WithStackTrace(err) + } + + logging.Debugf("%v attachment(s) found with %v", len(output.TransitGatewayAttachments),awsgo.StringValue(id)) + + for _, attachments := range output.TransitGatewayAttachments { + var ( + err error + attachmentType = awsgo.StringValue(attachments.ResourceType) + now = time.Now() + ) + + switch attachmentType { + case TransitGatewayAttachmentTypePeering: + logging.Debugf("[Execution] deleting the attachments of type %v for %v ", attachmentType, awsgo.StringValue(id)) + _, err = tgw.Client.DeleteTransitGatewayPeeringAttachmentWithContext(context.Background(), &ec2.DeleteTransitGatewayPeeringAttachmentInput{ + TransitGatewayAttachmentId: attachments.TransitGatewayAttachmentId, + }) + default: + err = fmt.Errorf("%v typed transit gateway attachment nuking not handled.", attachmentType) + } + if err != nil { + logging.Errorf("[Failed] unable to delete the transit gateway peernig attachment for %v : %s", awsgo.StringValue(id), err) + return err + } + + err = tgw.WaitUntilTransitGatewayAttachmentDeleted(id, attachmentType) + if err != nil { + logging.Errorf("[Failed] unable to wait until nuking the transit gateway attachment with type %v for %v : %s", attachmentType,awsgo.StringValue(id), err) + return err + } + + logging.Debugf("waited %v to nuke the attachment", time.Since(now)) + } + + logging.Debugf("[Ok] successfully nuked all the attachments on %v", awsgo.StringValue(id)) + return nil +} + +func (tgw *TransitGateways) WaitUntilTransitGatewayAttachmentDeleted(id *string, attachmentType string) error { + timeoutCtx, cancel := context.WithTimeout(tgw.Context, 5*time.Minute) + defer cancel() + + ticker := time.NewTicker(3 * time.Second) + defer ticker.Stop() + + for { + select { + case <-timeoutCtx.Done(): + return fmt.Errorf("transit gateway attachments deletion check timed out after 5 minute") + case <-ticker.C: + output, err := tgw.Client.DescribeTransitGatewayAttachmentsWithContext(tgw.Context,&ec2.DescribeTransitGatewayAttachmentsInput{ + Filters: []*ec2.Filter{ + { + Name: awsgo.String("transit-gateway-id"), + Values: []*string{ + id, + }, + }, + { + Name: awsgo.String("state"), + Values: []*string{ + awsgo.String("available"), + awsgo.String("deleting"), + }, + }, + }, + }) + if err != nil { + logging.Debugf("transit gateway attachment(s) as type %v existance checking error : %v", attachmentType,err) + return err + } + + if len(output.TransitGatewayAttachments) == 0 { + return nil + } + logging.Debugf("%v transit gateway attachments(s) still exists as type %v, waiting...", len(output.TransitGatewayAttachments), attachmentType) + } + } +} + // Delete all TransitGateways // it attempts to nuke only those resources for which the current IAM user has permission func (tgw *TransitGateways) nukeAll(ids []*string) error { @@ -78,13 +204,8 @@ func (tgw *TransitGateways) nukeAll(ids []*string) error { logging.Debugf("[Skipping] %s nuke because %v", *id, reason) continue } - - params := &ec2.DeleteTransitGatewayInput{ - TransitGatewayId: id, - } - - _, err := tgw.Client.DeleteTransitGatewayWithContext(tgw.Context, params) - + err := tgw.nuke(id) + // Record status of this resource e := report.Entry{ Identifier: aws.StringValue(id), diff --git a/aws/resources/transit_gateway_test.go b/aws/resources/transit_gateway_test.go index ba4039d3..f55f8cf3 100644 --- a/aws/resources/transit_gateway_test.go +++ b/aws/resources/transit_gateway_test.go @@ -18,6 +18,8 @@ type mockedTransitGateway struct { ec2iface.EC2API DescribeTransitGatewaysOutput ec2.DescribeTransitGatewaysOutput DeleteTransitGatewayOutput ec2.DeleteTransitGatewayOutput + DescribeTransitGatewayAttachmentsOutput ec2.DescribeTransitGatewayAttachmentsOutput + DeleteTransitGatewayPeeringAttachmentOutput ec2.DeleteTransitGatewayPeeringAttachmentOutput } type mockedTransitGatewayRouteTable struct { @@ -55,6 +57,17 @@ func (m mockedTransitGateway) DeleteTransitGatewayWithContext(_ awsgo.Context, _ return &m.DeleteTransitGatewayOutput, nil } +func (m mockedTransitGateway) DescribeTransitGatewayAttachmentsWithContext(awsgo.Context, *ec2.DescribeTransitGatewayAttachmentsInput, ...request.Option) (*ec2.DescribeTransitGatewayAttachmentsOutput, error){ + return &m.DescribeTransitGatewayAttachmentsOutput, nil +} + +func (m mockedTransitGateway) DeleteTransitGatewayPeeringAttachmentWithContext(aws.Context, *ec2.DeleteTransitGatewayPeeringAttachmentInput, ...request.Option) (*ec2.DeleteTransitGatewayPeeringAttachmentOutput, error){ + return &m.DeleteTransitGatewayPeeringAttachmentOutput, nil +} +func (m mockedTransitGateway) WaitUntilTransitGatewayAttachmentDeleted(*string, string) error { + return nil +} + func (m mockedTransitGatewayRouteTable) DescribeTransitGatewayRouteTablesWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayRouteTablesInput, _ ...request.Option) (*ec2.DescribeTransitGatewayRouteTablesOutput, error) { return &m.DescribeTransitGatewayRouteTablesOutput, nil } From 88d582a760a194e9f0c5ce934e36b0ba8fe8a448 Mon Sep 17 00:00:00 2001 From: James Kwon <96548424+hongil0316@users.noreply.github.com> Date: Thu, 25 Jul 2024 22:28:10 -0400 Subject: [PATCH 2/3] fix: transit gateway nuke failure --- aws/resources/transit_gateway.go | 195 +-------------------- aws/resources/transit_gateway_test.go | 243 -------------------------- 2 files changed, 5 insertions(+), 433 deletions(-) diff --git a/aws/resources/transit_gateway.go b/aws/resources/transit_gateway.go index fac72c3f..59e94452 100644 --- a/aws/resources/transit_gateway.go +++ b/aws/resources/transit_gateway.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go/aws" awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - goerror "github.com/go-errors/errors" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/cloud-nuke/report" @@ -120,21 +119,20 @@ func (tgw *TransitGateways) nukeAttachments(id *string) error { switch attachmentType { case TransitGatewayAttachmentTypePeering: logging.Debugf("[Execution] deleting the attachments of type %v for %v ", attachmentType, awsgo.StringValue(id)) - _, err = tgw.Client.DeleteTransitGatewayPeeringAttachmentWithContext(context.Background(), &ec2.DeleteTransitGatewayPeeringAttachmentInput{ + _, err = tgw.Client.DeleteTransitGatewayPeeringAttachmentWithContext(tgw.Context, &ec2.DeleteTransitGatewayPeeringAttachmentInput{ TransitGatewayAttachmentId: attachments.TransitGatewayAttachmentId, }) default: - err = fmt.Errorf("%v typed transit gateway attachment nuking not handled.", attachmentType) + err = fmt.Errorf("%v typed transit gateway attachment nuking not handled", attachmentType) } if err != nil { logging.Errorf("[Failed] unable to delete the transit gateway peernig attachment for %v : %s", awsgo.StringValue(id), err) return err } - err = tgw.WaitUntilTransitGatewayAttachmentDeleted(id, attachmentType) - if err != nil { + if err := tgw.WaitUntilTransitGatewayAttachmentDeleted(id, attachmentType); err != nil { logging.Errorf("[Failed] unable to wait until nuking the transit gateway attachment with type %v for %v : %s", attachmentType,awsgo.StringValue(id), err) - return err + return errors.WithStackTrace(err) } logging.Debugf("waited %v to nuke the attachment", time.Since(now)) @@ -175,7 +173,7 @@ func (tgw *TransitGateways) WaitUntilTransitGatewayAttachmentDeleted(id *string, }) if err != nil { logging.Debugf("transit gateway attachment(s) as type %v existance checking error : %v", attachmentType,err) - return err + return errors.WithStackTrace(err) } if len(output.TransitGatewayAttachments) == 0 { @@ -225,186 +223,3 @@ func (tgw *TransitGateways) nukeAll(ids []*string) error { logging.Debugf("[OK] %d Transit Gateway(s) deleted in %s", len(deletedIds), tgw.Region) return nil } - -// Returns a formatted string of TranstGatewayRouteTable IDs -func (tgw *TransitGatewaysRouteTables) getAll(c context.Context, configObj config.Config) ([]*string, error) { - // Remove defalt route table, that will be deleted along with its TransitGateway - param := &ec2.DescribeTransitGatewayRouteTablesInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("default-association-route-table"), - Values: []*string{ - aws.String("false"), - }, - }, - }, - } - - result, err := tgw.Client.DescribeTransitGatewayRouteTablesWithContext(tgw.Context, param) - if err != nil { - return nil, errors.WithStackTrace(err) - } - - var ids []*string - for _, transitGatewayRouteTable := range result.TransitGatewayRouteTables { - if configObj.TransitGatewayRouteTable.ShouldInclude(config.ResourceValue{Time: transitGatewayRouteTable.CreationTime}) && - awsgo.StringValue(transitGatewayRouteTable.State) != "deleted" && awsgo.StringValue(transitGatewayRouteTable.State) != "deleting" { - ids = append(ids, transitGatewayRouteTable.TransitGatewayRouteTableId) - } - } - - return ids, nil -} - -// Delete all TransitGatewayRouteTables -func (tgw *TransitGatewaysRouteTables) nukeAll(ids []*string) error { - if len(ids) == 0 { - logging.Debugf("No Transit Gateway Route Tables to nuke in region %s", tgw.Region) - return nil - } - - logging.Debugf("Deleting all Transit Gateway Route Tables in region %s", tgw.Region) - var deletedIds []*string - - for _, id := range ids { - param := &ec2.DeleteTransitGatewayRouteTableInput{ - TransitGatewayRouteTableId: id, - } - - _, err := tgw.Client.DeleteTransitGatewayRouteTableWithContext(tgw.Context, param) - if err != nil { - logging.Debugf("[Failed] %s", err) - } else { - deletedIds = append(deletedIds, id) - logging.Debugf("Deleted Transit Gateway Route Table: %s", *id) - } - } - - logging.Debugf("[OK] %d Transit Gateway Route Table(s) deleted in %s", len(deletedIds), tgw.Region) - return nil -} - -// Returns a formated string of TransitGatewayVpcAttachment IDs -func (tgw *TransitGatewaysVpcAttachment) getAll(c context.Context, configObj config.Config) ([]*string, error) { - result, err := tgw.Client.DescribeTransitGatewayVpcAttachmentsWithContext(tgw.Context, &ec2.DescribeTransitGatewayVpcAttachmentsInput{}) - if err != nil { - return nil, errors.WithStackTrace(err) - } - - var ids []*string - for _, tgwVpcAttachment := range result.TransitGatewayVpcAttachments { - if configObj.TransitGatewaysVpcAttachment.ShouldInclude(config.ResourceValue{Time: tgwVpcAttachment.CreationTime}) && - awsgo.StringValue(tgwVpcAttachment.State) != "deleted" && awsgo.StringValue(tgwVpcAttachment.State) != "deleting" { - ids = append(ids, tgwVpcAttachment.TransitGatewayAttachmentId) - } - } - - return ids, nil -} - -// Delete all TransitGatewayVpcAttachments -func (tgw *TransitGatewaysVpcAttachment) nukeAll(ids []*string) error { - if len(ids) == 0 { - logging.Debugf("No Transit Gateway Vpc Attachments to nuke in region %s", tgw.Region) - return nil - } - - logging.Debugf("Deleting all Transit Gateway Vpc Attachments in region %s", tgw.Region) - var deletedIds []*string - - for _, id := range ids { - param := &ec2.DeleteTransitGatewayVpcAttachmentInput{ - TransitGatewayAttachmentId: id, - } - - _, err := tgw.Client.DeleteTransitGatewayVpcAttachmentWithContext(tgw.Context, param) - - // Record status of this resource - e := report.Entry{ - Identifier: aws.StringValue(id), - ResourceType: "Transit Gateway", - Error: err, - } - report.Record(e) - - if err != nil { - logging.Debugf("[Failed] %s", err) - } else { - deletedIds = append(deletedIds, id) - logging.Debugf("Deleted Transit Gateway Vpc Attachment: %s", *id) - } - } - - if waiterr := waitForTransitGatewayAttachementToBeDeleted(*tgw); waiterr != nil { - return errors.WithStackTrace(waiterr) - } - logging.Debugf(("[OK] %d Transit Gateway Vpc Attachment(s) deleted in %s"), len(deletedIds), tgw.Region) - return nil -} - -func (tgpa *TransitGatewayPeeringAttachment) getAll(c context.Context, configObj config.Config) ([]*string, error) { - var ids []*string - err := tgpa.Client.DescribeTransitGatewayPeeringAttachmentsPagesWithContext(tgpa.Context, &ec2.DescribeTransitGatewayPeeringAttachmentsInput{}, func(result *ec2.DescribeTransitGatewayPeeringAttachmentsOutput, lastPage bool) bool { - for _, attachment := range result.TransitGatewayPeeringAttachments { - if configObj.TransitGatewayPeeringAttachment.ShouldInclude(config.ResourceValue{ - Time: attachment.CreationTime, - }) { - ids = append(ids, attachment.TransitGatewayAttachmentId) - } - } - - return !lastPage - }) - if err != nil { - return nil, errors.WithStackTrace(err) - } - - return ids, nil -} - -func (tgpa *TransitGatewayPeeringAttachment) nukeAll(ids []*string) error { - for _, id := range ids { - _, err := tgpa.Client.DeleteTransitGatewayPeeringAttachmentWithContext(tgpa.Context, &ec2.DeleteTransitGatewayPeeringAttachmentInput{ - TransitGatewayAttachmentId: id, - }) - // Record status of this resource - report.Record(report.Entry{ - Identifier: aws.StringValue(id), - ResourceType: tgpa.ResourceName(), - Error: err, - }) - if err != nil { - logging.Errorf("[Failed] %s", err) - } else { - logging.Debugf("Deleted Transit Gateway Peering Attachment: %s", *id) - } - } - - return nil -} - -func waitForTransitGatewayAttachementToBeDeleted(tgw TransitGatewaysVpcAttachment) error { - for i := 0; i < 30; i++ { - gateways, err := tgw.Client.DescribeTransitGatewayVpcAttachments( - &ec2.DescribeTransitGatewayVpcAttachmentsInput{ - TransitGatewayAttachmentIds: aws.StringSlice(tgw.Ids), - Filters: []*ec2.Filter{ - { - Name: awsgo.String("state"), - Values: []*string{awsgo.String("deleting")}, - }, - }, - }, - ) - if err != nil { - return err - } - if len(gateways.TransitGatewayVpcAttachments) == 0 { - return nil - } - logging.Info("Waiting for transit gateways attachemensts to be deleted...") - time.Sleep(10 * time.Second) - } - - return goerror.New("timed out waiting for transit gateway attahcments to be successfully deleted") -} diff --git a/aws/resources/transit_gateway_test.go b/aws/resources/transit_gateway_test.go index f55f8cf3..38fdc770 100644 --- a/aws/resources/transit_gateway_test.go +++ b/aws/resources/transit_gateway_test.go @@ -22,33 +22,6 @@ type mockedTransitGateway struct { DeleteTransitGatewayPeeringAttachmentOutput ec2.DeleteTransitGatewayPeeringAttachmentOutput } -type mockedTransitGatewayRouteTable struct { - ec2iface.EC2API - DescribeTransitGatewayRouteTablesOutput ec2.DescribeTransitGatewayRouteTablesOutput - DeleteTransitGatewayRouteTableOutput ec2.DeleteTransitGatewayRouteTableOutput -} - -type mockedTransitGatewayVpcAttachment struct { - ec2iface.EC2API - DescribeTransitGatewayVpcAttachmentsOutput ec2.DescribeTransitGatewayVpcAttachmentsOutput - DeleteTransitGatewayVpcAttachmentOutput ec2.DeleteTransitGatewayVpcAttachmentOutput -} - -type mockedTransitGatewayPeeringAttachment struct { - ec2iface.EC2API - DescribeTransitGatewayPeeringAttachmentsOutput ec2.DescribeTransitGatewayPeeringAttachmentsOutput - DeleteTransitGatewayPeeringAttachmentOutput ec2.DeleteTransitGatewayPeeringAttachmentOutput -} - -func (m mockedTransitGatewayPeeringAttachment) DescribeTransitGatewayPeeringAttachmentsPagesWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayPeeringAttachmentsInput, fn func(*ec2.DescribeTransitGatewayPeeringAttachmentsOutput, bool) bool, _ ...request.Option) error { - fn(&m.DescribeTransitGatewayPeeringAttachmentsOutput, true) - return nil -} - -func (m mockedTransitGatewayPeeringAttachment) DeleteTransitGatewayPeeringAttachmentWithContext(_ awsgo.Context, _ *ec2.DeleteTransitGatewayPeeringAttachmentInput, _ ...request.Option) (*ec2.DeleteTransitGatewayPeeringAttachmentOutput, error) { - return &m.DeleteTransitGatewayPeeringAttachmentOutput, nil -} - func (m mockedTransitGateway) DescribeTransitGatewaysWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewaysInput, _ ...request.Option) (*ec2.DescribeTransitGatewaysOutput, error) { return &m.DescribeTransitGatewaysOutput, nil } @@ -68,25 +41,6 @@ func (m mockedTransitGateway) WaitUntilTransitGatewayAttachmentDeleted(*string, return nil } -func (m mockedTransitGatewayRouteTable) DescribeTransitGatewayRouteTablesWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayRouteTablesInput, _ ...request.Option) (*ec2.DescribeTransitGatewayRouteTablesOutput, error) { - return &m.DescribeTransitGatewayRouteTablesOutput, nil -} - -func (m mockedTransitGatewayRouteTable) DeleteTransitGatewayRouteTableWithContext(_ awsgo.Context, _ *ec2.DeleteTransitGatewayRouteTableInput, _ ...request.Option) (*ec2.DeleteTransitGatewayRouteTableOutput, error) { - return &m.DeleteTransitGatewayRouteTableOutput, nil -} - -func (m mockedTransitGatewayVpcAttachment) DescribeTransitGatewayVpcAttachmentsWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayVpcAttachmentsInput, _ ...request.Option) (*ec2.DescribeTransitGatewayVpcAttachmentsOutput, error) { - return &m.DescribeTransitGatewayVpcAttachmentsOutput, nil -} -func (m mockedTransitGatewayVpcAttachment) DescribeTransitGatewayVpcAttachments(_ *ec2.DescribeTransitGatewayVpcAttachmentsInput) (*ec2.DescribeTransitGatewayVpcAttachmentsOutput, error) { - return &m.DescribeTransitGatewayVpcAttachmentsOutput, nil -} - -func (m mockedTransitGatewayVpcAttachment) DeleteTransitGatewayVpcAttachmentWithContext(_ awsgo.Context, _ *ec2.DeleteTransitGatewayVpcAttachmentInput, _ ...request.Option) (*ec2.DeleteTransitGatewayVpcAttachmentOutput, error) { - return &m.DeleteTransitGatewayVpcAttachmentOutput, nil -} - func TestTransitGateways_GetAll(t *testing.T) { t.Parallel() @@ -155,200 +109,3 @@ func TestTransitGateways_NukeAll(t *testing.T) { err := tgw.nukeAll([]*string{aws.String("test-gateway")}) require.NoError(t, err) } - -func TestTransitGatewayRouteTables_GetAll(t *testing.T) { - - t.Parallel() - - now := time.Now() - tableId1 := "table1" - tableId2 := "table2" - tgw := TransitGatewaysRouteTables{ - Client: mockedTransitGatewayRouteTable{ - DescribeTransitGatewayRouteTablesOutput: ec2.DescribeTransitGatewayRouteTablesOutput{ - TransitGatewayRouteTables: []*ec2.TransitGatewayRouteTable{ - { - TransitGatewayRouteTableId: aws.String(tableId1), - CreationTime: aws.Time(now), - State: aws.String("available"), - }, - { - TransitGatewayRouteTableId: aws.String(tableId2), - CreationTime: aws.Time(now.Add(1)), - State: aws.String("deleting"), - }, - }, - }, - }, - } - tests := map[string]struct { - configObj config.ResourceType - expected []string - }{ - "emptyFilter": { - configObj: config.ResourceType{}, - expected: []string{tableId1}, - }, - "timeAfterExclusionFilter": { - configObj: config.ResourceType{ - ExcludeRule: config.FilterRule{ - TimeAfter: aws.Time(now.Add(-1 * time.Hour)), - }}, - expected: []string{}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - names, err := tgw.getAll(context.Background(), config.Config{ - TransitGatewayRouteTable: tc.configObj, - }) - require.NoError(t, err) - require.Equal(t, tc.expected, aws.StringValueSlice(names)) - }) - } -} - -func TestTransitGatewayRouteTables_NukeAll(t *testing.T) { - - t.Parallel() - - tgw := TransitGatewaysRouteTables{ - Client: mockedTransitGatewayRouteTable{ - DeleteTransitGatewayRouteTableOutput: ec2.DeleteTransitGatewayRouteTableOutput{}, - }, - } - - err := tgw.nukeAll([]*string{aws.String("test-route-table")}) - require.NoError(t, err) -} - -func TestTransitGatewayVpcAttachments_GetAll(t *testing.T) { - - t.Parallel() - - now := time.Now() - attachment1 := "attachement1" - attachment2 := "attachement2" - tgw := TransitGatewaysVpcAttachment{ - Client: mockedTransitGatewayVpcAttachment{ - DescribeTransitGatewayVpcAttachmentsOutput: ec2.DescribeTransitGatewayVpcAttachmentsOutput{ - TransitGatewayVpcAttachments: []*ec2.TransitGatewayVpcAttachment{ - { - TransitGatewayAttachmentId: aws.String(attachment1), - CreationTime: aws.Time(now), - State: aws.String("available"), - }, - { - TransitGatewayAttachmentId: aws.String(attachment2), - CreationTime: aws.Time(now.Add(1)), - State: aws.String("deleting"), - }, - }, - }, - }, - } - tests := map[string]struct { - configObj config.ResourceType - expected []string - }{ - "emptyFilter": { - configObj: config.ResourceType{}, - expected: []string{attachment1}, - }, - "timeAfterExclusionFilter": { - configObj: config.ResourceType{ - ExcludeRule: config.FilterRule{ - TimeAfter: aws.Time(now.Add(-1 * time.Hour)), - }}, - expected: []string{}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - names, err := tgw.getAll(context.Background(), config.Config{ - TransitGatewaysVpcAttachment: tc.configObj, - }) - require.NoError(t, err) - require.Equal(t, tc.expected, aws.StringValueSlice(names)) - }) - } -} - -func TestTransitGatewayVpcAttachments_NukeAll(t *testing.T) { - - t.Parallel() - - tgw := TransitGatewaysVpcAttachment{ - Client: mockedTransitGatewayVpcAttachment{ - DeleteTransitGatewayVpcAttachmentOutput: ec2.DeleteTransitGatewayVpcAttachmentOutput{}, - }, - } - - err := tgw.nukeAll([]*string{aws.String("test-attachment")}) - require.NoError(t, err) -} - -func TestTransitGatewayPeeringAttachment_getAll(t *testing.T) { - - t.Parallel() - - now := time.Now() - attachment1 := "attachement1" - attachment2 := "attachement2" - tgpa := TransitGatewayPeeringAttachment{ - Client: mockedTransitGatewayPeeringAttachment{ - DescribeTransitGatewayPeeringAttachmentsOutput: ec2.DescribeTransitGatewayPeeringAttachmentsOutput{ - TransitGatewayPeeringAttachments: []*ec2.TransitGatewayPeeringAttachment{ - { - TransitGatewayAttachmentId: aws.String(attachment1), - CreationTime: aws.Time(now), - }, - { - TransitGatewayAttachmentId: aws.String(attachment2), - CreationTime: aws.Time(now.Add(1)), - }, - }, - }, - }, - } - - tests := map[string]struct { - configObj config.ResourceType - expected []string - }{ - "emptyFilter": { - configObj: config.ResourceType{}, - expected: []string{attachment1, attachment2}, - }, - "timeAfterExclusionFilter": { - configObj: config.ResourceType{ - ExcludeRule: config.FilterRule{ - TimeAfter: aws.Time(now), - }}, - expected: []string{attachment1}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - names, err := tgpa.getAll(context.Background(), config.Config{ - TransitGatewayPeeringAttachment: tc.configObj, - }) - require.NoError(t, err) - require.Equal(t, tc.expected, aws.StringValueSlice(names)) - }) - } -} - -func TestTransitGatewayPeeringAttachment_nukeAll(t *testing.T) { - - t.Parallel() - - tgw := TransitGatewayPeeringAttachment{ - Client: mockedTransitGatewayPeeringAttachment{ - DeleteTransitGatewayPeeringAttachmentOutput: ec2.DeleteTransitGatewayPeeringAttachmentOutput{}, - }, - } - - err := tgw.nukeAll([]*string{aws.String("test-attachment")}) - require.NoError(t, err) -} From f97d917c82f788da926f12e58bad194428d8413f Mon Sep 17 00:00:00 2001 From: James Kwon <96548424+james03160927@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:07:01 -0400 Subject: [PATCH 3/3] Refactor Transit Gateway (#750) * fix:code refactoring of transit_gateway file * fix:code refactoring of transit_gateway file --------- Co-authored-by: James Kwon <96548424+hongil0316@users.noreply.github.com> --- aws/resources/tgw_peering_attachment.go | 53 +++++++ aws/resources/tgw_peering_attachment_test.go | 95 ++++++++++++ aws/resources/tgw_peering_attachment_types.go | 58 ++++++++ aws/resources/tgw_route_tables.go | 70 +++++++++ aws/resources/tgw_route_tables_test.go | 94 ++++++++++++ aws/resources/tgw_route_tables_types.go | 58 ++++++++ aws/resources/tgw_vpc_attachment.go | 99 +++++++++++++ aws/resources/tgw_vpc_attachment_test.go | 98 +++++++++++++ aws/resources/tgw_vpc_attachment_types.go | 58 ++++++++ aws/resources/transit_gateway.go | 137 +---------------- aws/resources/transit_gateway_test.go | 13 -- aws/resources/transit_gateway_types.go | 138 ------------------ 12 files changed, 691 insertions(+), 280 deletions(-) create mode 100644 aws/resources/tgw_peering_attachment.go create mode 100644 aws/resources/tgw_peering_attachment_test.go create mode 100644 aws/resources/tgw_peering_attachment_types.go create mode 100644 aws/resources/tgw_route_tables.go create mode 100644 aws/resources/tgw_route_tables_test.go create mode 100644 aws/resources/tgw_route_tables_types.go create mode 100644 aws/resources/tgw_vpc_attachment.go create mode 100644 aws/resources/tgw_vpc_attachment_test.go create mode 100644 aws/resources/tgw_vpc_attachment_types.go diff --git a/aws/resources/tgw_peering_attachment.go b/aws/resources/tgw_peering_attachment.go new file mode 100644 index 00000000..e15323e7 --- /dev/null +++ b/aws/resources/tgw_peering_attachment.go @@ -0,0 +1,53 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "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/go-commons/errors" +) + +func (tgpa *TransitGatewayPeeringAttachment) getAll(c context.Context, configObj config.Config) ([]*string, error) { + var ids []*string + err := tgpa.Client.DescribeTransitGatewayPeeringAttachmentsPagesWithContext(tgpa.Context, &ec2.DescribeTransitGatewayPeeringAttachmentsInput{}, func(result *ec2.DescribeTransitGatewayPeeringAttachmentsOutput, lastPage bool) bool { + for _, attachment := range result.TransitGatewayPeeringAttachments { + if configObj.TransitGatewayPeeringAttachment.ShouldInclude(config.ResourceValue{ + Time: attachment.CreationTime, + }) { + ids = append(ids, attachment.TransitGatewayAttachmentId) + } + } + + return !lastPage + }) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + return ids, nil +} + +func (tgpa *TransitGatewayPeeringAttachment) nukeAll(ids []*string) error { + for _, id := range ids { + _, err := tgpa.Client.DeleteTransitGatewayPeeringAttachmentWithContext(tgpa.Context, &ec2.DeleteTransitGatewayPeeringAttachmentInput{ + TransitGatewayAttachmentId: id, + }) + // Record status of this resource + report.Record(report.Entry{ + Identifier: aws.StringValue(id), + ResourceType: tgpa.ResourceName(), + Error: err, + }) + if err != nil { + logging.Errorf("[Failed] %s", err) + } else { + logging.Debugf("Deleted Transit Gateway Peering Attachment: %s", *id) + } + } + + return nil +} diff --git a/aws/resources/tgw_peering_attachment_test.go b/aws/resources/tgw_peering_attachment_test.go new file mode 100644 index 00000000..edb8882f --- /dev/null +++ b/aws/resources/tgw_peering_attachment_test.go @@ -0,0 +1,95 @@ +package resources + +import ( + "context" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/stretchr/testify/require" +) + +type mockedTransitGatewayPeeringAttachment struct { + ec2iface.EC2API + DescribeTransitGatewayPeeringAttachmentsOutput ec2.DescribeTransitGatewayPeeringAttachmentsOutput + DeleteTransitGatewayPeeringAttachmentOutput ec2.DeleteTransitGatewayPeeringAttachmentOutput +} + +func (m mockedTransitGatewayPeeringAttachment) DescribeTransitGatewayPeeringAttachmentsPagesWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayPeeringAttachmentsInput, fn func(*ec2.DescribeTransitGatewayPeeringAttachmentsOutput, bool) bool, _ ...request.Option) error { + fn(&m.DescribeTransitGatewayPeeringAttachmentsOutput, true) + return nil +} + +func (m mockedTransitGatewayPeeringAttachment) DeleteTransitGatewayPeeringAttachmentWithContext(_ awsgo.Context, _ *ec2.DeleteTransitGatewayPeeringAttachmentInput, _ ...request.Option) (*ec2.DeleteTransitGatewayPeeringAttachmentOutput, error) { + return &m.DeleteTransitGatewayPeeringAttachmentOutput, nil +} + +func TestTransitGatewayPeeringAttachment_getAll(t *testing.T) { + + t.Parallel() + + now := time.Now() + attachment1 := "attachement1" + attachment2 := "attachement2" + tgpa := TransitGatewayPeeringAttachment{ + Client: mockedTransitGatewayPeeringAttachment{ + DescribeTransitGatewayPeeringAttachmentsOutput: ec2.DescribeTransitGatewayPeeringAttachmentsOutput{ + TransitGatewayPeeringAttachments: []*ec2.TransitGatewayPeeringAttachment{ + { + TransitGatewayAttachmentId: aws.String(attachment1), + CreationTime: aws.Time(now), + }, + { + TransitGatewayAttachmentId: aws.String(attachment2), + CreationTime: aws.Time(now.Add(1)), + }, + }, + }, + }, + } + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{attachment1, attachment2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now), + }}, + expected: []string{attachment1}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := tgpa.getAll(context.Background(), config.Config{ + TransitGatewayPeeringAttachment: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestTransitGatewayPeeringAttachment_nukeAll(t *testing.T) { + + t.Parallel() + + tgw := TransitGatewayPeeringAttachment{ + Client: mockedTransitGatewayPeeringAttachment{ + DeleteTransitGatewayPeeringAttachmentOutput: ec2.DeleteTransitGatewayPeeringAttachmentOutput{}, + }, + } + + err := tgw.nukeAll([]*string{aws.String("test-attachment")}) + require.NoError(t, err) +} diff --git a/aws/resources/tgw_peering_attachment_types.go b/aws/resources/tgw_peering_attachment_types.go new file mode 100644 index 00000000..d9dfae78 --- /dev/null +++ b/aws/resources/tgw_peering_attachment_types.go @@ -0,0 +1,58 @@ +package resources + +import ( + "context" + + 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/ec2/ec2iface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +// TransitGatewayPeeringAttachment - represents all transit gateways peering attachment +type TransitGatewayPeeringAttachment struct { + BaseAwsResource + Client ec2iface.EC2API + Region string + Ids []string +} + +func (tgpa *TransitGatewayPeeringAttachment) Init(session *session.Session) { + tgpa.Client = ec2.New(session) +} + +func (tgpa *TransitGatewayPeeringAttachment) ResourceName() string { + return "transit-gateway-peering-attachment" +} + +func (tgpa *TransitGatewayPeeringAttachment) MaxBatchSize() int { + return maxBatchSize +} + +func (tgpa *TransitGatewayPeeringAttachment) ResourceIdentifiers() []string { + return tgpa.Ids +} + +func (tgpa *TransitGatewayPeeringAttachment) GetAndSetResourceConfig(configObj config.Config) config.ResourceType { + return configObj.TransitGateway +} + +func (tgpa *TransitGatewayPeeringAttachment) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := tgpa.getAll(c, configObj) + if err != nil { + return nil, err + } + + tgpa.Ids = awsgo.StringValueSlice(identifiers) + return tgpa.Ids, nil +} + +func (tgpa *TransitGatewayPeeringAttachment) Nuke(identifiers []string) error { + if err := tgpa.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/resources/tgw_route_tables.go b/aws/resources/tgw_route_tables.go new file mode 100644 index 00000000..63a5d2ef --- /dev/null +++ b/aws/resources/tgw_route_tables.go @@ -0,0 +1,70 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/cloud-nuke/logging" + "github.com/gruntwork-io/go-commons/errors" +) + +// Returns a formatted string of TransitGatewayRouteTable IDs +func (tgw *TransitGatewaysRouteTables) getAll(c context.Context, configObj config.Config) ([]*string, error) { + // Remove default route table, that will be deleted along with its TransitGateway + param := &ec2.DescribeTransitGatewayRouteTablesInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("default-association-route-table"), + Values: []*string{ + aws.String("false"), + }, + }, + }, + } + + result, err := tgw.Client.DescribeTransitGatewayRouteTablesWithContext(tgw.Context, param) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var ids []*string + for _, transitGatewayRouteTable := range result.TransitGatewayRouteTables { + if configObj.TransitGatewayRouteTable.ShouldInclude(config.ResourceValue{Time: transitGatewayRouteTable.CreationTime}) && + awsgo.StringValue(transitGatewayRouteTable.State) != "deleted" && awsgo.StringValue(transitGatewayRouteTable.State) != "deleting" { + ids = append(ids, transitGatewayRouteTable.TransitGatewayRouteTableId) + } + } + + return ids, nil +} + +// Delete all TransitGatewayRouteTables +func (tgw *TransitGatewaysRouteTables) nukeAll(ids []*string) error { + if len(ids) == 0 { + logging.Debugf("No Transit Gateway Route Tables to nuke in region %s", tgw.Region) + return nil + } + + logging.Debugf("Deleting all Transit Gateway Route Tables in region %s", tgw.Region) + var deletedIds []*string + + for _, id := range ids { + param := &ec2.DeleteTransitGatewayRouteTableInput{ + TransitGatewayRouteTableId: id, + } + + _, err := tgw.Client.DeleteTransitGatewayRouteTableWithContext(tgw.Context, param) + if err != nil { + logging.Debugf("[Failed] %s", err) + } else { + deletedIds = append(deletedIds, id) + logging.Debugf("Deleted Transit Gateway Route Table: %s", *id) + } + } + + logging.Debugf("[OK] %d Transit Gateway Route Table(s) deleted in %s", len(deletedIds), tgw.Region) + return nil +} diff --git a/aws/resources/tgw_route_tables_test.go b/aws/resources/tgw_route_tables_test.go new file mode 100644 index 00000000..81f86f09 --- /dev/null +++ b/aws/resources/tgw_route_tables_test.go @@ -0,0 +1,94 @@ +package resources + +import ( + "context" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/stretchr/testify/require" +) + +type mockedTransitGatewayRouteTable struct { + ec2iface.EC2API + DescribeTransitGatewayRouteTablesOutput ec2.DescribeTransitGatewayRouteTablesOutput + DeleteTransitGatewayRouteTableOutput ec2.DeleteTransitGatewayRouteTableOutput +} + +func (m mockedTransitGatewayRouteTable) DescribeTransitGatewayRouteTablesWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayRouteTablesInput, _ ...request.Option) (*ec2.DescribeTransitGatewayRouteTablesOutput, error) { + return &m.DescribeTransitGatewayRouteTablesOutput, nil +} + +func (m mockedTransitGatewayRouteTable) DeleteTransitGatewayRouteTableWithContext(_ awsgo.Context, _ *ec2.DeleteTransitGatewayRouteTableInput, _ ...request.Option) (*ec2.DeleteTransitGatewayRouteTableOutput, error) { + return &m.DeleteTransitGatewayRouteTableOutput, nil +} +func TestTransitGatewayRouteTables_GetAll(t *testing.T) { + + t.Parallel() + + now := time.Now() + tableId1 := "table1" + tableId2 := "table2" + tgw := TransitGatewaysRouteTables{ + Client: mockedTransitGatewayRouteTable{ + DescribeTransitGatewayRouteTablesOutput: ec2.DescribeTransitGatewayRouteTablesOutput{ + TransitGatewayRouteTables: []*ec2.TransitGatewayRouteTable{ + { + TransitGatewayRouteTableId: aws.String(tableId1), + CreationTime: aws.Time(now), + State: aws.String("available"), + }, + { + TransitGatewayRouteTableId: aws.String(tableId2), + CreationTime: aws.Time(now.Add(1)), + State: aws.String("deleting"), + }, + }, + }, + }, + } + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{tableId1}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now.Add(-1 * time.Hour)), + }}, + expected: []string{}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := tgw.getAll(context.Background(), config.Config{ + TransitGatewayRouteTable: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestTransitGatewayRouteTables_NukeAll(t *testing.T) { + + t.Parallel() + + tgw := TransitGatewaysRouteTables{ + Client: mockedTransitGatewayRouteTable{ + DeleteTransitGatewayRouteTableOutput: ec2.DeleteTransitGatewayRouteTableOutput{}, + }, + } + + err := tgw.nukeAll([]*string{aws.String("test-route-table")}) + require.NoError(t, err) +} diff --git a/aws/resources/tgw_route_tables_types.go b/aws/resources/tgw_route_tables_types.go new file mode 100644 index 00000000..5da3055a --- /dev/null +++ b/aws/resources/tgw_route_tables_types.go @@ -0,0 +1,58 @@ +package resources + +import ( + "context" + + 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/ec2/ec2iface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +// TransitGatewaysRouteTables - represents all transit gateways route tables +type TransitGatewaysRouteTables struct { + BaseAwsResource + Client ec2iface.EC2API + Region string + Ids []string +} + +func (tgw *TransitGatewaysRouteTables) Init(session *session.Session) { + tgw.Client = ec2.New(session) +} + +// ResourceName - the simple name of the aws resource +func (tgw *TransitGatewaysRouteTables) ResourceName() string { + return "transit-gateway-route-table" +} + +// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle +func (tgw *TransitGatewaysRouteTables) MaxBatchSize() int { + return maxBatchSize +} + +// ResourceIdentifiers - The arns of the transit gateways route tables +func (tgw *TransitGatewaysRouteTables) ResourceIdentifiers() []string { + return tgw.Ids +} + +func (tgw *TransitGatewaysRouteTables) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := tgw.getAll(c, configObj) + if err != nil { + return nil, err + } + + tgw.Ids = awsgo.StringValueSlice(identifiers) + return tgw.Ids, nil +} + +// Nuke - nuke 'em all!!! +func (tgw *TransitGatewaysRouteTables) Nuke(identifiers []string) error { + if err := tgw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/resources/tgw_vpc_attachment.go b/aws/resources/tgw_vpc_attachment.go new file mode 100644 index 00000000..293d4a73 --- /dev/null +++ b/aws/resources/tgw_vpc_attachment.go @@ -0,0 +1,99 @@ +package resources + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go/aws" + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + goerror "github.com/go-errors/errors" + "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/go-commons/errors" +) + +// Returns a formated string of TransitGatewayVpcAttachment IDs +func (tgw *TransitGatewaysVpcAttachment) getAll(c context.Context, configObj config.Config) ([]*string, error) { + result, err := tgw.Client.DescribeTransitGatewayVpcAttachmentsWithContext(tgw.Context, &ec2.DescribeTransitGatewayVpcAttachmentsInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var ids []*string + for _, tgwVpcAttachment := range result.TransitGatewayVpcAttachments { + if configObj.TransitGatewaysVpcAttachment.ShouldInclude(config.ResourceValue{Time: tgwVpcAttachment.CreationTime}) && + awsgo.StringValue(tgwVpcAttachment.State) != "deleted" && awsgo.StringValue(tgwVpcAttachment.State) != "deleting" { + ids = append(ids, tgwVpcAttachment.TransitGatewayAttachmentId) + } + } + + return ids, nil +} + +// Delete all TransitGatewayVpcAttachments +func (tgw *TransitGatewaysVpcAttachment) nukeAll(ids []*string) error { + if len(ids) == 0 { + logging.Debugf("No Transit Gateway Vpc Attachments to nuke in region %s", tgw.Region) + return nil + } + + logging.Debugf("Deleting all Transit Gateway Vpc Attachments in region %s", tgw.Region) + var deletedIds []*string + + for _, id := range ids { + param := &ec2.DeleteTransitGatewayVpcAttachmentInput{ + TransitGatewayAttachmentId: id, + } + + _, err := tgw.Client.DeleteTransitGatewayVpcAttachmentWithContext(tgw.Context, param) + + // Record status of this resource + e := report.Entry{ + Identifier: aws.StringValue(id), + ResourceType: "Transit Gateway", + Error: err, + } + report.Record(e) + + if err != nil { + logging.Debugf("[Failed] %s", err) + } else { + deletedIds = append(deletedIds, id) + logging.Debugf("Deleted Transit Gateway Vpc Attachment: %s", *id) + } + } + + if waiterr := waitForTransitGatewayAttachementToBeDeleted(*tgw); waiterr != nil { + return errors.WithStackTrace(waiterr) + } + logging.Debugf("[OK] %d Transit Gateway Vpc Attachment(s) deleted in %s", len(deletedIds), tgw.Region) + return nil +} + +func waitForTransitGatewayAttachementToBeDeleted(tgw TransitGatewaysVpcAttachment) error { + for i := 0; i < 30; i++ { + gateways, err := tgw.Client.DescribeTransitGatewayVpcAttachments( + &ec2.DescribeTransitGatewayVpcAttachmentsInput{ + TransitGatewayAttachmentIds: aws.StringSlice(tgw.Ids), + Filters: []*ec2.Filter{ + { + Name: awsgo.String("state"), + Values: []*string{awsgo.String("deleting")}, + }, + }, + }, + ) + if err != nil { + return err + } + if len(gateways.TransitGatewayVpcAttachments) == 0 { + return nil + } + logging.Info("Waiting for transit gateways attachemensts to be deleted...") + time.Sleep(10 * time.Second) + } + + return goerror.New("timed out waiting for transit gateway attahcments to be successfully deleted") +} diff --git a/aws/resources/tgw_vpc_attachment_test.go b/aws/resources/tgw_vpc_attachment_test.go new file mode 100644 index 00000000..3e04c679 --- /dev/null +++ b/aws/resources/tgw_vpc_attachment_test.go @@ -0,0 +1,98 @@ +package resources + +import ( + "context" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/stretchr/testify/require" +) + +type mockedTransitGatewayVpcAttachment struct { + ec2iface.EC2API + DescribeTransitGatewayVpcAttachmentsOutput ec2.DescribeTransitGatewayVpcAttachmentsOutput + DeleteTransitGatewayVpcAttachmentOutput ec2.DeleteTransitGatewayVpcAttachmentOutput +} + +func (m mockedTransitGatewayVpcAttachment) DescribeTransitGatewayVpcAttachmentsWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewayVpcAttachmentsInput, _ ...request.Option) (*ec2.DescribeTransitGatewayVpcAttachmentsOutput, error) { + return &m.DescribeTransitGatewayVpcAttachmentsOutput, nil +} +func (m mockedTransitGatewayVpcAttachment) DescribeTransitGatewayVpcAttachments(_ *ec2.DescribeTransitGatewayVpcAttachmentsInput) (*ec2.DescribeTransitGatewayVpcAttachmentsOutput, error) { + return &m.DescribeTransitGatewayVpcAttachmentsOutput, nil +} + +func (m mockedTransitGatewayVpcAttachment) DeleteTransitGatewayVpcAttachmentWithContext(_ awsgo.Context, _ *ec2.DeleteTransitGatewayVpcAttachmentInput, _ ...request.Option) (*ec2.DeleteTransitGatewayVpcAttachmentOutput, error) { + return &m.DeleteTransitGatewayVpcAttachmentOutput, nil +} + +func TestTransitGatewayVpcAttachments_GetAll(t *testing.T) { + + t.Parallel() + + now := time.Now() + attachment1 := "attachement1" + attachment2 := "attachement2" + tgw := TransitGatewaysVpcAttachment{ + Client: mockedTransitGatewayVpcAttachment{ + DescribeTransitGatewayVpcAttachmentsOutput: ec2.DescribeTransitGatewayVpcAttachmentsOutput{ + TransitGatewayVpcAttachments: []*ec2.TransitGatewayVpcAttachment{ + { + TransitGatewayAttachmentId: aws.String(attachment1), + CreationTime: aws.Time(now), + State: aws.String("available"), + }, + { + TransitGatewayAttachmentId: aws.String(attachment2), + CreationTime: aws.Time(now.Add(1)), + State: aws.String("deleting"), + }, + }, + }, + }, + } + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{attachment1}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: aws.Time(now.Add(-1 * time.Hour)), + }}, + expected: []string{}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := tgw.getAll(context.Background(), config.Config{ + TransitGatewaysVpcAttachment: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestTransitGatewayVpcAttachments_NukeAll(t *testing.T) { + + t.Parallel() + + tgw := TransitGatewaysVpcAttachment{ + Client: mockedTransitGatewayVpcAttachment{ + DeleteTransitGatewayVpcAttachmentOutput: ec2.DeleteTransitGatewayVpcAttachmentOutput{}, + }, + } + + err := tgw.nukeAll([]*string{aws.String("test-attachment")}) + require.NoError(t, err) +} diff --git a/aws/resources/tgw_vpc_attachment_types.go b/aws/resources/tgw_vpc_attachment_types.go new file mode 100644 index 00000000..0a59971a --- /dev/null +++ b/aws/resources/tgw_vpc_attachment_types.go @@ -0,0 +1,58 @@ +package resources + +import ( + "context" + + 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/ec2/ec2iface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +// TransitGatewaysVpcAttachment - represents all transit gateways vpc attachments +type TransitGatewaysVpcAttachment struct { + BaseAwsResource + Client ec2iface.EC2API + Region string + Ids []string +} + +func (tgw *TransitGatewaysVpcAttachment) Init(session *session.Session) { + tgw.Client = ec2.New(session) +} + +// ResourceName - the simple name of the aws resource +func (tgw *TransitGatewaysVpcAttachment) ResourceName() string { + return "transit-gateway-attachment" +} + +// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle +func (tgw *TransitGatewaysVpcAttachment) MaxBatchSize() int { + return maxBatchSize +} + +// ResourceIdentifiers - The Ids of the transit gateways +func (tgw *TransitGatewaysVpcAttachment) ResourceIdentifiers() []string { + return tgw.Ids +} + +func (tgw *TransitGatewaysVpcAttachment) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := tgw.getAll(c, configObj) + if err != nil { + return nil, err + } + + tgw.Ids = awsgo.StringValueSlice(identifiers) + return tgw.Ids, nil +} + +// Nuke - nuke 'em all!!! +func (tgw *TransitGatewaysVpcAttachment) Nuke(identifiers []string) error { + if err := tgw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/resources/transit_gateway.go b/aws/resources/transit_gateway.go index 59e94452..cd17f8c4 100644 --- a/aws/resources/transit_gateway.go +++ b/aws/resources/transit_gateway.go @@ -2,8 +2,6 @@ package resources import ( "context" - "time" - "fmt" "github.com/aws/aws-sdk-go/aws" awsgo "github.com/aws/aws-sdk-go/aws" @@ -15,10 +13,6 @@ import ( "github.com/gruntwork-io/go-commons/errors" ) -const ( - TransitGatewayAttachmentTypePeering = "peering" -) - // [Note 1] : NOTE on the Apporach used:-Using the `dry run` approach on verifying the nuking permission in case of a scoped IAM role. // IAM:simulateCustomPolicy : could also be used but the IAM role itself needs permission for simulateCustomPolicy method //else this would not get the desired result. Also in case of multiple t-gateway, if only some has permssion to be nuked, @@ -37,12 +31,7 @@ func (tgw *TransitGateways) getAll(c context.Context, configObj config.Config) ( currentOwner := c.Value(util.AccountIdKey) var ids []*string for _, transitGateway := range result.TransitGateways { - hostNameTagValue := util.GetEC2ResourceNameTagValue(transitGateway.Tags) - - if configObj.TransitGateway.ShouldInclude(config.ResourceValue{ - Time: transitGateway.CreationTime, - Name: hostNameTagValue, - }) && + if configObj.TransitGateway.ShouldInclude(config.ResourceValue{Time: transitGateway.CreationTime}) && awsgo.StringValue(transitGateway.State) != "deleted" && awsgo.StringValue(transitGateway.State) != "deleting" { ids = append(ids, transitGateway.TransitGatewayId) } @@ -69,121 +58,6 @@ func (tgw *TransitGateways) getAll(c context.Context, configObj config.Config) ( return ids, nil } -func (tgw *TransitGateways) nuke(id *string) error { - // check the transit gateway has attachments and nuke them before - if err := tgw.nukeAttachments(id); err != nil { - return errors.WithStackTrace(err) - } - - if _, err := tgw.Client.DeleteTransitGatewayWithContext(tgw.Context, &ec2.DeleteTransitGatewayInput{ - TransitGatewayId: id, - }); err != nil { - return errors.WithStackTrace(err) - } - - return nil -} - -func (tgw *TransitGateways) nukeAttachments(id *string) error { - logging.Debugf("nuking transit gateway attachments for %v", awsgo.StringValue(id)) - output, err := tgw.Client.DescribeTransitGatewayAttachmentsWithContext(tgw.Context,&ec2.DescribeTransitGatewayAttachmentsInput{ - Filters: []*ec2.Filter{ - { - Name: awsgo.String("transit-gateway-id"), - Values: []*string{ - id, - }, - }, - { - Name: awsgo.String("state"), - Values: []*string{ - awsgo.String("available"), - }, - }, - }, - }) - if err != nil { - logging.Errorf("[Failed] unable to describe the transit gateway attachments for %v : %s", awsgo.StringValue(id), err) - return errors.WithStackTrace(err) - } - - logging.Debugf("%v attachment(s) found with %v", len(output.TransitGatewayAttachments),awsgo.StringValue(id)) - - for _, attachments := range output.TransitGatewayAttachments { - var ( - err error - attachmentType = awsgo.StringValue(attachments.ResourceType) - now = time.Now() - ) - - switch attachmentType { - case TransitGatewayAttachmentTypePeering: - logging.Debugf("[Execution] deleting the attachments of type %v for %v ", attachmentType, awsgo.StringValue(id)) - _, err = tgw.Client.DeleteTransitGatewayPeeringAttachmentWithContext(tgw.Context, &ec2.DeleteTransitGatewayPeeringAttachmentInput{ - TransitGatewayAttachmentId: attachments.TransitGatewayAttachmentId, - }) - default: - err = fmt.Errorf("%v typed transit gateway attachment nuking not handled", attachmentType) - } - if err != nil { - logging.Errorf("[Failed] unable to delete the transit gateway peernig attachment for %v : %s", awsgo.StringValue(id), err) - return err - } - - if err := tgw.WaitUntilTransitGatewayAttachmentDeleted(id, attachmentType); err != nil { - logging.Errorf("[Failed] unable to wait until nuking the transit gateway attachment with type %v for %v : %s", attachmentType,awsgo.StringValue(id), err) - return errors.WithStackTrace(err) - } - - logging.Debugf("waited %v to nuke the attachment", time.Since(now)) - } - - logging.Debugf("[Ok] successfully nuked all the attachments on %v", awsgo.StringValue(id)) - return nil -} - -func (tgw *TransitGateways) WaitUntilTransitGatewayAttachmentDeleted(id *string, attachmentType string) error { - timeoutCtx, cancel := context.WithTimeout(tgw.Context, 5*time.Minute) - defer cancel() - - ticker := time.NewTicker(3 * time.Second) - defer ticker.Stop() - - for { - select { - case <-timeoutCtx.Done(): - return fmt.Errorf("transit gateway attachments deletion check timed out after 5 minute") - case <-ticker.C: - output, err := tgw.Client.DescribeTransitGatewayAttachmentsWithContext(tgw.Context,&ec2.DescribeTransitGatewayAttachmentsInput{ - Filters: []*ec2.Filter{ - { - Name: awsgo.String("transit-gateway-id"), - Values: []*string{ - id, - }, - }, - { - Name: awsgo.String("state"), - Values: []*string{ - awsgo.String("available"), - awsgo.String("deleting"), - }, - }, - }, - }) - if err != nil { - logging.Debugf("transit gateway attachment(s) as type %v existance checking error : %v", attachmentType,err) - return errors.WithStackTrace(err) - } - - if len(output.TransitGatewayAttachments) == 0 { - return nil - } - logging.Debugf("%v transit gateway attachments(s) still exists as type %v, waiting...", len(output.TransitGatewayAttachments), attachmentType) - } - } -} - // Delete all TransitGateways // it attempts to nuke only those resources for which the current IAM user has permission func (tgw *TransitGateways) nukeAll(ids []*string) error { @@ -202,8 +76,13 @@ func (tgw *TransitGateways) nukeAll(ids []*string) error { logging.Debugf("[Skipping] %s nuke because %v", *id, reason) continue } - err := tgw.nuke(id) - + + params := &ec2.DeleteTransitGatewayInput{ + TransitGatewayId: id, + } + + _, err := tgw.Client.DeleteTransitGatewayWithContext(tgw.Context, params) + // Record status of this resource e := report.Entry{ Identifier: aws.StringValue(id), diff --git a/aws/resources/transit_gateway_test.go b/aws/resources/transit_gateway_test.go index 38fdc770..ef376a46 100644 --- a/aws/resources/transit_gateway_test.go +++ b/aws/resources/transit_gateway_test.go @@ -18,8 +18,6 @@ type mockedTransitGateway struct { ec2iface.EC2API DescribeTransitGatewaysOutput ec2.DescribeTransitGatewaysOutput DeleteTransitGatewayOutput ec2.DeleteTransitGatewayOutput - DescribeTransitGatewayAttachmentsOutput ec2.DescribeTransitGatewayAttachmentsOutput - DeleteTransitGatewayPeeringAttachmentOutput ec2.DeleteTransitGatewayPeeringAttachmentOutput } func (m mockedTransitGateway) DescribeTransitGatewaysWithContext(_ awsgo.Context, _ *ec2.DescribeTransitGatewaysInput, _ ...request.Option) (*ec2.DescribeTransitGatewaysOutput, error) { @@ -30,17 +28,6 @@ func (m mockedTransitGateway) DeleteTransitGatewayWithContext(_ awsgo.Context, _ return &m.DeleteTransitGatewayOutput, nil } -func (m mockedTransitGateway) DescribeTransitGatewayAttachmentsWithContext(awsgo.Context, *ec2.DescribeTransitGatewayAttachmentsInput, ...request.Option) (*ec2.DescribeTransitGatewayAttachmentsOutput, error){ - return &m.DescribeTransitGatewayAttachmentsOutput, nil -} - -func (m mockedTransitGateway) DeleteTransitGatewayPeeringAttachmentWithContext(aws.Context, *ec2.DeleteTransitGatewayPeeringAttachmentInput, ...request.Option) (*ec2.DeleteTransitGatewayPeeringAttachmentOutput, error){ - return &m.DeleteTransitGatewayPeeringAttachmentOutput, nil -} -func (m mockedTransitGateway) WaitUntilTransitGatewayAttachmentDeleted(*string, string) error { - return nil -} - func TestTransitGateways_GetAll(t *testing.T) { t.Parallel() diff --git a/aws/resources/transit_gateway_types.go b/aws/resources/transit_gateway_types.go index 6ef50d95..ad3656ef 100644 --- a/aws/resources/transit_gateway_types.go +++ b/aws/resources/transit_gateway_types.go @@ -11,144 +11,6 @@ import ( "github.com/gruntwork-io/go-commons/errors" ) -// TransitGateways - represents all transit gateways -type TransitGatewayPeeringAttachment struct { - BaseAwsResource - Client ec2iface.EC2API - Region string - Ids []string -} - -func (tgpa *TransitGatewayPeeringAttachment) Init(session *session.Session) { - tgpa.Client = ec2.New(session) -} - -func (tgpa *TransitGatewayPeeringAttachment) ResourceName() string { - return "transit-gateway-peering-attachment" -} - -func (tgpa *TransitGatewayPeeringAttachment) MaxBatchSize() int { - return maxBatchSize -} - -func (tgpa *TransitGatewayPeeringAttachment) ResourceIdentifiers() []string { - return tgpa.Ids -} - -func (tgpa *TransitGatewayPeeringAttachment) GetAndSetResourceConfig(configObj config.Config) config.ResourceType { - return configObj.TransitGateway -} - -func (tgpa *TransitGatewayPeeringAttachment) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { - identifiers, err := tgpa.getAll(c, configObj) - if err != nil { - return nil, err - } - - tgpa.Ids = awsgo.StringValueSlice(identifiers) - return tgpa.Ids, nil -} - -func (tgpa *TransitGatewayPeeringAttachment) Nuke(identifiers []string) error { - if err := tgpa.nukeAll(awsgo.StringSlice(identifiers)); err != nil { - return errors.WithStackTrace(err) - } - - return nil -} - -// TransitGatewaysVpcAttachment - represents all transit gateways vpc attachments -type TransitGatewaysVpcAttachment struct { - BaseAwsResource - Client ec2iface.EC2API - Region string - Ids []string -} - -func (tgw *TransitGatewaysVpcAttachment) Init(session *session.Session) { - tgw.Client = ec2.New(session) -} - -// ResourceName - the simple name of the aws resource -func (tgw *TransitGatewaysVpcAttachment) ResourceName() string { - return "transit-gateway-attachment" -} - -// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle -func (tgw *TransitGatewaysVpcAttachment) MaxBatchSize() int { - return maxBatchSize -} - -// ResourceIdentifiers - The Ids of the transit gateways -func (tgw *TransitGatewaysVpcAttachment) ResourceIdentifiers() []string { - return tgw.Ids -} - -func (tgw *TransitGatewaysVpcAttachment) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { - identifiers, err := tgw.getAll(c, configObj) - if err != nil { - return nil, err - } - - tgw.Ids = awsgo.StringValueSlice(identifiers) - return tgw.Ids, nil -} - -// Nuke - nuke 'em all!!! -func (tgw *TransitGatewaysVpcAttachment) Nuke(identifiers []string) error { - if err := tgw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { - return errors.WithStackTrace(err) - } - - return nil -} - -// TransitGatewaysRouteTables - represents all transit gateways route tables -type TransitGatewaysRouteTables struct { - BaseAwsResource - Client ec2iface.EC2API - Region string - Ids []string -} - -func (tgw *TransitGatewaysRouteTables) Init(session *session.Session) { - tgw.Client = ec2.New(session) -} - -// ResourceName - the simple name of the aws resource -func (tgw *TransitGatewaysRouteTables) ResourceName() string { - return "transit-gateway-route-table" -} - -// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle -func (tgw *TransitGatewaysRouteTables) MaxBatchSize() int { - return maxBatchSize -} - -// ResourceIdentifiers - The arns of the transit gateways route tables -func (tgw *TransitGatewaysRouteTables) ResourceIdentifiers() []string { - return tgw.Ids -} - -func (tgw *TransitGatewaysRouteTables) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { - identifiers, err := tgw.getAll(c, configObj) - if err != nil { - return nil, err - } - - tgw.Ids = awsgo.StringValueSlice(identifiers) - return tgw.Ids, nil -} - -// Nuke - nuke 'em all!!! -func (tgw *TransitGatewaysRouteTables) Nuke(identifiers []string) error { - if err := tgw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { - return errors.WithStackTrace(err) - } - - return nil -} - // TransitGateways - represents all transit gateways type TransitGateways struct { BaseAwsResource