Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 85 additions & 2 deletions cli/kv_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type kvCommand struct {
listNames bool
lsVerbose bool
lsVerboseDisplayValue bool
json bool
storage string
placementCluster string
placementTags []string
Expand Down Expand Up @@ -193,6 +194,7 @@ for an indefinite period or a per-bucket configured TTL.
ls.Flag("names", "Show just the bucket names").Short('n').UnNegatableBoolVar(&c.listNames)
ls.Flag("verbose", "Show detailed info about the key").Short('v').UnNegatableBoolVar(&c.lsVerbose)
ls.Flag("display-value", "Display value in verbose output (has no effect without 'verbose')").UnNegatableBoolVar(&c.lsVerboseDisplayValue)
ls.Flag("json", "Produce JSON output").Short('j').UnNegatableBoolVar(&c.json)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really keen on adding our own json representations here for info and keys the nats.go library doesn't have them, we risk inventing something here thats not compatible with future nats.go

So it would be fine for ls that just shows names - but not for info and the extended versions of ls

So if we can rescope this down to just the short ls output and then suggest you open issue with nats.go to add marshaller interfaces to the kv (and obj) data structures, once those have a standard method we can use them here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, I'll open an issue in nats.go


rmHistory := kv.Command("compact", "Reclaim space used by deleted keys").Action(c.compactAction)
rmHistory.Arg("bucket", "The bucket to act on").Required().StringVar(&c.bucket)
Expand Down Expand Up @@ -265,6 +267,10 @@ func (c *kvCommand) lsBucketKeys() error {
return err
}

if c.json {
return c.displayKeysJSON(kv, lister)
}

var found bool
if c.lsVerbose {
found, err = c.displayKeyInfo(kv, lister)
Expand Down Expand Up @@ -326,6 +332,46 @@ func (c *kvCommand) displayKeyInfo(kv jetstream.KeyValue, keys jetstream.KeyList
return found, nil
}

func (c *kvCommand) displayKeysJSON(kv jetstream.KeyValue, keys jetstream.KeyLister) error {
if c.lsVerbose {
err := c.displayKeyInfoJSON(kv, keys)
if err != nil {
return fmt.Errorf("unable to display key info: %w", err)
}
return nil
}

out := []string{}
for v := range keys.Keys() {
out = append(out, v)
}
return iu.PrintJSON(out)
}

func (c *kvCommand) displayKeyInfoJSON(kv jetstream.KeyValue, keys jetstream.KeyLister) error {
out := []map[string]any{}
for keyName := range keys.Keys() {
kve, err := kv.Get(ctx, keyName)
if err != nil {
return fmt.Errorf("unable to fetch key %s: %w", keyName, err)
}

kveJSON := map[string]any{
"key": kve.Key(),
"created": f(kve.Created()),
"delta": kve.Delta(),
"revision": kve.Revision(),
}

if c.lsVerboseDisplayValue {
kveJSON["value"] = string(kve.Value())
}

out = append(out, kveJSON)
}
return iu.PrintJSON(out)
}

func (c *kvCommand) lsBuckets() error {
_, mgr, err := prepareHelper("", natsOpts()...)
if err != nil {
Expand All @@ -343,9 +389,13 @@ func (c *kvCommand) lsBuckets() error {
return err
}

if c.json {
return c.lsBucketsJSON(found)
}

if c.listNames {
for _, s := range found {
fmt.Println(strings.TrimPrefix(s.Name(), "KV_"))
fmt.Println(kvBucketName(s))
}
return nil
}
Expand All @@ -368,14 +418,47 @@ func (c *kvCommand) lsBuckets() error {
for _, s := range found {
nfo, _ := s.LatestInformation()

table.AddRow(strings.TrimPrefix(s.Name(), "KV_"), s.Description(), f(nfo.Created), humanize.IBytes(nfo.State.Bytes), f(nfo.State.Msgs), f(time.Since(nfo.State.LastTime)))
table.AddRow(kvBucketName(s), s.Description(), f(nfo.Created), humanize.IBytes(nfo.State.Bytes), f(nfo.State.Msgs), f(time.Since(nfo.State.LastTime)))
}

fmt.Println(table.Render())

return nil
}

func (c *kvCommand) lsBucketsJSON(found []*jsm.Stream) error {
switch {
case len(found) == 0:
fmt.Println("[]") // Write empty JSON array
return nil
case c.listNames:
names := make([]string, 0, len(found))
for _, s := range found {
names = append(names, kvBucketName(s))
}
return iu.PrintJSON(names)
default:
info := make([]map[string]any, 0, len(found))
for _, s := range found {
nfo, _ := s.LatestInformation()
info = append(info,
map[string]any{
"bucket": kvBucketName(s),
"description": s.Description(),
"created": f(nfo.Created),
"size": humanize.IBytes(nfo.State.Bytes),
"values": nfo.State.Msgs,
"lastUpdate": f(nfo.State.LastTime),
})
}
return iu.PrintJSON(info)
}
}

func kvBucketName(s *jsm.Stream) string {
return strings.TrimPrefix(s.Name(), "KV_")
}

func (c *kvCommand) revertAction(pc *fisk.ParseContext) error {
_, _, store, err := c.loadBucket()
if err != nil {
Expand Down