Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletions aws/resource_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ func getRegisteredRegionalResources() []AwsResource {
resources.NewACM(),
resources.NewACMPCA(),
resources.NewAMIs(),
&resources.ApiGateway{},
&resources.ApiGatewayV2{},
&resources.ASGroups{},
resources.NewApiGateway(),
resources.NewApiGatewayV2(),
resources.NewASGroups(),
resources.NewAppRunnerService(),
resources.NewBackupVault(),
resources.NewManagedPrometheus(),
Expand All @@ -75,57 +75,57 @@ func getRegisteredRegionalResources() []AwsResource {
resources.NewCloudWatchLogGroups(),
&resources.CloudMapServices{},
&resources.CloudMapNamespaces{},
&resources.CodeDeployApplications{},
resources.NewCodeDeployApplications(),
resources.NewConfigServiceRecorders(),
&resources.ConfigServiceRule{},
resources.NewDataSyncTask(),
resources.NewDataSyncLocation(),
resources.NewDynamoDB(),
&resources.EBSVolumes{},
resources.NewEBSVolumes(),
resources.NewEBApplications(),
&resources.EC2Instances{},
&resources.EC2DedicatedHosts{},
resources.NewEC2Instances(),
resources.NewEC2DedicatedHosts(),
resources.NewEC2KeyPairs(),
resources.NewEC2PlacementGroups(),
&resources.TransitGateways{},
resources.NewTransitGateways(),
resources.NewTransitGatewaysRouteTables(),
// Note: nuking transitgateway vpc attachement before nuking the vpc since vpc could be associated with it.
resources.NewTransitGatewayPeeringAttachment(),
&resources.TransitGatewaysVpcAttachment{},
&resources.EC2Endpoints{},
resources.NewTransitGatewaysVpcAttachment(),
resources.NewEC2Endpoints(),
resources.NewECR(),
&resources.ECSClusters{},
&resources.ECSServices{},
resources.NewECSClusters(),
resources.NewECSServices(),
&resources.EgressOnlyInternetGateway{},
&resources.ElasticFileSystem{},
resources.NewElasticFileSystem(),
resources.NewEIPAddresses(),
&resources.EKSClusters{},
resources.NewEKSClusters(),
&resources.ElasticCacheServerless{},
&resources.Elasticaches{},
&resources.ElasticacheParameterGroups{},
&resources.ElasticacheSubnetGroups{},
&resources.LoadBalancers{},
&resources.LoadBalancersV2{},
resources.NewElasticaches(),
resources.NewElasticacheParameterGroups(),
resources.NewElasticacheSubnetGroups(),
resources.NewLoadBalancers(),
resources.NewLoadBalancersV2(),
resources.NewGuardDuty(),
resources.NewKinesisFirehose(),
resources.NewKinesisStreams(),
&resources.KmsCustomerKeys{},
&resources.LambdaFunctions{},
&resources.LambdaLayers{},
resources.NewKmsCustomerKeys(),
resources.NewLambdaFunctions(),
resources.NewLambdaLayers(),
resources.NewLaunchConfigs(),
resources.NewLaunchTemplates(),
&resources.MacieMember{},
&resources.MSKCluster{},
resources.NewMSKCluster(),
resources.NewNatGateways(),
&resources.OpenSearchDomains{},
resources.NewOpenSearchDomains(),
&resources.DBGlobalClusterMemberships{},
&resources.DBInstances{},
&resources.DBSubnetGroups{},
&resources.DBClusters{},
resources.NewDBInstances(),
resources.NewDBSubnetGroups(),
resources.NewDBClusters(),
resources.NewRdsProxy(),
resources.NewRdsSnapshot(),
&resources.RdsParameterGroup{},
&resources.RedshiftClusters{},
resources.NewRdsParameterGroup(),
resources.NewRedshiftClusters(),
&resources.RedshiftSnapshotCopyGrants{},
&resources.S3Buckets{},
&resources.S3AccessPoint{},
Expand All @@ -135,7 +135,7 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.SageMakerStudio{},
&resources.SageMakerEndpoint{},
resources.NewSecretsManagerSecrets(),
&resources.SecurityHub{},
resources.NewSecurityHub(),
resources.NewSesConfigurationSet(),
resources.NewSesEmailTemplates(),
resources.NewSesIdentities(),
Expand All @@ -160,9 +160,9 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.NetworkFirewallRuleGroup{},
&resources.NetworkFirewallTLSConfig{},
&resources.NetworkFirewallResourcePolicy{},
&resources.VPCLatticeServiceNetwork{},
resources.NewVPCLatticeServiceNetwork(),
&resources.VPCLatticeService{},
&resources.VPCLatticeTargetGroup{},
resources.NewVPCLatticeTargetGroup(),
// Note: VPCs must be deleted last after all resources that create network interfaces (EKS, ECS, etc.)
&resources.EC2VPCs{},
// Note: nuking EC2 DHCP options after nuking EC2 VPC because DHCP options could be associated with VPCs.
Expand Down
139 changes: 63 additions & 76 deletions aws/resources/apigateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,52 @@ import (
"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/resource"
"github.com/gruntwork-io/go-commons/errors"
"github.com/hashicorp/go-multierror"
)

func (gateway *ApiGateway) getAll(c context.Context, configObj config.Config) ([]*string, error) {
result, err := gateway.Client.GetRestApis(c, &apigateway.GetRestApisInput{})
// ApiGatewayAPI defines the interface for API Gateway (v1) operations.
type ApiGatewayAPI interface {
GetRestApis(ctx context.Context, params *apigateway.GetRestApisInput, optFns ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
GetDomainNames(ctx context.Context, params *apigateway.GetDomainNamesInput, optFns ...func(*apigateway.Options)) (*apigateway.GetDomainNamesOutput, error)
GetBasePathMappings(ctx context.Context, params *apigateway.GetBasePathMappingsInput, optFns ...func(*apigateway.Options)) (*apigateway.GetBasePathMappingsOutput, error)
DeleteBasePathMapping(ctx context.Context, params *apigateway.DeleteBasePathMappingInput, optFns ...func(*apigateway.Options)) (*apigateway.DeleteBasePathMappingOutput, error)
DeleteRestApi(ctx context.Context, params *apigateway.DeleteRestApiInput, optFns ...func(*apigateway.Options)) (*apigateway.DeleteRestApiOutput, error)
}

// NewApiGateway creates a new ApiGateway resource using the generic resource pattern.
func NewApiGateway() AwsResource {
return NewAwsResource(&resource.Resource[ApiGatewayAPI]{
ResourceTypeName: "apigateway",
BatchSize: 10,
InitClient: func(r *resource.Resource[ApiGatewayAPI], cfg any) {
awsCfg, ok := cfg.(aws.Config)
if !ok {
logging.Debugf("Invalid config type for ApiGateway client: expected aws.Config")
return
}
r.Scope.Region = awsCfg.Region
r.Client = apigateway.NewFromConfig(awsCfg)
},
ConfigGetter: func(c config.Config) config.ResourceType {
return c.APIGateway
},
Lister: listApiGateways,
Nuker: deleteApiGateways,
})
}

// listApiGateways retrieves all API Gateway (v1) REST APIs that match the config filters.
func listApiGateways(ctx context.Context, client ApiGatewayAPI, scope resource.Scope, cfg config.ResourceType) ([]*string, error) {
result, err := client.GetRestApis(ctx, &apigateway.GetRestApisInput{})
if err != nil {
return []*string{}, errors.WithStackTrace(err)
}

var IDs []*string
for _, api := range result.Items {
if configObj.APIGateway.ShouldInclude(config.ResourceValue{
if cfg.ShouldInclude(config.ResourceValue{
Name: api.Name,
Time: api.CreatedDate,
}) {
Expand All @@ -32,25 +65,26 @@ func (gateway *ApiGateway) getAll(c context.Context, configObj config.Config) ([
return IDs, nil
}

func (gateway *ApiGateway) nukeAll(identifiers []*string) error {
// deleteApiGateways deletes the provided API Gateway (v1) REST APIs.
func deleteApiGateways(ctx context.Context, client ApiGatewayAPI, scope resource.Scope, resourceType string, identifiers []*string) error {
if len(identifiers) == 0 {
logging.Debugf("No API Gateways (v1) to nuke in region %s", gateway.Region)
logging.Debugf("No API Gateways (v1) to nuke in region %s", scope.Region)
return nil
}

if len(identifiers) > 100 {
logging.Errorf("Nuking too many API Gateways (v1) at once (100): " +
"halting to avoid hitting AWS API rate limiting")
logging.Errorf("Nuking too many API Gateways (v1) at once (100): halting to avoid hitting AWS API rate limiting")
return TooManyApiGatewayErr{}
}

// There is no bulk delete Api Gateway API, so we delete the batch of gateways concurrently using goroutines
logging.Debugf("Deleting Api Gateways (v1) in region %s", gateway.Region)
logging.Debugf("Deleting Api Gateways (v1) in region %s", scope.Region)
wg := new(sync.WaitGroup)
wg.Add(len(identifiers))
errChans := make([]chan error, len(identifiers))
for i, apigwID := range identifiers {
errChans[i] = make(chan error, 1)
go gateway.nukeAsync(wg, errChans[i], apigwID)
go deleteApiGatewayAsync(ctx, client, scope.Region, wg, errChans[i], apigwID)
}
wg.Wait()

Expand All @@ -69,59 +103,16 @@ func (gateway *ApiGateway) nukeAll(identifiers []*string) error {
return nil
}

func (gateway *ApiGateway) getAttachedStageClientCerts(apigwID *string) ([]*string, error) {
var clientCerts []*string

// remove the client certificate attached with the stages
stages, err := gateway.Client.GetStages(gateway.Context, &apigateway.GetStagesInput{
RestApiId: apigwID,
})

if err != nil {
return nil, err
}
// get the stages attached client certificates
for _, stage := range stages.Item {
if stage.ClientCertificateId == nil {
logging.Debugf("Skipping certyficate for stage %s, certyficate ID is nil", *stage.StageName)
continue
}
clientCerts = append(clientCerts, stage.ClientCertificateId)
}
return clientCerts, nil
}

func (gateway *ApiGateway) removeAttachedClientCertificates(clientCerts []*string) error {

for _, cert := range clientCerts {
logging.Debugf("Deleting Client Certificate %s", *cert)
_, err := gateway.Client.DeleteClientCertificate(gateway.Context, &apigateway.DeleteClientCertificateInput{
ClientCertificateId: cert,
})
if err != nil {
logging.Errorf("[Failed] Error deleting Client Certificate %s", *cert)
return err
}
}
return nil
}

func (gateway *ApiGateway) nukeAsync(
func deleteApiGatewayAsync(
ctx context.Context, client ApiGatewayAPI, region string,
wg *sync.WaitGroup, errChan chan error, apigwID *string,
) {
defer wg.Done()

var err error

// Why defer?
// Defer error reporting, channel sending, and logging to ensure they run
// after function execution completes, regardless of success or failure.
// This ensures consistent reporting, prevents missed logs, and avoids
// duplicated code paths for error/success handling.
//
// See: https://go.dev/ref/spec#Defer_statements
// Defer error reporting, channel sending, and logging
defer func() {
// send the error data to channel
errChan <- err

// Record status of this resource
Expand All @@ -133,45 +124,34 @@ func (gateway *ApiGateway) nukeAsync(
report.Record(e)

if err == nil {
logging.Debugf("[OK] API Gateway (v1) %s deleted in %s", aws.ToString(apigwID), gateway.Region)
logging.Debugf("[OK] API Gateway (v1) %s deleted in %s", aws.ToString(apigwID), region)
} else {
logging.Debugf("[Failed] Error deleting API Gateway (v1) %s in %s", aws.ToString(apigwID), gateway.Region)
logging.Debugf("[Failed] Error deleting API Gateway (v1) %s in %s", aws.ToString(apigwID), region)
}
}()

// get the attached client certificates
var clientCerts []*string
clientCerts, err = gateway.getAttachedStageClientCerts(apigwID)
if err != nil {
return
}

// Check if the API Gateway has any associated API mappings.
// If so, remove them before deleting the API Gateway.
err = gateway.deleteAssociatedApiMappings(context.Background(), []*string{apigwID})
err = deleteAssociatedApiMappingsV1(ctx, client, []*string{apigwID})
if err != nil {
return
}

// delete the API Gateway
input := &apigateway.DeleteRestApiInput{RestApiId: apigwID}
_, err = gateway.Client.DeleteRestApi(gateway.Context, input)
if err != nil {
return
}

// When the rest-api endpoint delete successfully, then remove attached client certs
err = gateway.removeAttachedClientCertificates(clientCerts)
_, err = client.DeleteRestApi(ctx, input)
}

func (gateway *ApiGateway) deleteAssociatedApiMappings(ctx context.Context, identifiers []*string) error {
// deleteAssociatedApiMappingsV1 deletes API mappings for API Gateway v1.
// Named with V1 suffix to avoid conflict with similar function in apigatewayv2.go.
func deleteAssociatedApiMappingsV1(ctx context.Context, client ApiGatewayAPI, identifiers []*string) error {
// Convert identifiers to map to check if identifier is in list
identifierMap := make(map[string]struct{})
for _, identifier := range identifiers {
identifierMap[*identifier] = struct{}{}
}

domainNames, err := gateway.Client.GetDomainNames(ctx, &apigateway.GetDomainNamesInput{})
domainNames, err := client.GetDomainNames(ctx, &apigateway.GetDomainNamesInput{})
if err != nil {
logging.Debugf("Failed to get domain names: %s", err)
return errors.WithStackTrace(err)
Expand All @@ -180,7 +160,7 @@ func (gateway *ApiGateway) deleteAssociatedApiMappings(ctx context.Context, iden
logging.Debugf("Found %d domain name(s)", len(domainNames.Items))
for _, domain := range domainNames.Items {

apiMappings, err := gateway.Client.GetBasePathMappings(ctx, &apigateway.GetBasePathMappingsInput{
apiMappings, err := client.GetBasePathMappings(ctx, &apigateway.GetBasePathMappingsInput{
DomainName: domain.DomainName,
})

Expand All @@ -202,7 +182,7 @@ func (gateway *ApiGateway) deleteAssociatedApiMappings(ctx context.Context, iden

logging.Debugf("Deleting base path mapping for API %s on domain %s", *mapping.RestApiId, *domain.DomainName)

_, err := gateway.Client.DeleteBasePathMapping(ctx, &apigateway.DeleteBasePathMappingInput{
_, err := client.DeleteBasePathMapping(ctx, &apigateway.DeleteBasePathMappingInput{
DomainName: domain.DomainName,
BasePath: mapping.BasePath,
})
Expand All @@ -218,3 +198,10 @@ func (gateway *ApiGateway) deleteAssociatedApiMappings(ctx context.Context, iden
logging.Debug("Completed deletion of matching API mappings.")
return nil
}

// TooManyApiGatewayErr is returned when too many API Gateways are requested at once.
type TooManyApiGatewayErr struct{}

func (err TooManyApiGatewayErr) Error() string {
return "Too many Api Gateways requested at once."
}
Loading