diff --git a/docs/resources/s3-vectors-bucket.md b/docs/resources/s3-vectors-bucket.md new file mode 100644 index 00000000..2b2dd0d5 --- /dev/null +++ b/docs/resources/s3-vectors-bucket.md @@ -0,0 +1,38 @@ +--- +generated: true +--- + +# S3VectorsBucket + + +## Resource + +```text +S3VectorsBucket +``` + +## Properties + + +- `ARN`: No Description +- `Name`: No Description + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + +### DependsOn + +!!! important - Experimental Feature + This resource depends on a resource using the experimental feature. This means that the resource will + only be deleted if all the resources of a particular type are deleted first or reach a terminal state. + +- [S3VectorsIndex](./s3-vectors-index.md) diff --git a/docs/resources/s3-vectors-index.md b/docs/resources/s3-vectors-index.md new file mode 100644 index 00000000..01e3a5c3 --- /dev/null +++ b/docs/resources/s3-vectors-index.md @@ -0,0 +1,39 @@ +--- +generated: true +--- + +# S3VectorsIndex + + +## Resource + +```text +S3VectorsIndex +``` + +## Properties + + +- `BucketName`: No Description +- `IndexARN`: No Description +- `IndexName`: No Description + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. + +### DependsOn + +!!! important - Experimental Feature + This resource depends on a resource using the experimental feature. This means that the resource will + only be deleted if all the resources of a particular type are deleted first or reach a terminal state. + +- [S3VectorsVector](./s3-vectors-vector.md) diff --git a/docs/resources/s3-vectors-vector.md b/docs/resources/s3-vectors-vector.md new file mode 100644 index 00000000..ea318df0 --- /dev/null +++ b/docs/resources/s3-vectors-vector.md @@ -0,0 +1,31 @@ +--- +generated: true +--- + +# S3VectorsVector + + +## Resource + +```text +S3VectorsVector +``` + +## Properties + + +- `IndexName`: No Description +- `Key`: No Description +- `VectorBucketName`: No Description + +!!! note - Using Properties + Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property + names to write filters for what you want to **keep** and omit from the nuke process. + +### String Property + +The string representation of a resource is generally the value of the Name, ID or ARN field of the resource. Not all +resources support properties. To write a filter against the string representation, simply omit the `property` field in +the filter. + +The string value is always what is used in the output of the log format when a resource is identified. diff --git a/go.mod b/go.mod index 98daa4df..72273dcf 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.25.3 require ( github.com/aws/aws-sdk-go v1.55.8 - github.com/aws/aws-sdk-go-v2 v1.39.3 + github.com/aws/aws-sdk-go-v2 v1.39.4 github.com/aws/aws-sdk-go-v2/config v1.28.11 github.com/aws/aws-sdk-go-v2/credentials v1.17.71 github.com/aws/aws-sdk-go-v2/service/amp v1.36.0 @@ -28,6 +28,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53profiles v1.4.17 github.com/aws/aws-sdk-go-v2/service/s3 v1.72.3 github.com/aws/aws-sdk-go-v2/service/s3control v1.52.7 + github.com/aws/aws-sdk-go-v2/service/s3vectors v1.4.10 github.com/aws/aws-sdk-go-v2/service/shield v1.34.6 github.com/aws/aws-sdk-go-v2/service/ssmquicksetup v1.3.10 github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 @@ -51,8 +52,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.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // 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.13.0 // indirect diff --git a/go.sum b/go.sum index b8b1f882..2e6a2e6a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.39.3 h1:h7xSsanJ4EQJXG5iuW4UqgP7qBopLpj84mpkNx3wPjM= -github.com/aws/aws-sdk-go-v2 v1.39.3/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= +github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= 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= @@ -10,10 +10,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 h1:mj/bdWleWEh81DtpdHKkw41IrS+r3uw1J/VQtbwYYp8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10/go.mod h1:7+oEMxAZWP8gZCyjcm9VicI0M61Sx4DJtcGfKYv2yKQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 h1:wh+/mn57yhUrFtLIxyFPh2RgxgQz/u+Yrf7hiHGHqKY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10/go.mod h1:7zirD+ryp5gitJJ2m1BBux56ai8RIRDykXZrJSp540w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= 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= @@ -64,6 +64,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.72.3 h1:WZOmJfCDV+4tYacLxpiojoAdT5sxT github.com/aws/aws-sdk-go-v2/service/s3 v1.72.3/go.mod h1:xMekrnhmJ5aqmyxtmALs7mlvXw5xRh+eYjOjvrIIFJ4= github.com/aws/aws-sdk-go-v2/service/s3control v1.52.7 h1:cewH1fJ35N26pujUo8pXtqngf0QZio0ko62rT1x2Uak= github.com/aws/aws-sdk-go-v2/service/s3control v1.52.7/go.mod h1:zZ6ah0Hp8TqLZERFcwSQ2T5A4lMkX5vujkDvSkFiXh8= +github.com/aws/aws-sdk-go-v2/service/s3vectors v1.4.10 h1:hgJrhznAL6SjFZAqNIexiE9L7Zjc5PMGmwPWNtTE3zc= +github.com/aws/aws-sdk-go-v2/service/s3vectors v1.4.10/go.mod h1:gJNoydxeaa5Av62mqcKTcA/9oFJnnZRseWfDmPKfGv8= github.com/aws/aws-sdk-go-v2/service/shield v1.34.6 h1:AWKt4pVqiqzLIT3xoOThd0xT6dY1lSB+7yDcn0N3I48= github.com/aws/aws-sdk-go-v2/service/shield v1.34.6/go.mod h1:Io5NYTndCqsmL+vdfoQEkInZkbZn8gLloqEjGvng+7M= github.com/aws/aws-sdk-go-v2/service/ssmquicksetup v1.3.10 h1:3e9ZvkZB5NsDellLxPuaCJSeA5Hg7SeHY+Godotzizc= diff --git a/mkdocs.yml b/mkdocs.yml index e31040b4..66a11e42 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -580,6 +580,9 @@ nav: - S3 Bucket: resources/s3-bucket.md - S3 Multipart Upload: resources/s3-multipart-upload.md - S3 Object: resources/s3-object.md + - S3 Vectors Bucket: resources/s3-vectors-bucket.md + - S3 Vectors Index: resources/s3-vectors-index.md + - S3 Vectors Vector: resources/s3-vectors-vector.md - SNS Endpoint: resources/sns-endpoint.md - SNS Platform Application: resources/sns-platform-application.md - SNS Subscription: resources/sns-subscription.md diff --git a/resources/s3vectors-bucket.go b/resources/s3vectors-bucket.go new file mode 100644 index 00000000..b34c3c67 --- /dev/null +++ b/resources/s3vectors-bucket.go @@ -0,0 +1,76 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/s3vectors" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const S3VectorsBucketResource = "S3VectorsBucket" + +func init() { + registry.Register(®istry.Registration{ + Name: S3VectorsBucketResource, + Scope: nuke.Account, + Resource: &S3VectorsBucket{}, + Lister: &S3VectorsBucketLister{}, + DependsOn: []string{ + S3VectorsIndexResource, + }, + }) +} + +type S3VectorsBucketLister struct{} + +func (l *S3VectorsBucketLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := s3vectors.NewFromConfig(*opts.Config) + + var resources []resource.Resource + params := &s3vectors.ListVectorBucketsInput{} + + paginator := s3vectors.NewListVectorBucketsPaginator(svc, params) + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + return nil, err + } + + for _, bucket := range page.VectorBuckets { + resources = append(resources, &S3VectorsBucket{ + svc: svc, + Name: bucket.VectorBucketName, + ARN: bucket.VectorBucketArn, + }) + } + } + + return resources, nil +} + +type S3VectorsBucket struct { + svc *s3vectors.Client + Name *string + ARN *string +} + +func (r *S3VectorsBucket) Remove(ctx context.Context) error { + _, err := r.svc.DeleteVectorBucket(ctx, &s3vectors.DeleteVectorBucketInput{ + VectorBucketName: r.Name, + }) + return err +} + +func (r *S3VectorsBucket) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *S3VectorsBucket) String() string { + return *r.Name +} diff --git a/resources/s3vectors-bucket_test.go b/resources/s3vectors-bucket_test.go new file mode 100644 index 00000000..f128ceff --- /dev/null +++ b/resources/s3vectors-bucket_test.go @@ -0,0 +1,28 @@ +package resources + +import ( + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func Test_S3VectorsBucket_Properties(t *testing.T) { + bucket := &S3VectorsBucket{ + Name: ptr.String("my-vector-bucket"), + ARN: ptr.String("arn:aws:s3vectors:us-east-1:123456789012:bucket/my-vector-bucket"), + } + + properties := bucket.Properties() + + assert.Equal(t, "my-vector-bucket", properties.Get("Name")) + assert.Equal(t, "arn:aws:s3vectors:us-east-1:123456789012:bucket/my-vector-bucket", properties.Get("ARN")) +} + +func Test_S3VectorsBucket_String(t *testing.T) { + bucket := &S3VectorsBucket{ + Name: ptr.String("test-vector-bucket"), + } + + assert.Equal(t, "test-vector-bucket", bucket.String()) +} diff --git a/resources/s3vectors-index.go b/resources/s3vectors-index.go new file mode 100644 index 00000000..8622ba3f --- /dev/null +++ b/resources/s3vectors-index.go @@ -0,0 +1,96 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/s3vectors" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const S3VectorsIndexResource = "S3VectorsIndex" + +func init() { + registry.Register(®istry.Registration{ + Name: S3VectorsIndexResource, + Scope: nuke.Account, + Resource: &S3VectorsIndex{}, + Lister: &S3VectorsIndexLister{}, + DependsOn: []string{ + S3VectorsVectorResource, + }, + }) +} + +type S3VectorsIndexLister struct{} + +func (l *S3VectorsIndexLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := s3vectors.NewFromConfig(*opts.Config) + + var resources []resource.Resource + + // First, list all vector buckets + bucketsParams := &s3vectors.ListVectorBucketsInput{} + bucketsPaginator := s3vectors.NewListVectorBucketsPaginator(svc, bucketsParams) + + for bucketsPaginator.HasMorePages() { + bucketsPage, err := bucketsPaginator.NextPage(ctx) + if err != nil { + return nil, err + } + + // For each bucket, list all indexes + for _, bucket := range bucketsPage.VectorBuckets { + indexParams := &s3vectors.ListIndexesInput{ + VectorBucketName: bucket.VectorBucketName, + } + + paginator := s3vectors.NewListIndexesPaginator(svc, indexParams) + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + return nil, err + } + + for _, index := range page.Indexes { + resources = append(resources, &S3VectorsIndex{ + svc: svc, + BucketName: bucket.VectorBucketName, + IndexName: index.IndexName, + IndexARN: index.IndexArn, + }) + } + } + } + } + + return resources, nil +} + +type S3VectorsIndex struct { + svc *s3vectors.Client + BucketName *string + IndexName *string + IndexARN *string +} + +func (r *S3VectorsIndex) Remove(ctx context.Context) error { + _, err := r.svc.DeleteIndex(ctx, &s3vectors.DeleteIndexInput{ + VectorBucketName: r.BucketName, + IndexName: r.IndexName, + }) + return err +} + +func (r *S3VectorsIndex) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *S3VectorsIndex) String() string { + return *r.IndexName +} diff --git a/resources/s3vectors-index_test.go b/resources/s3vectors-index_test.go new file mode 100644 index 00000000..47f182d0 --- /dev/null +++ b/resources/s3vectors-index_test.go @@ -0,0 +1,31 @@ +package resources + +import ( + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func Test_S3VectorsIndex_Properties(t *testing.T) { + index := &S3VectorsIndex{ + BucketName: ptr.String("my-vector-bucket"), + IndexName: ptr.String("embeddings-index"), + IndexARN: ptr.String("arn:aws:s3vectors:us-east-1:123456789012:index/my-vector-bucket/embeddings-index"), + } + + properties := index.Properties() + + assert.Equal(t, "my-vector-bucket", properties.Get("BucketName")) + assert.Equal(t, "embeddings-index", properties.Get("IndexName")) + assert.Equal(t, "arn:aws:s3vectors:us-east-1:123456789012:index/my-vector-bucket/embeddings-index", properties.Get("IndexARN")) +} + +func Test_S3VectorsIndex_String(t *testing.T) { + index := &S3VectorsIndex{ + BucketName: ptr.String("test-bucket"), + IndexName: ptr.String("test-index"), + } + + assert.Equal(t, "test-index", index.String()) +} diff --git a/resources/s3vectors-vector.go b/resources/s3vectors-vector.go new file mode 100644 index 00000000..3383ac43 --- /dev/null +++ b/resources/s3vectors-vector.go @@ -0,0 +1,112 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/s3vectors" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const S3VectorsVectorResource = "S3VectorsVector" + +func init() { + registry.Register(®istry.Registration{ + Name: S3VectorsVectorResource, + Scope: nuke.Account, + Resource: &S3VectorsVector{}, + Lister: &S3VectorsVectorLister{}, + }) +} + +type S3VectorsVectorLister struct{} + +func (l *S3VectorsVectorLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + svc := s3vectors.NewFromConfig(*opts.Config) + + var resources []resource.Resource + + // First, list all vector buckets + bucketsParams := &s3vectors.ListVectorBucketsInput{} + bucketsPaginator := s3vectors.NewListVectorBucketsPaginator(svc, bucketsParams) + + for bucketsPaginator.HasMorePages() { + bucketsPage, err := bucketsPaginator.NextPage(ctx) + if err != nil { + return nil, err + } + + // For each bucket, list all indexes + for _, bucket := range bucketsPage.VectorBuckets { + indexParams := &s3vectors.ListIndexesInput{ + VectorBucketName: bucket.VectorBucketName, + } + + indexPaginator := s3vectors.NewListIndexesPaginator(svc, indexParams) + for indexPaginator.HasMorePages() { + indexPage, err := indexPaginator.NextPage(ctx) + if err != nil { + return nil, err + } + + // For each index, list all vectors + for _, index := range indexPage.Indexes { + vectorParams := &s3vectors.ListVectorsInput{ + VectorBucketName: bucket.VectorBucketName, + IndexName: index.IndexName, + ReturnMetadata: false, // Don't need metadata for deletion + ReturnData: false, // Don't need vector data for deletion + } + + vectorPaginator := s3vectors.NewListVectorsPaginator(svc, vectorParams) + for vectorPaginator.HasMorePages() { + vectorPage, err := vectorPaginator.NextPage(ctx) + if err != nil { + return nil, err + } + + for _, vector := range vectorPage.Vectors { + resources = append(resources, &S3VectorsVector{ + svc: svc, + VectorBucketName: bucket.VectorBucketName, + IndexName: index.IndexName, + Key: vector.Key, + }) + } + } + } + } + } + } + + return resources, nil +} + +type S3VectorsVector struct { + svc *s3vectors.Client + VectorBucketName *string + IndexName *string + Key *string +} + +func (r *S3VectorsVector) Remove(ctx context.Context) error { + _, err := r.svc.DeleteVectors(ctx, &s3vectors.DeleteVectorsInput{ + VectorBucketName: r.VectorBucketName, + IndexName: r.IndexName, + Keys: []string{*r.Key}, + }) + return err +} + +func (r *S3VectorsVector) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *S3VectorsVector) String() string { + return *r.Key +} diff --git a/resources/s3vectors-vector_test.go b/resources/s3vectors-vector_test.go new file mode 100644 index 00000000..34f4b0dd --- /dev/null +++ b/resources/s3vectors-vector_test.go @@ -0,0 +1,32 @@ +package resources + +import ( + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func Test_S3VectorsVector_Properties(t *testing.T) { + vector := &S3VectorsVector{ + VectorBucketName: ptr.String("my-vector-bucket"), + IndexName: ptr.String("embeddings-index"), + Key: ptr.String("document-123"), + } + + properties := vector.Properties() + + assert.Equal(t, "my-vector-bucket", properties.Get("VectorBucketName")) + assert.Equal(t, "embeddings-index", properties.Get("IndexName")) + assert.Equal(t, "document-123", properties.Get("Key")) +} + +func Test_S3VectorsVector_String(t *testing.T) { + vector := &S3VectorsVector{ + VectorBucketName: ptr.String("test-bucket"), + IndexName: ptr.String("test-index"), + Key: ptr.String("test-key"), + } + + assert.Equal(t, "test-key", vector.String()) +}