Skip to content

Commit

Permalink
feat(app): add OldObjectsOnly option (#146)
Browse files Browse the repository at this point in the history
* feat(all): add OldObjectsOnly option

* change docs

* change docs

* change option name

* refactor

* change doc

* change doc
  • Loading branch information
go-to-k authored Dec 12, 2023
1 parent 7f7dbfb commit dcf1462
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 43 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This tool allows you to **search for bucket names** and empty or delete **multip

## Features

### Delete bucket option
### Bucket deletion option

Initially, the tool was intended to "empty the bucket", but since I was going to go through the trouble, I also added an option (`-f|--force`) to **"delete the bucket as well"**.

Expand All @@ -34,6 +34,14 @@ In deleting multiple buckets, you can delete them all at once, even if they are

**Even if versioning is turned on**, you can empty it just as if it were turned off. Therefore, you can use it **without** being aware of the versioning settings.

### Deletion of old version objects only

The `-o | --oldVersionsOnly` option allows you to **delete only old versions** and all delete-markers **without new versions and a bucket itself**.

**So you can retain the latest version objects only.**

This option cannot be specified with the `-f | --force` option.

### Number of objects that can be deleted

The delete-objects API provided by the CLI and SDK has a limit of **"the number of objects that can be deleted in one command is limited to 1000"**, but **This tool has no limit on the number**.
Expand Down Expand Up @@ -112,7 +120,7 @@ INF testgoto1 Clearing...
## How to use

```bash
cls3 -b <bucketName> [-b <bucketName>] [-p <profile>] [-r <region>] [-f|--force] [-i|--interactive] [-q|--quiet]
cls3 -b <bucketName> [-b <bucketName>] [-p <profile>] [-r <region>] [-f|--force] [-i|--interactive] [-q|--quiet] [-o|--oldVersionsOnly]
```

- -b, --bucketName: optional
Expand All @@ -132,6 +140,9 @@ INF testgoto1 Clearing...
- Interactive Mode for buckets selection
- -q, --quiet: optional
- Not to display a progress bar
- -o, --oldVersionsOnly: optional
- Delete old version objects only (including all delete-markers)
- Do not specify the `-f` option if you specify this option.

## Interactive Mode

Expand Down
14 changes: 13 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type App struct {
ForceMode bool
InteractiveMode bool
Quiet bool
OldVersionsOnly bool
}

func NewApp(version string) *App {
Expand Down Expand Up @@ -73,6 +74,13 @@ func NewApp(version string) *App {
Usage: "Not to display a progress bar",
Destination: &app.Quiet,
},
&cli.BoolFlag{
Name: "oldVersionsOnly",
Aliases: []string{"o"},
Value: false,
Usage: "Delete old version objects only (including all delete-markers)",
Destination: &app.OldVersionsOnly,
},
},
}

Expand All @@ -97,6 +105,10 @@ func (a *App) getAction() func(c *cli.Context) error {
errMsg := fmt.Sprintln("When specifying -i, do not specify the -b option.")
return fmt.Errorf("InvalidOptionError: %v", errMsg)
}
if a.ForceMode && a.OldVersionsOnly {
errMsg := fmt.Sprintln("When specifying -o, do not specify the -f option.")
return fmt.Errorf("InvalidOptionError: %v", errMsg)
}

