Skip to content

Commit

Permalink
add aws v2 sdk to support newer resources (#745) (#746)
Browse files Browse the repository at this point in the history
add support for Managed Prometheus
  • Loading branch information
wakeful authored Aug 26, 2024
1 parent 45e4ac8 commit 0c6e8ba
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
| CloudWatch | Alarms |
| OpenSearch | Domains |
| KMS | Custgomer managed keys (and associated key aliases) |
| Managed Prometheus | Prometheus Workspace |
| GuardDuty | Detectors |
| Macie | Member accounts |
| SageMaker | Notebook instances |
Expand Down Expand Up @@ -608,6 +609,7 @@ of the file that are supported are listed here.
| lt | LaunchTemplate | ✅ (Launch Template Name) | ✅ (Created Time) | ❌ | ✅ |
| macie-member | MacieMember | ❌ | ✅ (Creation Time) | ❌ | ✅ |
| msk-cluster | MSKCluster | ✅ (Cluster Name) | ✅ (Creation Time) | ❌ | ✅ |
| managed-prometheus | ManagedPrometheus | ✅ (Workspace Alias) | ✅ (Creation Time) | ✅ | ✅ |
| nat-gateway | NatGateway | ✅ (EC2 Name Tag) | ✅ (Creation Time) | ✅ | ✅ |
| network-acl | NetworkACL | ✅ (ACL Name Tag) | ✅ (Creation Time) | ✅ | ✅ |
| network-interface | NetworkInterface | ✅ (Interface Name Tag) | ✅ (Creation Time) | ✅ | ✅ |
Expand Down
27 changes: 27 additions & 0 deletions aws/region.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package aws

import (
"context"
"fmt"

awsgoV2 "github.com/aws/aws-sdk-go-v2/aws"
awsgoV2cfg "github.com/aws/aws-sdk-go-v2/config"
awsgoV2cred "github.com/aws/aws-sdk-go-v2/credentials"
awsgo "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
Expand Down Expand Up @@ -146,3 +151,25 @@ func GetTargetRegions(enabledRegions []string, selectedRegions []string, exclude
}
return targetRegions, nil
}

func Session2cfg(ctx context.Context, session *session.Session) (awsgoV2.Config, error) {
cfgV1 := session.Config
cred, err := cfgV1.Credentials.Get()
if err != nil {
return awsgoV2.Config{}, errors.WithStackTrace(err)
}

cfgV2, err := awsgoV2cfg.LoadDefaultConfig(ctx,
awsgoV2cfg.WithRegion(*cfgV1.Region),
awsgoV2cfg.WithCredentialsProvider(awsgoV2cred.NewStaticCredentialsProvider(
cred.AccessKeyID,
cred.SecretAccessKey,
cred.SessionToken,
)),
)
if err != nil {
return awsgoV2.Config{}, errors.WithStackTrace(err)
}

return cfgV2, nil
}
3 changes: 3 additions & 0 deletions aws/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import (
"context"
"strings"

awsgoV2 "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/gruntwork-io/cloud-nuke/config"
)

// AwsResource is an interface that represents a single AWS resource
type AwsResource interface {
Init(session *session.Session)
InitV2(cfg awsgoV2.Config)
ResourceName() string
ResourceIdentifiers() []string
MaxBatchSize() int
Nuke(identifiers []string) error
GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error)
IsNukable(string) (bool, error)
IsUsingV2() bool

PrepareContext(context.Context, config.ResourceType) error
GetAndSetResourceConfig(config.Config) config.ResourceType
Expand Down
27 changes: 26 additions & 1 deletion aws/resource_registry.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package aws

import (
"context"
"fmt"
"reflect"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/gruntwork-io/cloud-nuke/aws/resources"
"github.com/gruntwork-io/cloud-nuke/logging"
)

const Global = "global"
Expand Down Expand Up @@ -60,6 +63,7 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.ASGroups{},
&resources.AppRunnerService{},
&resources.BackupVault{},
&resources.ManagedPrometheus{},
&resources.CloudtrailTrail{},
&resources.CloudWatchAlarms{},
&resources.CloudWatchDashboards{},
Expand Down Expand Up @@ -164,7 +168,28 @@ func toAwsResourcesPointer(resources []AwsResource) []*AwsResource {

func initRegisteredResources(resources []*AwsResource, session *session.Session, region string) []*AwsResource {
for _, resource := range resources {
(*resource).Init(session)
if (*resource).IsUsingV2() {
v2Config, err := Session2cfg(context.Background(), session)
if err != nil {
logging.Debug(fmt.Sprintf(
"[aws sdk cfg] failed to convert v1 session into aws v2 config for resource %s: %v",
(*resource).ResourceName(),
err,
))
}

logging.Debug(fmt.Sprintf(
"[aws sdk cfg] using aws sdk v2 for resource %s",
(*resource).ResourceName(),
))
(*resource).InitV2(v2Config)
} else {
logging.Debug(fmt.Sprintf(
"[aws sdk cfg] using deprecated aws sdk v1 for resource %s",
(*resource).ResourceName(),
))
(*resource).Init(session)
}

// Note: only regional resources have the field `Region`, which is used for logging purposes only
setRegionForRegionalResource(resource, region)
Expand Down
10 changes: 10 additions & 0 deletions aws/resources/base_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"time"

awsgoV2 "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/cloud-nuke/util"
Expand All @@ -25,6 +26,11 @@ type BaseAwsResource struct {
func (br *BaseAwsResource) Init(_ *session.Session) {
br.Nukables = make(map[string]error)
}

func (br *BaseAwsResource) InitV2(cfg awsgoV2.Config) {
br.Nukables = make(map[string]error)
}

func (br *BaseAwsResource) ResourceName() string {
return "not implemented: ResourceName"
}
Expand Down Expand Up @@ -101,3 +107,7 @@ func (br *BaseAwsResource) IsNukable(identifier string) (bool, error) {

return true, nil
}

func (br *BaseAwsResource) IsUsingV2() bool {
return false
}
77 changes: 77 additions & 0 deletions aws/resources/managed_prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package resources

import (
"context"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/amp"
"github.com/aws/aws-sdk-go-v2/service/amp/types"
"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 *ManagedPrometheus) nukeAll(identifiers []*string) error {
if len(identifiers) == 0 {
logging.Debugf("[Managed Prometheus] No Prometheus Workspaces found in region %s", a.Region)
return nil
}

logging.Debugf("[Managed Prometheus] Deleting all Prometheus Workspaces in %s", a.Region)
var deleted []*string

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

_, err := a.Client.DeleteWorkspace(a.Context, &amp.DeleteWorkspaceInput{
WorkspaceId: identifier,
ClientToken: nil,
})
if err != nil {
logging.Debugf("[Managed Prometheus] Error deleting Workspace %s in region %s", *identifier, a.Region)
} else {
deleted = append(deleted, identifier)
logging.Debugf("[Managed Prometheus] Deleted Workspace %s in region %s", *identifier, a.Region)
}

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

logging.Debugf("[OK] %d Prometheus Workspace(s) deleted in %s", len(deleted), a.Region)
return nil
}

func (a *ManagedPrometheus) getAll(ctx context.Context, cnfObj config.Config) ([]*string, error) {
paginator := amp.NewListWorkspacesPaginator(a.Client, &amp.ListWorkspacesInput{})

var identifiers []*string
for paginator.HasMorePages() {
workspaces, err := paginator.NextPage(ctx)
if err != nil {
logging.Debugf("[Managed Prometheus] Failed to list workspaces: %s", err)
return nil, errors.WithStackTrace(err)
}

for _, workspace := range workspaces.Workspaces {
if workspace.Status.StatusCode != types.WorkspaceStatusCodeActive {
continue
}

if cnfObj.ManagedPrometheus.ShouldInclude(config.ResourceValue{
Name: workspace.Alias,
Time: workspace.CreatedAt,
Tags: workspace.Tags,
}) {
identifiers = append(identifiers, workspace.WorkspaceId)
}
}
}

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

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

"github.com/aws/aws-sdk-go-v2/service/amp"
"github.com/aws/aws-sdk-go-v2/service/amp/types"
"github.com/aws/aws-sdk-go/aws"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type mockedManagedPrometheusService struct {
ManagedPrometheusAPI
ListServiceOutput amp.ListWorkspacesOutput
DeleteWorkspaceOutput amp.DeleteWorkspaceOutput
}

func (m mockedManagedPrometheusService) ListWorkspaces(ctx context.Context, input *amp.ListWorkspacesInput, f ...func(*amp.Options)) (*amp.ListWorkspacesOutput, error) {
return &m.ListServiceOutput, nil
}

func (m mockedManagedPrometheusService) DeleteWorkspace(ctx context.Context, params *amp.DeleteWorkspaceInput, optFns ...func(*amp.Options)) (*amp.DeleteWorkspaceOutput, error) {
return &m.DeleteWorkspaceOutput, nil
}

func Test_ManagedPrometheus_GetAll(t *testing.T) {

t.Parallel()

now := time.Now()

workSpace1 := "test-workspace-1"
workSpace2 := "test-workspace-2"

service := ManagedPrometheus{
Client: mockedManagedPrometheusService{
ListServiceOutput: amp.ListWorkspacesOutput{
Workspaces: []types.WorkspaceSummary{
{
Arn: aws.String(fmt.Sprintf("arn::%s", workSpace1)),
Alias: aws.String(workSpace1),
CreatedAt: &now,
Status: &types.WorkspaceStatus{
StatusCode: types.WorkspaceStatusCodeActive,
},
WorkspaceId: aws.String(workSpace1),
},
{
Arn: aws.String(fmt.Sprintf("arn::%s", workSpace2)),
Alias: aws.String(workSpace2),
CreatedAt: aws.Time(now.Add(time.Hour)),
Status: &types.WorkspaceStatus{
StatusCode: types.WorkspaceStatusCodeActive,
},
WorkspaceId: aws.String(workSpace2),
},
},
},
},
}

tests := map[string]struct {
configObj config.ResourceType
expected []string
}{
"emptyFilter": {
configObj: config.ResourceType{},
expected: []string{workSpace1, workSpace2},
},
"nameExclusionFilter": {
configObj: config.ResourceType{
ExcludeRule: config.FilterRule{
NamesRegExp: []config.Expression{{
RE: *regexp.MustCompile(workSpace1),
}},
}},
expected: []string{workSpace2},
},
"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) {
workspaces, err := service.getAll(
context.Background(),
config.Config{ManagedPrometheus: tc.configObj},
)
require.NoError(t, err)
require.Equal(t, tc.expected, aws.StringValueSlice(workspaces))
})
}
}

func Test_ManagedPrometheus_NukeAll(t *testing.T) {

t.Parallel()

workspaceName := "test-workspace-1"
service := ManagedPrometheus{
Client: mockedManagedPrometheusService{
DeleteWorkspaceOutput: amp.DeleteWorkspaceOutput{},
},
}

err := service.nukeAll([]*string{&workspaceName})
assert.NoError(t, err)
}
Loading

0 comments on commit 0c6e8ba

Please sign in to comment.