-
-
Notifications
You must be signed in to change notification settings - Fork 354
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add codedeploy application * use batch apis, add missing config * remove unused code * fix exclude after check * docs: update for codedeploy * add tests * refactor batching for readability * add async nuke * use uniqueId, instead of randomString * fix test config * fix merge
- Loading branch information
1 parent
8d9464b
commit b0d7647
Showing
7 changed files
with
432 additions
and
3 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,165 @@ | ||
package aws | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/codedeploy" | ||
"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" | ||
"github.com/hashicorp/go-multierror" | ||
) | ||
|
||
func getAllCodeDeployApplications(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]string, error) { | ||
svc := codedeploy.New(session) | ||
|
||
codeDeployApplicationsFilteredByName := []string{} | ||
|
||
err := svc.ListApplicationsPages(&codedeploy.ListApplicationsInput{}, func(page *codedeploy.ListApplicationsOutput, lastPage bool) bool { | ||
for _, application := range page.Applications { | ||
// Check if the CodeDeploy Application should be excluded by name as that information is available to us here. | ||
// CreationDate is not available in the ListApplications API call, so we can't filter by that here, but we do filter by it later. | ||
// By filtering the name here, we can reduce the number of BatchGetApplication API calls we have to make. | ||
if config.ShouldInclude(*application, configObj.CodeDeployApplications.IncludeRule.NamesRegExp, configObj.CodeDeployApplications.ExcludeRule.NamesRegExp) { | ||
codeDeployApplicationsFilteredByName = append(codeDeployApplicationsFilteredByName, *application) | ||
} | ||
} | ||
return !lastPage | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Check if the CodeDeploy Application should be excluded by CreationDate and return. | ||
// We have to do this after the ListApplicationsPages API call because CreationDate is not available in that call. | ||
return batchDescribeAndFilterCodeDeployApplications(session, codeDeployApplicationsFilteredByName, excludeAfter) | ||
} | ||
|
||
// batchDescribeAndFilterCodeDeployApplications - Describe the CodeDeploy Applications and filter out the ones that should be excluded by CreationDate. | ||
func batchDescribeAndFilterCodeDeployApplications(session *session.Session, identifiers []string, excludeAfter time.Time) ([]string, error) { | ||
svc := codedeploy.New(session) | ||
|
||
// BatchGetApplications can only take 100 identifiers at a time, so we have to break up the identifiers into chunks of 100. | ||
batchSize := 100 | ||
applicationNames := []string{} | ||
|
||
for { | ||
// if there are no identifiers left, then break out of the loop | ||
if len(identifiers) == 0 { | ||
break | ||
} | ||
|
||
// if the batch size is larger than the number of identifiers left, then set the batch size to the number of identifiers left | ||
if len(identifiers) < batchSize { | ||
batchSize = len(identifiers) | ||
} | ||
|
||
// get the next batch of identifiers | ||
batch := aws.StringSlice(identifiers[:batchSize]) | ||
// then using that batch of identifiers, get the applicationsinfo | ||
resp, err := svc.BatchGetApplications( | ||
&codedeploy.BatchGetApplicationsInput{ApplicationNames: batch}, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// for each applicationsinfo, check if it should be excluded by creation date | ||
for j := range resp.ApplicationsInfo { | ||
if shouldNukeByCreationTime(resp.ApplicationsInfo[j], excludeAfter) { | ||
applicationNames = append(applicationNames, *resp.ApplicationsInfo[j].ApplicationName) | ||
} | ||
} | ||
|
||
// reduce the identifiers by the batch size we just processed, note that the slice header is mutated here | ||
identifiers = identifiers[batchSize:] | ||
} | ||
|
||
return applicationNames, nil | ||
} | ||
|
||
// shouldNukeByCreationTime - Check if the CodeDeploy Application should be excluded by CreationDate. | ||
func shouldNukeByCreationTime(applicationInfo *codedeploy.ApplicationInfo, excludeAfter time.Time) bool { | ||
// If the CreationDate is nil, then we can't filter by it, so we return false as a precaution. | ||
if applicationInfo == nil || applicationInfo.CreateTime == nil { | ||
return false | ||
} | ||
|
||
// If the excludeAfter date is before the CreationDate, then we should not nuke the resource. | ||
if excludeAfter.Before(*applicationInfo.CreateTime) { | ||
return false | ||
} | ||
|
||
// Otherwise, the resource can be safely nuked. | ||
return true | ||
} | ||
|
||
func nukeAllCodeDeployApplications(session *session.Session, identifiers []string) error { | ||
svc := codedeploy.New(session) | ||
|
||
if len(identifiers) == 0 { | ||
logging.Logger.Debugf("No CodeDeploy Applications to nuke in region %s", *session.Config.Region) | ||
return nil | ||
} | ||
|
||
logging.Logger.Infof("Deleting CodeDeploy Applications in region %s", *session.Config.Region) | ||
|
||
var wg sync.WaitGroup | ||
errChan := make(chan error, len(identifiers)) | ||
|
||
for _, identifier := range identifiers { | ||
wg.Add(1) | ||
go nukeCodeDeployApplicationAsync(svc, &wg, errChan, identifier) | ||
} | ||
|
||
wg.Wait() | ||
close(errChan) | ||
|
||
var allErrors *multierror.Error | ||
for err := range errChan { | ||
allErrors = multierror.Append(allErrors, err) | ||
|
||
logging.Logger.Errorf("[Failed] Error deleting CodeDeploy Application: %s", err) | ||
telemetry.TrackEvent(commonTelemetry.EventContext{ | ||
EventName: "Error Nuking CodeDeploy Application", | ||
}, map[string]interface{}{ | ||
"region": *session.Config.Region, | ||
}) | ||
} | ||
|
||
finalErr := allErrors.ErrorOrNil() | ||
if finalErr != nil { | ||
return errors.WithStackTrace(finalErr) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func nukeCodeDeployApplicationAsync(svc *codedeploy.CodeDeploy, wg *sync.WaitGroup, errChan chan<- error, identifier string) { | ||
defer wg.Done() | ||
|
||
_, err := svc.DeleteApplication(&codedeploy.DeleteApplicationInput{ApplicationName: &identifier}) | ||
if err != nil { | ||
errChan <- err | ||
} | ||
|
||
// record the status of the nuke attempt | ||
e := report.Entry{ | ||
Identifier: identifier, | ||
ResourceType: "CodeDeploy Application", | ||
Error: err, | ||
} | ||
report.Record(e) | ||
|
||
if err == nil { | ||
logging.Logger.Debugf("[OK] Deleted CodeDeploy Application: %s", identifier) | ||
} else { | ||
logging.Logger.Debugf("[Failed] Error deleting CodeDeploy Application %s: %s", identifier, err) | ||
} | ||
} |
Oops, something went wrong.