diff --git a/go.mod b/go.mod index 9f13ebb1..969b9f8d 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,13 @@ toolchain go1.24.0 require ( github.com/aws/aws-sdk-go v1.55.6 - github.com/aws/aws-sdk-go-v2 v1.36.2 + github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/aws/aws-sdk-go-v2/config v1.28.11 github.com/aws/aws-sdk-go-v2/credentials v1.17.60 github.com/aws/aws-sdk-go-v2/service/apigateway v1.28.12 github.com/aws/aws-sdk-go-v2/service/appsync v1.42.3 github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.11 + github.com/aws/aws-sdk-go-v2/service/dsql v1.1.1 github.com/aws/aws-sdk-go-v2/service/iam v1.38.10 github.com/aws/aws-sdk-go-v2/service/route53profiles v1.4.16 github.com/aws/aws-sdk-go-v2/service/s3 v1.72.3 @@ -38,8 +39,8 @@ require ( require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect diff --git a/go.sum b/go.sum index 432d3d8a..ecde585f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBW github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU= github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= github.com/aws/aws-sdk-go-v2/config v1.28.11 h1:7Ekru0IkRHRnSRWGQLnLN6i0o1Jncd0rHo2T130+tEQ= @@ -24,10 +26,14 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 h1:AmB5QxnD+fBFrg9LcqzkgF/CaYvMyU/BTlejG4t1S7Q= @@ -48,6 +54,8 @@ github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.10 h1:fdLh7eMf5mxtggx2nG0+ github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.10/go.mod h1:uBca+/1aH5v/RYWXqyymLrsbmx1vU9bBxeurlC627Gc= github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.11 h1:azwNdO35Sp6EtX1RTi+gjrdng88tb2LUA7arvoWO3TI= github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.11/go.mod h1:nr2/4ch+huedh56oGZClTeCVENvMBqeEkzkObAFCDQM= +github.com/aws/aws-sdk-go-v2/service/dsql v1.1.1 h1:h2YbGqsnzL36l2B/Bd/dvrHfrKo/b7HVpZykL3yIQn8= +github.com/aws/aws-sdk-go-v2/service/dsql v1.1.1/go.mod h1:StDU/D7R42LhrKp24PGzvxyKjjDm0lwo9JMwuy2qbo4= github.com/aws/aws-sdk-go-v2/service/iam v1.38.10 h1:u/MwkFwRkKRDvy7D76/khJTk8HMp4mC5sZKErU53jos= github.com/aws/aws-sdk-go-v2/service/iam v1.38.10/go.mod h1:Gid0WEVky3EWbkeXiS67kHhbiK+q3/wO/hvPh7plR0c= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= diff --git a/resources/dsql-cluster.go b/resources/dsql-cluster.go new file mode 100644 index 00000000..1c6c1e77 --- /dev/null +++ b/resources/dsql-cluster.go @@ -0,0 +1,166 @@ +package resources + +import ( + "context" + "errors" + "slices" + "time" + + "github.com/gotidy/ptr" + + "github.com/aws/aws-sdk-go-v2/service/dsql" + dsqltypes "github.com/aws/aws-sdk-go-v2/service/dsql/types" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + libsettings "github.com/ekristen/libnuke/pkg/settings" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const DSQLClusterResource = "DSQLCluster" + +func init() { + registry.Register(®istry.Registration{ + Name: DSQLClusterResource, + Scope: nuke.Account, + Resource: &DSQLCluster{}, + Lister: &DSQLClusterLister{}, + Settings: []string{ + "DisableDeletionProtection", + }, + }) +} + +type DSQLClusterLister struct{} + +func (l *DSQLClusterLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := dsql.NewFromConfig(*opts.Config) + var resources []resource.Resource + + if !l.IsSupportedRegion(opts.Region.Name) { + return resources, nil + } + + params := &dsql.ListClustersInput{ + MaxResults: ptr.Int32(100), + } + + for { + res, err := svc.ListClusters(ctx, params) + if err != nil { + return nil, err + } + + for _, clusterSummary := range res.Clusters { + // get additional cluster metadata not returned in ListClusters + cluster, err := svc.GetCluster(ctx, &dsql.GetClusterInput{ + Identifier: clusterSummary.Identifier, + }) + if err != nil { + return nil, err + } + // get cluster tags + var tags map[string]string + tagsRes, err := svc.ListTagsForResource(ctx, &dsql.ListTagsForResourceInput{ + ResourceArn: clusterSummary.Arn, + }) + if err != nil { + opts.Logger.Warnf("unable to fetch tags for dsql cluster: %s", ptr.ToString(clusterSummary.Arn)) + } else { + tags = tagsRes.Tags + } + + resources = append(resources, &DSQLCluster{ + svc: svc, + Arn: clusterSummary.Arn, + CreationTime: cluster.CreationTime, + DeletionProtectionEnabled: cluster.DeletionProtectionEnabled, + Identifier: clusterSummary.Identifier, + Status: cluster.Status, + Tags: tags, + }) + } + + if res.NextToken == nil { + break + } + + params.NextToken = res.NextToken + } + + return resources, nil +} + +func (l *DSQLClusterLister) IsSupportedRegion(region string) bool { + // https://aws.amazon.com/rds/aurora/dsql/faqs/#:~:text=available%20in%20all-,AWS%20Regions,-%3F + // NOTE: us-west-2 (Oregon) is available as a witness region, but clusters cannot be created in this region + supportedRegions := []string{ + "us-east-1", + "us-east-2", + } + + return slices.Contains(supportedRegions, region) +} + +type DSQLCluster struct { + svc *dsql.Client + settings *libsettings.Setting + Arn *string + CreationTime *time.Time + DeletionProtectionEnabled *bool + Identifier *string + Status dsqltypes.ClusterStatus + Tags map[string]string +} + +func (r *DSQLCluster) Remove(ctx context.Context) error { + err := r.RemoveDeletionProtection(ctx) + if err != nil { + return err + } + + _, err = r.svc.DeleteCluster(ctx, &dsql.DeleteClusterInput{ + Identifier: r.Identifier, + }) + + return err +} + +func (r *DSQLCluster) Filter() error { + if r.Status == dsqltypes.ClusterStatusDeleted { + return errors.New("dsql cluster already deleted") + } + + return nil +} + +func (r *DSQLCluster) RemoveDeletionProtection(ctx context.Context) error { + if !r.settings.GetBool("DisableDeletionProtection") { + return nil + } + + _, err := r.svc.UpdateCluster(ctx, &dsql.UpdateClusterInput{ + Identifier: r.Identifier, + DeletionProtectionEnabled: ptr.Bool(false), + }) + if err != nil { + return err + } + + return nil +} + +func (r *DSQLCluster) Settings(settings *libsettings.Setting) { + r.settings = settings +} + +func (r *DSQLCluster) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *DSQLCluster) String() string { + return *r.Arn +}