Skip to content

Commit

Permalink
Merge pull request #475 from ekristen/s3-fixes
Browse files Browse the repository at this point in the history
refactor: s3
  • Loading branch information
ekristen authored Dec 24, 2024
2 parents b0c963f + 027cbe2 commit 9d839a8
Show file tree
Hide file tree
Showing 13 changed files with 464 additions and 296 deletions.
7 changes: 7 additions & 0 deletions docs/resources/s3-access-point.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ generated: true
S3AccessPoint
```

## Properties


- `ARN`: No Description
- `Alias`: No Description
- `Bucket`: No Description
- `Name`: No Description
- `NetworkOrigin`: 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.
Expand Down
4 changes: 4 additions & 0 deletions docs/resources/s3-bucket.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ AWS::S3::Bucket
## Properties


- `CreationDate`: No Description
- `Name`: No Description
- `ObjectLock`: No Description
- `tag:<key>:`: This resource has tags with property `Tags`. These are key/value pairs that are
added as their own property with the prefix of `tag:` (e.g. [tag:example: "value"])

!!! note - Using Properties
Properties are what [Filters](../config-filtering.md) are written against in your configuration. You use the property
Expand Down
5 changes: 5 additions & 0 deletions docs/resources/s3-multipart-upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ generated: true
S3MultipartUpload
```

## Properties


