-
-
Notifications
You must be signed in to change notification settings - Fork 356
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ca7dd2b
commit 5f96bd5
Showing
7 changed files
with
347 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package resources | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"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/cloud-nuke/util" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
func (ec2subnet *EC2Subnet) setFirstSeenTag(sb ec2.Subnet, value time.Time) error { | ||
_, err := ec2subnet.Client.CreateTags(&ec2.CreateTagsInput{ | ||
Resources: []*string{sb.SubnetId}, | ||
Tags: []*ec2.Tag{ | ||
{ | ||
Key: aws.String(util.FirstSeenTagKey), | ||
Value: aws.String(util.FormatTimestamp(value)), | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (ec2subnet *EC2Subnet) getFirstSeenTag(sb ec2.Subnet) (*time.Time, error) { | ||
tags := sb.Tags | ||
for _, tag := range tags { | ||
if util.IsFirstSeenTag(tag.Key) { | ||
firstSeenTime, err := util.ParseTimestamp(tag.Value) | ||
if err != nil { | ||
return nil, errors.WithStackTrace(err) | ||
} | ||
|
||
return firstSeenTime, nil | ||
} | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
func shouldIncludeEc2Subnet(subnet *ec2.Subnet, firstSeenTime *time.Time, configObj config.Config) bool { | ||
var subnetName string | ||
tagMap := util.ConvertEC2TagsToMap(subnet.Tags) | ||
if name, ok := tagMap["Name"]; ok { | ||
subnetName = name | ||
} | ||
|
||
return configObj.EC2Subnet.ShouldInclude(config.ResourceValue{ | ||
Name: &subnetName, | ||
Time: firstSeenTime, | ||
Tags: tagMap, | ||
}) | ||
} | ||
|
||
// Returns a formatted string of EC2 subnets | ||
func (ec2subnet *EC2Subnet) getAll(_ context.Context, configObj config.Config) ([]*string, error) { | ||
result := []*string{} | ||
err := ec2subnet.Client.DescribeSubnetsPages(&ec2.DescribeSubnetsInput{}, func(pages *ec2.DescribeSubnetsOutput, lastPage bool) bool { | ||
for _, subnet := range pages.Subnets { | ||
|
||
// check first seen tag | ||
firstSeenTime, err := ec2subnet.getFirstSeenTag(*subnet) | ||
if err != nil { | ||
logging.Errorf( | ||
"Unable to retrieve tags for Subnet: %s, with error: %s", *subnet.SubnetId, err) | ||
continue | ||
} | ||
|
||
// if the first seen tag is not there, then create one | ||
if firstSeenTime == nil { | ||
now := time.Now().UTC() | ||
firstSeenTime = &now | ||
if err := ec2subnet.setFirstSeenTag(*subnet, time.Now().UTC()); err != nil { | ||
logging.Errorf( | ||
"Unable to apply first seen tag Subnet: %s, with error: %s", *subnet.SubnetId, err) | ||
continue | ||
} | ||
} | ||
|
||
if shouldIncludeEc2Subnet(subnet, firstSeenTime, configObj) { | ||
result = append(result, subnet.SubnetId) | ||
} | ||
} | ||
return !lastPage | ||
}) | ||
|
||
if err != nil { | ||
return nil, errors.WithStackTrace(err) | ||
} | ||
|
||
// check the resources are nukable | ||
ec2subnet.VerifyNukablePermissions(result, func(id *string) error { | ||
params := &ec2.DeleteSubnetInput{ | ||
SubnetId: id, | ||
DryRun: aws.Bool(true), // dry run set as true , checks permission without actualy making the request | ||
} | ||
_, err := ec2subnet.Client.DeleteSubnet(params) | ||
return err | ||
}) | ||
|
||
return result, nil | ||
} | ||
|
||
// Deletes all Subnets | ||
func (ec2subnet *EC2Subnet) nukeAll(ids []*string) error { | ||
if len(ids) == 0 { | ||
logging.Debugf("No Subnets to nuke in region %s", ec2subnet.Region) | ||
return nil | ||
} | ||
|
||
logging.Debugf("Deleting all Subnets in region %s", ec2subnet.Region) | ||
var deletedAddresses []*string | ||
|
||
for _, id := range ids { | ||
// check the id has the permission to nuke, if not. continue the execution | ||
if nukable, err := ec2subnet.IsNukable(*id); !nukable { | ||
// not adding the report on final result hence not adding a record entry here | ||
// NOTE: We can skip the error checking and return it here, since it is already being checked while | ||
// displaying the identifiers. Here, `err` refers to the error indicating whether the identifier is eligible for nuke or not, | ||
// and it is not a programming error. | ||
logging.Debugf("[Skipping] %s nuke because %v", *id, err) | ||
continue | ||
} | ||
|
||
_, err := ec2subnet.Client.DeleteSubnet(&ec2.DeleteSubnetInput{ | ||
SubnetId: id, | ||
}) | ||
|
||
// Record status of this resource | ||
e := report.Entry{ | ||
Identifier: aws.StringValue(id), | ||
ResourceType: "Subnet", | ||
Error: err, | ||
} | ||
report.Record(e) | ||
|
||
if err != nil { | ||
logging.Debugf("[Failed] %s", err) | ||
} else { | ||
deletedAddresses = append(deletedAddresses, id) | ||
logging.Debugf("Deleted Subnet: %s", *id) | ||
} | ||
} | ||
|
||
logging.Debugf("[OK] %d EC2 Subnet(s) deleted in %s", len(deletedAddresses), ec2subnet.Region) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package resources | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
"testing" | ||
"time" | ||
|
||
"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/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/telemetry" | ||
"github.com/gruntwork-io/cloud-nuke/util" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type mockedEC2Subnets struct { | ||
ec2iface.EC2API | ||
DescribeSubnetsOutput ec2.DescribeSubnetsOutput | ||
DeleteSubnetOutput ec2.DeleteSubnetOutput | ||
} | ||
|
||
func (m mockedEC2Subnets) DescribeSubnetsPages(_ *ec2.DescribeSubnetsInput, callback func(pages *ec2.DescribeSubnetsOutput, lastPage bool) bool) error { | ||
callback(&m.DescribeSubnetsOutput, true) | ||
return nil | ||
} | ||
func (m mockedEC2Subnets) DeleteSubnet(_ *ec2.DeleteSubnetInput) (*ec2.DeleteSubnetOutput, error) { | ||
return &m.DeleteSubnetOutput, nil | ||
} | ||
|
||
func TestEc2Subnets_GetAll(t *testing.T) { | ||
telemetry.InitTelemetry("cloud-nuke", "") | ||
t.Parallel() | ||
|
||
var ( | ||
now = time.Now() | ||
subnet1 = "subnet-0631b58700ba3db41" | ||
testName1 = "cloud-nuke-subnet-001" | ||
subnet2 = "subnet-0631b58700ba3db42" | ||
testName2 = "cloud-nuke-subnet-002" | ||
) | ||
|
||
ec2subnet := EC2Subnet{ | ||
Client: mockedEC2Subnets{ | ||
DescribeSubnetsOutput: ec2.DescribeSubnetsOutput{ | ||
Subnets: []*ec2.Subnet{ | ||
{ | ||
SubnetId: aws.String(subnet1), | ||
Tags: []*ec2.Tag{ | ||
{ | ||
Key: aws.String("Name"), | ||
Value: aws.String(testName1), | ||
}, { | ||
Key: aws.String(util.FirstSeenTagKey), | ||
Value: aws.String(util.FormatTimestamp(now)), | ||
}, | ||
}, | ||
}, | ||
{ | ||
SubnetId: aws.String(subnet2), | ||
Tags: []*ec2.Tag{ | ||
{ | ||
Key: aws.String("Name"), | ||
Value: aws.String(testName2), | ||
}, { | ||
Key: aws.String(util.FirstSeenTagKey), | ||
Value: aws.String(util.FormatTimestamp(now.Add(1))), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
ec2subnet.BaseAwsResource.Init(nil) | ||
|
||
tests := map[string]struct { | ||
configObj config.ResourceType | ||
expected []string | ||
}{ | ||
"emptyFilter": { | ||
configObj: config.ResourceType{}, | ||
expected: []string{subnet1, subnet2}, | ||
}, | ||
"nameExclusionFilter": { | ||
configObj: config.ResourceType{ | ||
ExcludeRule: config.FilterRule{ | ||
NamesRegExp: []config.Expression{{ | ||
RE: *regexp.MustCompile(testName1), | ||
}}}, | ||
}, | ||
expected: []string{subnet2}, | ||
}, | ||
"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 := ec2subnet.getAll(context.Background(), config.Config{ | ||
EC2Subnet: tc.configObj, | ||
}) | ||
require.NoError(t, err) | ||
require.Equal(t, tc.expected, aws.StringValueSlice(names)) | ||
}) | ||
} | ||
|
||
} | ||
|
||
func TestEc2Subnet_NukeAll(t *testing.T) { | ||
telemetry.InitTelemetry("cloud-nuke", "") | ||
t.Parallel() | ||
|
||
tgw := EC2Subnet{ | ||
Client: mockedEC2Subnets{ | ||
DeleteSubnetOutput: ec2.DeleteSubnetOutput{}, | ||
}, | ||
} | ||
|
||
err := tgw.nukeAll([]*string{aws.String("test-gateway")}) | ||
require.NoError(t, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
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" | ||
) | ||
|
||
// Ec2Subnet- represents all Subnets | ||
type EC2Subnet struct { | ||
BaseAwsResource | ||
Client ec2iface.EC2API | ||
Region string | ||
Subnets []string | ||
} | ||
|
||
func (es *EC2Subnet) Init(session *session.Session) { | ||
es.BaseAwsResource.Init(session) | ||
es.Client = ec2.New(session) | ||
} | ||
|
||
// ResourceName - the simple name of the aws resource | ||
func (es *EC2Subnet) ResourceName() string { | ||
return "ec2-subnet" | ||
} | ||
|
||
func (es *EC2Subnet) MaxBatchSize() int { | ||
// Tentative batch size to ensure AWS doesn't throttle | ||
return 49 | ||
} | ||
|
||
// ResourceIdentifiers - The ids of the subnets | ||
func (es *EC2Subnet) ResourceIdentifiers() []string { | ||
return es.Subnets | ||
} | ||
|
||
func (es *EC2Subnet) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { | ||
identifiers, err := es.getAll(c, configObj) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
es.Subnets = awsgo.StringValueSlice(identifiers) | ||
return es.Subnets, nil | ||
} | ||
|
||
// Nuke - nuke 'em all!!! | ||
func (es *EC2Subnet) Nuke(identifiers []string) error { | ||
if err := es.nukeAll(awsgo.StringSlice(identifiers)); err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters