-
-
Notifications
You must be signed in to change notification settings - Fork 359
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support resource EC2 Placement Groups (#758)
Signed-off-by: Frank Lichtenheld <[email protected]>
- Loading branch information
1 parent
ef5d750
commit 6c07bd2
Showing
7 changed files
with
281 additions
and
0 deletions.
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,95 @@ | ||
package resources | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
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/cloud-nuke/util" | ||
"github.com/gruntwork-io/gruntwork-cli/errors" | ||
"github.com/hashicorp/go-multierror" | ||
) | ||
|
||
// getAll extracts the list of existing ec2 placement groups | ||
func (p *EC2PlacementGroups) getAll(c context.Context, configObj config.Config) ([]*string, error) { | ||
var names []*string | ||
var firstSeenTime *time.Time | ||
|
||
result, err := p.Client.DescribePlacementGroupsWithContext(p.Context, &ec2.DescribePlacementGroupsInput{}) | ||
if err != nil { | ||
return nil, errors.WithStackTrace(err) | ||
} | ||
for _, placementGroup := range result.PlacementGroups { | ||
firstSeenTime, err = util.GetOrCreateFirstSeen(c, p.Client, placementGroup.GroupId, util.ConvertEC2TagsToMap(placementGroup.Tags)) | ||
if err != nil { | ||
logging.Error("Unable to retrieve tags") | ||
return nil, errors.WithStackTrace(err) | ||
} | ||
|
||
if configObj.EC2PlacementGroups.ShouldInclude(config.ResourceValue{ | ||
Name: placementGroup.GroupName, | ||
Time: firstSeenTime, | ||
Tags: util.ConvertEC2TagsToMap(placementGroup.Tags), | ||
}) { | ||
names = append(names, placementGroup.GroupName) | ||
} | ||
} | ||
|
||
// checking the nukable permissions | ||
p.VerifyNukablePermissions(names, func(name *string) error { | ||
_, err := p.Client.DeletePlacementGroupWithContext(p.Context, &ec2.DeletePlacementGroupInput{ | ||
GroupName: name, | ||
DryRun: awsgo.Bool(true), | ||
}) | ||
return err | ||
}) | ||
|
||
return names, nil | ||
} | ||
|
||
// deleteKeyPair is a helper method that deletes the given ec2 key pair. | ||
func (p *EC2PlacementGroups) deletePlacementGroup(placementGroupName *string) error { | ||
params := &ec2.DeletePlacementGroupInput{ | ||
GroupName: placementGroupName, | ||
} | ||
|
||
_, err := p.Client.DeletePlacementGroupWithContext(p.Context, params) | ||
if err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// nukeAllEc2KeyPairs attempts to delete given ec2 key pair IDs. | ||
func (p *EC2PlacementGroups) nukeAll(groupNames []*string) error { | ||
if len(groupNames) == 0 { | ||
logging.Infof("No EC2 placement groups to nuke in region %s", p.Region) | ||
return nil | ||
} | ||
|
||
logging.Infof("Terminating all EC2 placement groups in region %s", p.Region) | ||
|
||
deletedPlacementGroups := 0 | ||
var multiErr *multierror.Error | ||
for _, groupName := range groupNames { | ||
if nukable, reason := p.IsNukable(awsgo.StringValue(groupName)); !nukable { | ||
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(groupName), reason) | ||
continue | ||
} | ||
|
||
if err := p.deletePlacementGroup(groupName); err != nil { | ||
logging.Errorf("[Failed] %s", err) | ||
multiErr = multierror.Append(multiErr, err) | ||
} else { | ||
deletedPlacementGroups++ | ||
logging.Infof("Deleted EC2 Placement Group: %s", *groupName) | ||
} | ||
} | ||
|
||
logging.Infof("[OK] %d EC2 Placement Group(s) terminated", deletedPlacementGroups) | ||
return multiErr.ErrorOrNil() | ||
} |
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,121 @@ | ||
package resources | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
"testing" | ||
"time" | ||
|
||
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/gruntwork-io/cloud-nuke/util" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type mockedEC2PlacementGroups struct { | ||
ec2iface.EC2API | ||
DescribePlacementGroupsOutput ec2.DescribePlacementGroupsOutput | ||
DeletePlacementGroupOutput ec2.DeletePlacementGroupOutput | ||
} | ||
|
||
func (m mockedEC2PlacementGroups) DescribePlacementGroupsWithContext(_ awsgo.Context, _ *ec2.DescribePlacementGroupsInput, _ ...request.Option) (*ec2.DescribePlacementGroupsOutput, error) { | ||
return &m.DescribePlacementGroupsOutput, nil | ||
} | ||
|
||
func (m mockedEC2PlacementGroups) DeletePlacementGroupWithContext(_ awsgo.Context, _ *ec2.DeletePlacementGroupInput, _ ...request.Option) (*ec2.DeletePlacementGroupOutput, error) { | ||
return &m.DeletePlacementGroupOutput, nil | ||
} | ||
|
||
func TestEC2PlacementGroups_GetAll(t *testing.T) { | ||
|
||
t.Parallel() | ||
|
||
// Set excludeFirstSeenTag to false for testing | ||
ctx := context.WithValue(context.Background(), util.ExcludeFirstSeenTagKey, false) | ||
|
||
now := time.Now() | ||
testId1 := "test-group-id1" | ||
testName1 := "test-group1" | ||
testId2 := "test-group-id2" | ||
testName2 := "test-group2" | ||
p := EC2PlacementGroups{ | ||
Client: mockedEC2PlacementGroups{ | ||
DescribePlacementGroupsOutput: ec2.DescribePlacementGroupsOutput{ | ||
PlacementGroups: []*ec2.PlacementGroup{ | ||
{ | ||
GroupName: awsgo.String(testName1), | ||
GroupId: awsgo.String(testId1), | ||
Tags: []*ec2.Tag{{ | ||
Key: awsgo.String(util.FirstSeenTagKey), | ||
Value: awsgo.String(util.FormatTimestamp(now)), | ||
}}, | ||
}, | ||
{ | ||
GroupName: awsgo.String(testName2), | ||
GroupId: awsgo.String(testId2), | ||
Tags: []*ec2.Tag{{ | ||
Key: awsgo.String(util.FirstSeenTagKey), | ||
Value: awsgo.String(util.FormatTimestamp(now.Add(2 * time.Hour))), | ||
}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
tests := map[string]struct { | ||
ctx context.Context | ||
configObj config.ResourceType | ||
expected []string | ||
}{ | ||
"emptyFilter": { | ||
ctx: ctx, | ||
configObj: config.ResourceType{}, | ||
expected: []string{testName1, testName2}, | ||
}, | ||
"nameExclusionFilter": { | ||
ctx: ctx, | ||
configObj: config.ResourceType{ | ||
ExcludeRule: config.FilterRule{ | ||
NamesRegExp: []config.Expression{{ | ||
RE: *regexp.MustCompile(testName1), | ||
}}}, | ||
}, | ||
expected: []string{testName2}, | ||
}, | ||
"timeAfterExclusionFilter": { | ||
ctx: ctx, | ||
configObj: config.ResourceType{ | ||
ExcludeRule: config.FilterRule{ | ||
TimeAfter: awsgo.Time(now.Add(1 * time.Hour)), | ||
}}, | ||
expected: []string{testName1}, | ||
}, | ||
} | ||
for name, tc := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
names, err := p.getAll(tc.ctx, config.Config{ | ||
EC2PlacementGroups: tc.configObj, | ||
}) | ||
require.NoError(t, err) | ||
require.Equal(t, tc.expected, awsgo.StringValueSlice(names)) | ||
}) | ||
} | ||
} | ||
|
||
func TestEC2PlacementGroups_NukeAll(t *testing.T) { | ||
|
||
t.Parallel() | ||
|
||
h := EC2PlacementGroups{ | ||
Client: mockedEC2PlacementGroups{ | ||
DeletePlacementGroupOutput: ec2.DeletePlacementGroupOutput{}, | ||
}, | ||
} | ||
|
||
err := h.nukeAll([]*string{awsgo.String("test-group1"), awsgo.String("test-group2")}) | ||
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,60 @@ | ||
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" | ||
) | ||
|
||
type EC2PlacementGroups struct { | ||
BaseAwsResource | ||
Client ec2iface.EC2API | ||
Region string | ||
PlacementGroupNames []string | ||
} | ||
|
||
func (k *EC2PlacementGroups) Init(session *session.Session) { | ||
k.Client = ec2.New(session) | ||
} | ||
|
||
// ResourceName - the simple name of the aws resource | ||
func (k *EC2PlacementGroups) ResourceName() string { | ||
return "ec2-placement-groups" | ||
} | ||
|
||
// ResourceIdentifiers - IDs of the ec2 key pairs | ||
func (k *EC2PlacementGroups) ResourceIdentifiers() []string { | ||
return k.PlacementGroupNames | ||
} | ||
|
||
func (k *EC2PlacementGroups) MaxBatchSize() int { | ||
// Tentative batch size to ensure AWS doesn't throttle | ||
return 200 | ||
} | ||
|
||
func (k *EC2PlacementGroups) GetAndSetResourceConfig(configObj config.Config) config.ResourceType { | ||
return configObj.EC2PlacementGroups | ||
} | ||
|
||
func (k *EC2PlacementGroups) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { | ||
identifiers, err := k.getAll(c, configObj) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
k.PlacementGroupNames = awsgo.StringValueSlice(identifiers) | ||
return k.PlacementGroupNames, nil | ||
} | ||
|
||
func (k *EC2PlacementGroups) Nuke(identifiers []string) error { | ||
if err := k.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