- `Bucket`: No Description
- `Key`: No Description
- `UploadID`: 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.
Expand Down
7 changes: 7 additions & 0 deletions docs/resources/s3-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ generated: true
S3Object
```

## Properties


- `Bucket`: No Description
- `CreationDate`: No Description
- `IsLatest`: No Description
- `Key`: No Description
- `VersionID`: 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.
Expand Down
46 changes: 24 additions & 22 deletions resources/s3-access-points.go → resources/s3-access-point.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/gotidy/ptr"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3control"

"github.com/ekristen/libnuke/pkg/registry"
Expand Down Expand Up @@ -46,46 +45,49 @@ func (l *S3AccessPointLister) List(_ context.Context, o interface{}) ([]resource

for _, accessPoint := range resp.AccessPointList {
resources = append(resources, &S3AccessPoint{
svc: svc,
accountID: opts.AccountID,
accessPoint: accessPoint,
svc: svc,
accountID: opts.AccountID,
Name: accessPoint.Name,
ARN: accessPoint.AccessPointArn,
Alias: accessPoint.Alias,
Bucket: accessPoint.Bucket,
NetworkOrigin: accessPoint.NetworkOrigin,
})
}

if resp.NextToken == nil {
break
}

params.NextToken = resp.NextToken
}

return resources, nil
}

type S3AccessPoint struct {
svc *s3control.S3Control
accountID *string
accessPoint *s3control.AccessPoint
svc *s3control.S3Control
accountID *string
Name *string
ARN *string
Alias *string
Bucket *string
NetworkOrigin *string
}

func (e *S3AccessPoint) Remove(_ context.Context) error {
_, err := e.svc.DeleteAccessPoint(&s3control.DeleteAccessPointInput{
AccountId: e.accountID,
Name: aws.String(*e.accessPoint.Name),
func (r *S3AccessPoint) Remove(_ context.Context) error {
_, err := r.svc.DeleteAccessPoint(&s3control.DeleteAccessPointInput{
AccountId: r.accountID,
Name: r.Name,
})
return err
}

func (e *S3AccessPoint) Properties() types.Properties {
properties := types.NewProperties()
properties.Set("AccessPointArn", e.accessPoint.AccessPointArn).
Set("Alias", e.accessPoint.Alias).
Set("Bucket", e.accessPoint.Bucket).
Set("Name", e.accessPoint.Name).
Set("NetworkOrigin", e.accessPoint.NetworkOrigin)

return properties
func (r *S3AccessPoint) Properties() types.Properties {
return types.NewPropertiesFromStruct(r).
Set("AccessPointArn", r.ARN) // TODO(ek): this is an alias, should be deprecated for ARN
}

func (e *S3AccessPoint) String() string {
return ptr.ToString(e.accessPoint.AccessPointArn)
func (r *S3AccessPoint) String() string {
return ptr.ToString(r.ARN) // TODO(ek): this should be the Name not the ARN
}
49 changes: 49 additions & 0 deletions resources/s3-access-point_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package resources

import (
"fmt"
"testing"

"github.com/gotidy/ptr"
"github.com/stretchr/testify/assert"
)

func TestS3AccessPointProperties(t *testing.T) {
tests := []struct {
accountID string
name string
alias string
bucket string
networkOrigin string
}{
{
accountID: "123456789012",
name: "test-access-point",
alias: "some-alias",
bucket: "some-bucket",
networkOrigin: "some-network-origin",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
obj := &S3AccessPoint{
accountID: ptr.String(tc.accountID),
ARN: ptr.String(fmt.Sprintf("arn:aws:s3:::%s:%s", tc.accountID, tc.name)),
Name: ptr.String(tc.name),
Alias: ptr.String(tc.alias),
Bucket: ptr.String(tc.bucket),
NetworkOrigin: ptr.String(tc.networkOrigin),
}

got := obj.Properties()
assert.Equal(t, tc.name, got.Get("Name"))
assert.Equal(t, fmt.Sprintf("arn:aws:s3:::%s:%s", tc.accountID, tc.name), got.Get("AccessPointArn"))
assert.Equal(t, tc.alias, got.Get("Alias"))
assert.Equal(t, tc.bucket, got.Get("Bucket"))
assert.Equal(t, tc.networkOrigin, got.Get("NetworkOrigin"))

assert.Equal(t, fmt.Sprintf("arn:aws:s3:::%s:%s", tc.accountID, tc.name), obj.String())
})
}
}
172 changes: 172 additions & 0 deletions resources/s3-bucket-helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package resources

import (
"context"
"time"

"github.com/gotidy/ptr"
"github.com/sirupsen/logrus"

"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"

"github.com/ekristen/aws-nuke/v3/pkg/awsmod"
)

func bypassGovernanceRetention(input *s3.DeleteObjectsInput) {
input.BypassGovernanceRetention = ptr.Bool(true)
}

type s3DeleteVersionListIterator struct {
Bucket *string
Paginator *s3.ListObjectVersionsPaginator
objects []s3types.ObjectVersion
lastNotify time.Time
BypassGovernanceRetention *bool
err error
}

func newS3DeleteVersionListIterator(
svc *s3.Client,
input *s3.ListObjectVersionsInput,
bypass bool,
opts ...func(*s3DeleteVersionListIterator)) awsmod.BatchDeleteIterator {
iter := &s3DeleteVersionListIterator{
Bucket: input.Bucket,
Paginator: s3.NewListObjectVersionsPaginator(svc, input),
BypassGovernanceRetention: ptr.Bool(bypass),
}

for _, opt := range opts {
opt(iter)
}

return iter
}

// Next will use the S3API client to iterate through a list of objects.
func (iter *s3DeleteVersionListIterator) Next() bool {
if len(iter.objects) > 0 {
iter.objects = iter.objects[1:]
if len(iter.objects) > 0 {
return true
}
}

if !iter.Paginator.HasMorePages() {
return false
}

page, err := iter.Paginator.NextPage(context.TODO())
if err != nil {
iter.err = err
return false
}

iter.objects = page.Versions
for _, entry := range page.DeleteMarkers {
iter.objects = append(iter.objects, s3types.ObjectVersion{
Key: entry.Key,
VersionId: entry.VersionId,
})
}

if len(iter.objects) > 500 && (iter.lastNotify.IsZero() || time.Since(iter.lastNotify) > 120*time.Second) {
logrus.Infof(
"S3Bucket: %s - empty bucket operation in progress, this could take a while, please be patient",
*iter.Bucket)
iter.lastNotify = time.Now().UTC()
}

return len(iter.objects) > 0
}

// Err will return the last known error from Next.
func (iter *s3DeleteVersionListIterator) Err() error {
return iter.err
}

// DeleteObject will return the current object to be deleted.
func (iter *s3DeleteVersionListIterator) DeleteObject() awsmod.BatchDeleteObject {
return awsmod.BatchDeleteObject{
Object: &s3.DeleteObjectInput{
Bucket: iter.Bucket,
Key: iter.objects[0].Key,
VersionId: iter.objects[0].VersionId,
BypassGovernanceRetention: iter.BypassGovernanceRetention,
},
}
}

type s3ObjectDeleteListIterator struct {
Bucket *string
Paginator *s3.ListObjectsV2Paginator
objects []s3types.Object
lastNotify time.Time
BypassGovernanceRetention bool
err error
}

func newS3ObjectDeleteListIterator(
svc *s3.Client,
input *s3.ListObjectsV2Input,
bypass bool,
opts ...func(*s3ObjectDeleteListIterator)) awsmod.BatchDeleteIterator {
iter := &s3ObjectDeleteListIterator{
Bucket: input.Bucket,
Paginator: s3.NewListObjectsV2Paginator(svc, input),
BypassGovernanceRetention: bypass,
}

for _, opt := range opts {
opt(iter)
}
return iter
}

// Next will use the S3API client to iterate through a list of objects.
func (iter *s3ObjectDeleteListIterator) Next() bool {
if len(iter.objects) > 0 {
iter.objects = iter.objects[1:]
if len(iter.objects) > 0 {
return true
}
}

if !iter.Paginator.HasMorePages() {
return false
}

page, err := iter.Paginator.NextPage(context.TODO())
if err != nil {
iter.err = err
return false
}

iter.objects = page.Contents

if len(iter.objects) > 500 && (iter.lastNotify.IsZero() || time.Since(iter.lastNotify) > 120*time.Second) {
logrus.Infof(
"S3Bucket: %s - empty bucket operation in progress, this could take a while, please be patient",
*iter.Bucket)
iter.lastNotify = time.Now().UTC()
}

return len(iter.objects) > 0
}

// Err will return the last known error from Next.
func (iter *s3ObjectDeleteListIterator) Err() error {
return iter.err
}

// DeleteObject will return the current object to be deleted.
func (iter *s3ObjectDeleteListIterator) DeleteObject() awsmod.BatchDeleteObject {
return awsmod.BatchDeleteObject{
Object: &s3.DeleteObjectInput{
Bucket: iter.Bucket,
Key: iter.objects[0].Key,
BypassGovernanceRetention: ptr.Bool(iter.BypassGovernanceRetention),
},
}
}
Loading

0 comments on commit 9d839a8

Please sign in to comment.