Skip to content

Commit

Permalink
add support for nuking app runner service(s).
Browse files Browse the repository at this point in the history
  • Loading branch information
wakeful committed Jun 15, 2024
1 parent b2ce0a4 commit 170c7ff
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res

| Resource Family | Resource type |
|-------------------------|----------------------------------------------------------|
| App Runner | Service |
| EC2 | Auto scaling groups |
| EC2 | Elastic Load Balancers (v1 and v2) |
| EC2 | EBS Volumes |
Expand Down Expand Up @@ -547,6 +548,7 @@ of the file that are supported are listed here.
| apigatewayv2 | APIGatewayV2 | ✅ (API Name) | ✅ (Created Time) | ❌ | ✅ |
| accessanalyzer | AccessAnalyzer | ✅ (Analyzer Name) | ✅ (Created Time) | ❌ | ✅ |
| asg | AutoScalingGroup | ✅ (ASG Name) | ✅ (Created Time) | ✅ | ✅ |
| app-runner-service | AppRunnerService | ✅ (App Runner Service Name) | ✅ (Created Time) | ❌ | ✅ |
| backup-vault | BackupVault | ✅ (Backup Vault Name) | ✅ (Created Time) | ❌ | ✅ |
| cloudwatch-alarm | CloudWatchAlarm | ✅ (Alarm Name) | ✅ (AlarmConfigurationUpdated Time) | ❌ | ✅ |
| cloudwatch-dashboard | CloudWatchDashboard | ✅ (Dashboard Name) | ✅ (LastModified Time) | ❌ | ✅ |
Expand Down
1 change: 1 addition & 0 deletions aws/resource_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.ApiGateway{},
&resources.ApiGatewayV2{},
&resources.ASGroups{},
&resources.AppRunnerService{},
&resources.BackupVault{},
&resources.CloudtrailTrail{},
&resources.CloudWatchAlarms{},
Expand Down
72 changes: 72 additions & 0 deletions aws/resources/apprunner_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package resources

import (
"context"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/apprunner"
"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 (a *AppRunnerService) nukeAll(identifiers []*string) error {
if len(identifiers) == 0 {
logging.Debugf("[App Runner Service] No App Runner Services found in region %s", a.Region)
return nil
}

logging.Debugf("[App Runner Service] Deleting all App Runner Services in region %s", a.Region)
var deleted []*string

for _, identifier := range identifiers {
logging.Debugf("[App Runner Service] Deleting App Runner Service %s in region %s", *identifier, a.Region)

_, err := a.Client.DeleteServiceWithContext(a.Context, &apprunner.DeleteServiceInput{
ServiceArn: identifier,
})
if err != nil {
logging.Debugf("[App Runner Service] Error deleting App Runner Service %s in region %s", *identifier, a.Region)
} else {
deleted = append(deleted, identifier)
logging.Debugf("[App Runner Service] Deleted App Runner Service %s in region %s", *identifier, a.Region)
}

e := report.Entry{
Identifier: aws.StringValue(identifier),
ResourceType: a.ResourceName(),
Error: err,
}
report.Record(e)
}

logging.Debugf("[OK] %d App Runner Service(s) nuked in %s", len(deleted), a.Region)
return nil
}

func (a *AppRunnerService) getAll(c context.Context, configObj config.Config) ([]*string, error) {
var identifiers []*string
paginator := func(output *apprunner.ListServicesOutput, lastPage bool) bool {
for _, service := range output.ServiceSummaryList {
if configObj.AppRunnerService.ShouldInclude(config.ResourceValue{
Name: service.ServiceName,
Time: service.CreatedAt,
}) {
identifiers = append(identifiers, service.ServiceArn)
}
}
return !lastPage
}

param := &apprunner.ListServicesInput{
MaxResults: aws.Int64(19),
}

if err := a.Client.ListServicesPagesWithContext(c, param, paginator); err != nil {
logging.Debugf("[App Runner Service] Failed to list app runner services: %s", err)
return nil, errors.WithStackTrace(err)
}

return identifiers, nil
}
109 changes: 109 additions & 0 deletions aws/resources/apprunner_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package resources

