Skip to content

Commit

Permalink
command/storage: add versioning support (#475)
Browse files Browse the repository at this point in the history
This commit adds versioning support to the s5cmd.

Added --all-versions flag to ls, rm, du and select subcommands
to apply operation on(/over) all versions of the objects.
Added --version-id flag to cat, cp/mv, rm, du and select
subcommands to apply operation on(/over) a specific versions of the object.
Added bucket-version command to configure bucket versioning. Bucket name
alone returns the bucket versioning status of the bucket. Bucket versioning can
be configured with set flag which only accepts.
Added --raw flag to cat and select subcommands. It disables the wildcard operations.
Note: Google Cloud Storage uses a different approach for versioning. So with current implementation, s5cmd cannot use or retrieve generation numbers . However, bucket-version command and du command with all-versions flag works as expected since they do not use version ids.

Fixes: #218
Fixes: #386
Fixes: #539
  • Loading branch information
kucukaslan authored Jun 16, 2023
1 parent f0ce87d commit 0993c6d
Show file tree
Hide file tree
Showing 27 changed files with 1,348 additions and 152 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
- Added `--profile` flag to allow users to specify a [named profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html). ([#353](https://github.com/peak/s5cmd/issues/353))
- Added `--credentials-file` flag to allow users to specify path for the AWS credentials file instead of using the [default location](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-where).
- Added `bench.py` script under new `benchmark` folder to compare performances of two different builds of s5cmd. ([#471](https://github.com/peak/s5cmd/pull/471))
- Added `--all-versions` flag to `ls`, `rm`, `du` and `select` subcommands to apply operation on(/over) all versions of the objects. ([#475](https://github.com/peak/s5cmd/pull/475))
- Added `--version-id` flag to `cat`, `cp`/`mv`, `rm`, `du` and `select` subcommands to apply operation on(/over) a specific versions of the object. ([#475](https://github.com/peak/s5cmd/pull/475))
- Added `bucket-version` command to configure bucket versioning. Bucket name
alone returns the bucket versioning status of the bucket. Bucket versioning can
be configured with `set` flag. ([#475](https://github.com/peak/s5cmd/pull/475))
- Added `--raw` flag to `cat` and `select` subcommands. It disables the wildcard operations. ([#475](https://github.com/peak/s5cmd/pull/475))

#### Improvements
- Disable AWS SDK logger if log level is not `trace`. ([##460](https://github.com/peak/s5cmd/pull/460))
Expand Down
1 change: 1 addition & 0 deletions command/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ func Commands() []*cli.Command {
NewRunCommand(),
NewSyncCommand(),
NewVersionCommand(),
NewBucketVersionCommand(),
}
}

Expand Down
151 changes: 151 additions & 0 deletions command/bucket_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package command

import (
"context"
"fmt"
"strings"

"github.com/urfave/cli/v2"

"github.com/peak/s5cmd/log"
"github.com/peak/s5cmd/storage"
"github.com/peak/s5cmd/storage/url"
"github.com/peak/s5cmd/strutil"
)

var bucketVersionHelpTemplate = `Name:
{{.HelpName}} - {{.Usage}}
Usage:
{{.HelpName}} [options] s3://bucketname
Options:
{{range .VisibleFlags}}{{.}}
{{end}}
Examples:
1. Get bucket versioning status of a bucket
> s5cmd {{.HelpName}} s3://bucketname
2. Enable bucket versioning for the bucket
> s5cmd {{.HelpName}} --set Enabled s3://bucketname
3. Suspend bucket versioning for the bucket
> s5cmd {{.HelpName}} --set Suspended s3://bucketname
`

func NewBucketVersionCommand() *cli.Command {
cmd := &cli.Command{
Name: "bucket-version",
CustomHelpTemplate: bucketVersionHelpTemplate,
HelpName: "bucket-version",
Usage: "configure bucket versioning",
Flags: []cli.Flag{
&cli.GenericFlag{
Name: "set",
Value: &EnumValue{
Enum: []string{"Suspended", "Enabled"},
Default: "",
ConditionFunction: strings.EqualFold,
},
Usage: "set versioning status of bucket: (Suspended, Enabled)",
},
},
Before: func(ctx *cli.Context) error {
if err := checkNumberOfArguments(ctx, 1, 1); err != nil {
printError(commandFromContext(ctx), ctx.Command.Name, err)
return err
}
return nil
},
Action: func(c *cli.Context) error {
status := c.String("set")

fullCommand := commandFromContext(c)

bucket, err := url.New(c.Args().First())
if err != nil {
printError(fullCommand, c.Command.Name, err)
return err
}

return BucketVersion{
src: bucket,
op: c.Command.Name,
fullCommand: fullCommand,

status: status,
storageOpts: NewStorageOpts(c),
}.Run(c.Context)
},
}
cmd.BashComplete = getBashCompleteFn(cmd, true, true)
return cmd
}

type BucketVersion struct {
src *url.URL
op string
fullCommand string

status string
storageOpts storage.Options
}

func (v BucketVersion) Run(ctx context.Context) error {
client, err := storage.NewRemoteClient(ctx, &url.URL{}, v.storageOpts)
if err != nil {
printError(v.fullCommand, v.op, err)
return err
}

if v.status != "" {
v.status = strutil.CapitalizeFirstRune(v.status)

err := client.SetBucketVersioning(ctx, v.status, v.src.Bucket)
if err != nil {
printError(v.fullCommand, v.op, err)
return err
}
msg := BucketVersionMessage{
Bucket: v.src.Bucket,
Status: v.status,
isSet: true,
}
log.Info(msg)
return nil
}

status, err := client.GetBucketVersioning(ctx, v.src.Bucket)
if err != nil {
printError(v.fullCommand, v.op, err)
return err
}

msg := BucketVersionMessage{
Bucket: v.src.Bucket,
Status: status,
isSet: false,
}
log.Info(msg)
return nil
}

type BucketVersionMessage struct {
Bucket string `json:"bucket"`
Status string `json:"status"`
isSet bool
}

func (v BucketVersionMessage) String() string {
if v.isSet {
return fmt.Sprintf("Bucket versioning for %q is set to %q", v.Bucket, v.Status)
}
if v.Status != "" {
return fmt.Sprintf("Bucket versioning for %q is %q", v.Bucket, v.Status)
}
return fmt.Sprintf("%q is an unversioned bucket", v.Bucket)
}

func (v BucketVersionMessage) JSON() string {
return strutil.JSON(v)
}
32 changes: 26 additions & 6 deletions command/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,26 @@ Options:
Examples:
1. Print a remote object's content to stdout
> s5cmd {{.HelpName}} s3://bucket/prefix/object
2. Print specific version of a remote object's content to stdout
> s5cmd {{.HelpName}} --version-id VERSION_ID s3://bucket/prefix/object
`

func NewCatCommand() *cli.Command {
cmd := &cli.Command{
Name: "cat",
HelpName: "cat",
Usage: "print remote object content",
Name: "cat",
HelpName: "cat",
Usage: "print remote object content",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "raw",
Usage: "disable the wildcard operations, useful with filenames that contains glob characters",
},
&cli.StringFlag{
Name: "version-id",
Usage: "use the specified version of an object",
},
},
CustomHelpTemplate: catHelpTemplate,
Before: func(c *cli.Context) error {
err := validateCatCommand(c)
Expand All @@ -43,9 +56,11 @@ func NewCatCommand() *cli.Command {
Action: func(c *cli.Context) (err error) {
defer stat.Collect(c.Command.FullName(), &err)()

src, err := url.New(c.Args().Get(0))
op := c.Command.Name
fullCommand := commandFromContext(c)

src, err := url.New(c.Args().Get(0), url.WithVersion(c.String("version-id")),
url.WithRaw(c.Bool("raw")))
if err != nil {
printError(fullCommand, op, err)
return err
Expand Down Expand Up @@ -102,8 +117,8 @@ func validateCatCommand(c *cli.Context) error {
return fmt.Errorf("expected only one argument")
}

src, err := url.New(c.Args().Get(0))

src, err := url.New(c.Args().Get(0), url.WithVersion(c.String("version-id")),
url.WithRaw(c.Bool("raw")))
if err != nil {
return err
}
Expand All @@ -119,5 +134,10 @@ func validateCatCommand(c *cli.Context) error {
if src.IsWildcard() {
return fmt.Errorf("remote source %q can not contain glob characters", src)
}

if err := checkVersioningWithGoogleEndpoint(c); err != nil {
return err
}

return nil
}
Loading

0 comments on commit 0993c6d

Please sign in to comment.