config, err := client.LoadAWSConfig(c.Context, a.Region, a.Profile)
if err != nil {
Expand Down Expand Up @@ -126,7 +138,7 @@ func (a *App) getAction() func(c *cli.Context) error {
}

for _, bucket := range a.BucketNames.Value() {
if err := s3Wrapper.ClearS3Objects(c.Context, bucket, a.ForceMode, a.Quiet); err != nil {
if err := s3Wrapper.ClearS3Objects(c.Context, bucket, a.ForceMode, a.Quiet, a.OldVersionsOnly); err != nil {
return err
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/wrapper/s3_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewS3Wrapper(client client.IS3) *S3Wrapper {
}
}

func (s *S3Wrapper) ClearS3Objects(ctx context.Context, bucketName string, forceMode bool, quiet bool) error {
func (s *S3Wrapper) ClearS3Objects(ctx context.Context, bucketName string, forceMode bool, quiet bool, oldVersionsOnly bool) error {
exists, err := s.client.CheckBucketExists(ctx, aws.String(bucketName))
if err != nil {
return err
Expand All @@ -37,7 +37,7 @@ func (s *S3Wrapper) ClearS3Objects(ctx context.Context, bucketName string, force

io.Logger.Info().Msgf("%v Checking...", bucketName)

versions, err := s.client.ListObjectVersions(ctx, aws.String(bucketName), region)
versions, err := s.client.ListObjectVersions(ctx, aws.String(bucketName), region, oldVersionsOnly)
if err != nil {
return err
}
Expand Down
20 changes: 10 additions & 10 deletions internal/wrapper/s3_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(
[]types.ObjectIdentifier{
{
Key: aws.String("KeyForVersions"),
Expand All @@ -72,7 +72,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(
[]types.ObjectIdentifier{
{
Key: aws.String("KeyForVersions"),
Expand All @@ -99,7 +99,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(
[]types.ObjectIdentifier{
{
Key: aws.String("KeyForVersions"),
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(nil, fmt.Errorf("ListObjectVersionsError"))
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(nil, fmt.Errorf("ListObjectVersionsError"))
},
want: fmt.Errorf("ListObjectVersionsError"),
wantErr: true,
Expand All @@ -186,7 +186,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(
[]types.ObjectIdentifier{
{
Key: aws.String("KeyForVersions"),
Expand All @@ -213,7 +213,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(
[]types.ObjectIdentifier{
{
Key: aws.String("KeyForVersions"),
Expand Down Expand Up @@ -249,7 +249,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return(
[]types.ObjectIdentifier{
{
Key: aws.String("KeyForVersions"),
Expand Down Expand Up @@ -277,7 +277,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return([]types.ObjectIdentifier{}, nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return([]types.ObjectIdentifier{}, nil)
m.EXPECT().DeleteBucket(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(nil)
},
want: nil,
Expand All @@ -294,7 +294,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {
prepareMockFn: func(m *client.MockIS3) {
m.EXPECT().CheckBucketExists(gomock.Any(), aws.String("test")).Return(true, nil)
m.EXPECT().GetBucketLocation(gomock.Any(), aws.String("test")).Return("ap-northeast-1", nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1").Return([]types.ObjectIdentifier{}, nil)
m.EXPECT().ListObjectVersions(gomock.Any(), aws.String("test"), "ap-northeast-1", false).Return([]types.ObjectIdentifier{}, nil)
m.EXPECT().DeleteBucket(gomock.Any(), aws.String("test"), "ap-northeast-1").Return(fmt.Errorf("DeleteBucketError"))
},
want: fmt.Errorf("DeleteBucketError"),
Expand All @@ -310,7 +310,7 @@ func TestS3Wrapper_ClearS3Objects(t *testing.T) {

s3 := NewS3Wrapper(s3Mock)

err := s3.ClearS3Objects(tt.args.ctx, tt.args.bucketName, tt.args.forceMode, tt.args.quiet)
err := s3.ClearS3Objects(tt.args.ctx, tt.args.bucketName, tt.args.forceMode, tt.args.quiet, false)
if (err != nil) != tt.wantErr {
t.Errorf("error = %#v, wantErr %#v", err.Error(), tt.wantErr)
return
Expand Down
7 changes: 5 additions & 2 deletions pkg/client/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var SleepTimeSecForS3 = 10
type IS3 interface {
DeleteBucket(ctx context.Context, bucketName *string, region string) error
DeleteObjects(ctx context.Context, bucketName *string, objects []types.ObjectIdentifier, region string, quiet bool) ([]types.Error, error)
ListObjectVersions(ctx context.Context, bucketName *string, region string) ([]types.ObjectIdentifier, error)
ListObjectVersions(ctx context.Context, bucketName *string, region string, oldVersionsOnly bool) ([]types.ObjectIdentifier, error)
CheckBucketExists(ctx context.Context, bucketName *string) (bool, error)
ListBuckets(ctx context.Context) ([]types.Bucket, error)
GetBucketLocation(ctx context.Context, bucketName *string) (string, error)
Expand Down Expand Up @@ -180,7 +180,7 @@ func (s *S3) DeleteObjects(ctx context.Context, bucketName *string, objects []ty
return errors, nil
}

func (s *S3) ListObjectVersions(ctx context.Context, bucketName *string, region string) ([]types.ObjectIdentifier, error) {
func (s *S3) ListObjectVersions(ctx context.Context, bucketName *string, region string, oldVersionsOnly bool) ([]types.ObjectIdentifier, error) {
var keyMarker *string
var versionIdMarker *string
objectIdentifiers := []types.ObjectIdentifier{}
Expand Down Expand Up @@ -212,6 +212,9 @@ func (s *S3) ListObjectVersions(ctx context.Context, bucketName *string, region
}

for _, version := range output.Versions {
if oldVersionsOnly && (version.IsLatest == nil || *version.IsLatest) {
continue
}
objectIdentifier := types.ObjectIdentifier{
Key: version.Key,
VersionId: version.VersionId,
Expand Down
8 changes: 4 additions & 4 deletions pkg/client/s3_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit dcf1462

Please sign in to comment.