import (
"context"
"fmt"
"regexp"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/apprunner"
"github.com/aws/aws-sdk-go/service/apprunner/apprunneriface"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type mockedAppRunnerService struct {
apprunneriface.AppRunnerAPI
ListServicesOutput apprunner.ListServicesOutput
DeleteServiceOutput apprunner.DeleteServiceOutput
}

func (m mockedAppRunnerService) ListServicesPagesWithContext(_ aws.Context, _ *apprunner.ListServicesInput, callback func(*apprunner.ListServicesOutput, bool) bool, _ ...request.Option) error {
callback(&m.ListServicesOutput, true)
return nil
}

func (m mockedAppRunnerService) DeleteServiceWithContext(aws.Context, *apprunner.DeleteServiceInput, ...request.Option) (*apprunner.DeleteServiceOutput, error) {
return &m.DeleteServiceOutput, nil
}

func Test_AppRunnerService_GetAll(t *testing.T) {

t.Parallel()

testName1 := "test-service-1"
testName2 := "test-service-2"
now := time.Now()
service := AppRunnerService{
Client: mockedAppRunnerService{
ListServicesOutput: apprunner.ListServicesOutput{
ServiceSummaryList: []*apprunner.ServiceSummary{
{
ServiceName: &testName1,
ServiceArn: aws.String(fmt.Sprintf("arn::%s", testName1)),
CreatedAt: &now,
},
{
ServiceName: &testName2,
ServiceArn: aws.String(fmt.Sprintf("arn::%s", testName2)),
CreatedAt: aws.Time(now.Add(1)),
},
},
},
},
}

tests := map[string]struct {
configObj config.ResourceType
expected []string
}{
"emptyFilter": {
configObj: config.ResourceType{},
expected: []string{fmt.Sprintf("arn::%s", testName1), fmt.Sprintf("arn::%s", testName2)},
},
"nameExclusionFilter": {
configObj: config.ResourceType{
ExcludeRule: config.FilterRule{
NamesRegExp: []config.Expression{{
RE: *regexp.MustCompile(testName1),
}},
}},
expected: []string{fmt.Sprintf("arn::%s", testName2)},
},
"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 := service.getAll(context.Background(), config.Config{
AppRunnerService: tc.configObj,
})
require.NoError(t, err)
require.Equal(t, tc.expected, aws.StringValueSlice(names))
})
}
}

func TestAppRunnerService_NukeAll(t *testing.T) {

t.Parallel()

testName := "test-app-runner-service"
service := AppRunnerService{
Client: mockedAppRunnerService{
DeleteServiceOutput: apprunner.DeleteServiceOutput{},
},
}

err := service.nukeAll([]*string{&testName})
assert.NoError(t, err)
}
51 changes: 51 additions & 0 deletions aws/resources/apprunner_service_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package resources

import (
"context"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/apprunner"
"github.com/aws/aws-sdk-go/service/apprunner/apprunneriface"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/go-commons/errors"
)

type AppRunnerService struct {
BaseAwsResource
Client apprunneriface.AppRunnerAPI
Region string
AppRunners []string
}

func (a *AppRunnerService) GetAndSetResourceConfig(configObj config.Config) config.ResourceType {
return configObj.AppRunnerService
}

func (a *AppRunnerService) Init(session *session.Session) {
a.Client = apprunner.New(session)
}

func (a *AppRunnerService) ResourceName() string { return "app-runner-service" }

func (a *AppRunnerService) ResourceIdentifiers() []string { return a.AppRunners }

func (a *AppRunnerService) MaxBatchSize() int { return 19 }

func (a *AppRunnerService) Nuke(identifiers []string) error {
if err := a.nukeAll(aws.StringSlice(identifiers)); err != nil {
return errors.WithStackTrace(err)
}

return nil
}

func (a *AppRunnerService) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
identifiers, err := a.getAll(c, configObj)
if err != nil {
return nil, err
}

a.AppRunners = aws.StringValueSlice(identifiers)
return a.AppRunners, nil
}
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Config struct {
APIGatewayV2 ResourceType `yaml:"APIGatewayV2"`
AccessAnalyzer ResourceType `yaml:"AccessAnalyzer"`
AutoScalingGroup ResourceType `yaml:"AutoScalingGroup"`
AppRunnerService ResourceType `yaml:"AppRunnerService"`
BackupVault ResourceType `yaml:"BackupVault"`
CloudWatchAlarm ResourceType `yaml:"CloudWatchAlarm"`
CloudWatchDashboard ResourceType `yaml:"CloudWatchDashboard"`
Expand Down

0 comments on commit 170c7ff

Please sign in to comment.