345ab87b5c
With this change users can use the uplink cli in scripts (ie. bash) more easily, since the output can be switched to an easier processable json format. It keeps the default of tabbed output. Change-Id: I37e2c55f75c2250c3119fd8df8b66a766ff9096b
228 lines
5.5 KiB
Go
228 lines
5.5 KiB
Go
// Copyright (C) 2021 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/zeebo/clingy"
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/storj/cmd/uplink/ulext"
|
|
"storj.io/storj/cmd/uplink/ulfs"
|
|
"storj.io/storj/cmd/uplink/ulloc"
|
|
"storj.io/uplink"
|
|
)
|
|
|
|
type cmdLs struct {
|
|
ex ulext.External
|
|
|
|
access string
|
|
recursive bool
|
|
encrypted bool
|
|
expanded bool
|
|
pending bool
|
|
utc bool
|
|
output string
|
|
|
|
prefix *ulloc.Location
|
|
}
|
|
|
|
func newCmdLs(ex ulext.External) *cmdLs {
|
|
return &cmdLs{ex: ex}
|
|
}
|
|
|
|
func (c *cmdLs) Setup(params clingy.Parameters) {
|
|
c.access = params.Flag("access", "Access name or value to use", "").(string)
|
|
c.recursive = params.Flag("recursive", "List recursively", false,
|
|
clingy.Short('r'),
|
|
clingy.Transform(strconv.ParseBool), clingy.Boolean,
|
|
).(bool)
|
|
c.encrypted = params.Flag("encrypted", "Shows keys base64 encoded without decrypting", false,
|
|
clingy.Transform(strconv.ParseBool), clingy.Boolean,
|
|
).(bool)
|
|
c.pending = params.Flag("pending", "List pending object uploads instead", false,
|
|
clingy.Transform(strconv.ParseBool), clingy.Boolean,
|
|
).(bool)
|
|
c.expanded = params.Flag("expanded", "Use expanded output, showing object expiration times and whether there is custom metadata attached", false,
|
|
clingy.Short('x'),
|
|
clingy.Transform(strconv.ParseBool), clingy.Boolean,
|
|
).(bool)
|
|
c.utc = params.Flag("utc", "Show all timestamps in UTC instead of local time", false,
|
|
clingy.Transform(strconv.ParseBool), clingy.Boolean,
|
|
).(bool)
|
|
c.output = params.Flag("output", "Output Format (tabbed, json)", "tabbed",
|
|
clingy.Short('o'),
|
|
).(string)
|
|
|
|
c.prefix = params.Arg("prefix", "Prefix to list (sj://BUCKET[/KEY])", clingy.Optional,
|
|
clingy.Transform(ulloc.Parse),
|
|
).(*ulloc.Location)
|
|
}
|
|
|
|
func (c *cmdLs) Execute(ctx clingy.Context) error {
|
|
if c.prefix == nil {
|
|
return c.listBuckets(ctx)
|
|
}
|
|
return c.listLocation(ctx, *c.prefix)
|
|
}
|
|
|
|
func (c *cmdLs) listBuckets(ctx clingy.Context) error {
|
|
project, err := c.ex.OpenProject(ctx, c.access)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = project.Close() }()
|
|
|
|
iter := project.ListBuckets(ctx, nil)
|
|
|
|
switch c.output {
|
|
case "tabbed":
|
|
return c.printTabbedBucket(ctx, iter)
|
|
case "json":
|
|
return c.printJSONBucket(ctx, iter)
|
|
default:
|
|
return errs.New("unknown output format, got %s", c.output)
|
|
}
|
|
}
|
|
|
|
func (c *cmdLs) listLocation(ctx clingy.Context, prefix ulloc.Location) error {
|
|
fs, err := c.ex.OpenFilesystem(ctx, c.access, ulext.BypassEncryption(c.encrypted))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = fs.Close() }()
|
|
|
|
if fs.IsLocalDir(ctx, prefix) {
|
|
prefix = prefix.AsDirectoryish()
|
|
}
|
|
|
|
// create the object iterator of either existing objects or pending multipart uploads
|
|
iter, err := fs.List(ctx, prefix, &ulfs.ListOptions{
|
|
Recursive: c.recursive,
|
|
Pending: c.pending,
|
|
Expanded: c.expanded,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch c.output {
|
|
case "tabbed":
|
|
return c.printTabbedLocation(ctx, iter)
|
|
case "json":
|
|
return c.printJSONLocation(ctx, iter)
|
|
default:
|
|
return errs.New("unknown output format, got %s", c.output)
|
|
}
|
|
}
|
|
|
|
func (c *cmdLs) printTabbedBucket(ctx clingy.Context, iter *uplink.BucketIterator) (err error) {
|
|
tw := newTabbedWriter(ctx.Stdout(), "CREATED", "NAME")
|
|
defer tw.Done()
|
|
|
|
for iter.Next() {
|
|
item := iter.Item()
|
|
tw.WriteLine(formatTime(c.utc, item.Created), item.Name)
|
|
}
|
|
return iter.Err()
|
|
}
|
|
|
|
func (c *cmdLs) printJSONBucket(ctx clingy.Context, iter *uplink.BucketIterator) (err error) {
|
|
jw := json.NewEncoder(ctx.Stdout())
|
|
|
|
for iter.Next() {
|
|
obj := iter.Item()
|
|
|
|
err = jw.Encode(obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return iter.Err()
|
|
}
|
|
|
|
func (c *cmdLs) printTabbedLocation(ctx clingy.Context, iter ulfs.ObjectIterator) (err error) {
|
|
headers := []string{"KIND", "CREATED", "SIZE", "KEY"}
|
|
if c.expanded {
|
|
headers = append(headers, "EXPIRES", "META")
|
|
}
|
|
|
|
tw := newTabbedWriter(ctx.Stdout(), headers...)
|
|
defer tw.Done()
|
|
|
|
// iterate and print the results
|
|
for iter.Next() {
|
|
obj := iter.Item()
|
|
|
|
var parts []interface{}
|
|
if obj.IsPrefix {
|
|
parts = append(parts, "PRE", "", "", obj.Loc.Loc())
|
|
if c.expanded {
|
|
parts = append(parts, "", "")
|
|
}
|
|
} else {
|
|
parts = append(parts, "OBJ", formatTime(c.utc, obj.Created), obj.ContentLength, obj.Loc.Loc())
|
|
if c.expanded {
|
|
parts = append(parts, formatTime(c.utc, obj.Expires), sumMetadataSize(obj.Metadata))
|
|
}
|
|
}
|
|
|
|
tw.WriteLine(parts...)
|
|
}
|
|
return iter.Err()
|
|
}
|
|
|
|
func (c *cmdLs) printJSONLocation(ctx clingy.Context, iter ulfs.ObjectIterator) (err error) {
|
|
jw := json.NewEncoder(ctx.Stdout())
|
|
|
|
for iter.Next() {
|
|
obj := iter.Item()
|
|
|
|
if obj.IsPrefix {
|
|
err = jw.Encode(struct {
|
|
Kind string `json:"kind"`
|
|
Key string `json:"key"`
|
|
}{"PRE", obj.Loc.Loc()})
|
|
} else {
|
|
err = jw.Encode(struct {
|
|
Kind string `json:"kind"`
|
|
Created string `json:"created"`
|
|
Size int64 `json:"size"`
|
|
Key string `json:"key"`
|
|
Expires string `json:"expires,omitempty"`
|
|
Metadata int `json:"meta,omitempty"`
|
|
}{"OBJ", formatTime(c.utc, obj.Created), obj.ContentLength, obj.Loc.Loc(), formatTime(c.utc, obj.Expires), sumMetadataSize(obj.Metadata)})
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return iter.Err()
|
|
}
|
|
|
|
func formatTime(utc bool, x time.Time) string {
|
|
if x.IsZero() {
|
|
return ""
|
|
}
|
|
|
|
if utc {
|
|
x = x.UTC()
|
|
} else {
|
|
x = x.Local()
|
|
}
|
|
return x.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
func sumMetadataSize(md uplink.CustomMetadata) int {
|
|
size := 0
|
|
for k, v := range md {
|
|
size += len(k)
|
|
size += len(v)
|
|
}
|
|
return size
|
|
}
|