Skip to content
Closed
Show file tree
Hide file tree
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
4 changes: 0 additions & 4 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,6 @@ mattn/go-isatty - https://github.com/mattn/go-isatty
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
License - https://github.com/mattn/go-isatty/blob/master/LICENSE

mattn/go-runewidth - https://github.com/mattn/go-runewidth
Copyright (c) 2016 Yasuhiro Matsumoto
License - https://github.com/mattn/go-runewidth/blob/master/LICENSE

sabhiram/go-gitignore - https://github.com/sabhiram/go-gitignore
Copyright (c) 2015 Shaba Abhiram
License - https://github.com/sabhiram/go-gitignore/blob/master/LICENSE
Expand Down
152 changes: 46 additions & 106 deletions cmd/lakebox/list.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
package lakebox

import (
"encoding/json"
"fmt"
"strings"

"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/mattn/go-runewidth"
"github.com/databricks/cli/libs/flags"
"github.com/spf13/cobra"
)

func newListCommand() *cobra.Command {
var outputJSON bool
// listRow embeds sandboxEntry so JSON output stays byte-identical to the
// raw API response. Text-mode fields are tagged `json:"-"` so they don't
// leak when the user passes `-o json`.
type listRow struct {
sandboxEntry
DisplayName string `json:"-"`
AutoStop string `json:"-"`
Default string `json:"-"`
}

// State colors are picked from cmdio's RenderFuncMap palette. green,
// yellow, and blue all emit same-byte-width SGR sequences, so the STATUS
// column stays aligned under tabwriter even when colors vary per row.
const (
listHeaderTemplate = `{{header "ID"}} {{header "NAME"}} {{header "STATUS"}} {{header "AUTOSTOP"}} {{header "DEFAULT"}}`
listRowTemplate = `{{range .}}{{.SandboxID | bold}} {{.DisplayName}} {{if eq .Status "Running"}}{{green "%s" .Status}}{{else if eq .Status "Creating"}}{{yellow "%s" .Status}}{{else}}{{blue "%s" .Status}}{{end}} {{.AutoStop | faint}} {{.Default | cyan}}
{{end}}`
)

func newListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List your Lakebox environments",
Expand All @@ -25,8 +40,12 @@ current status and ID.

Example:
databricks lakebox list
databricks lakebox list --json`,
databricks lakebox list -o json`,
PreRunE: root.MustWorkspaceClient,
Annotations: map[string]string{
"headerTemplate": listHeaderTemplate,
"template": listRowTemplate,
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
w := cmdctx.WorkspaceClient(ctx)
Expand Down Expand Up @@ -84,120 +103,41 @@ Example:
}
_ = setSandboxes(ctx, profile, refs)

if jsonOutput(cmd, outputJSON) {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(entries)
// JSON path: emit the raw entries (not the display-row wrapper)
// so consumers see the same shape as the underlying API.
if root.OutputType(cmd) == flags.OutputJSON {
return cmdio.Render(ctx, entries)
}

if len(entries) == 0 {
fmt.Fprintf(cmd.ErrOrStderr(), " %s\n", cmdio.Faint(ctx, "No lakeboxes found."))
return nil
}

out := cmd.OutOrStdout()

// Compute column widths. AUTOSTOP holds short tokens like
// `never`, `15m`, `1h30m` — 8 chars covers them. NAME is
// rendered only when at least one entry sets a display name
// different from the ID — there's no point in a column of
// pet-names that duplicate the ID column.
// All column widths are measured in *terminal cells*, not
// bytes or runes — emoji and CJK glyphs render as 2 cells
// despite being 1 rune / multi-byte, and using len() here
// (which counts bytes) misaligns the row whenever a `--name`
// includes wide characters. runewidth.StringWidth gives the
// East-Asian-Width-corrected cell count.
idCol := 10
autostopCol := 8
nameCol := 4
showName := false
for _, e := range entries {
if l := runewidth.StringWidth(e.SandboxID); l > idCol {
idCol = l
}
if l := runewidth.StringWidth(e.autoStopLabel()); l > autostopCol {
autostopCol = l
rows := make([]listRow, len(entries))
for i, e := range entries {
// "-" stands in for an unset NAME (or a NAME that just
// echoes the ID and so carries no extra information).
// Keep it ASCII so it doesn't add an ANSI wrapper that
// would throw off the column.
name := e.Name
if name == "" || name == e.SandboxID {
name = "-"
}
if e.Name != "" && e.Name != e.SandboxID {
showName = true
}
if l := runewidth.StringWidth(e.Name); l > nameCol {
nameCol = l
}
}
idCol += 2
autostopCol += 2
if showName {
nameCol += 2
}
const statusCol = 10
const defaultCol = 7

blank(out)
var header string
if showName {
header = fmt.Sprintf("%-*s %-*s %-*s %-*s %s",
idCol, "ID", nameCol, "NAME", statusCol, "STATUS", autostopCol, "AUTOSTOP", "DEFAULT")
} else {
header = fmt.Sprintf("%-*s %-*s %-*s %s",
idCol, "ID", statusCol, "STATUS", autostopCol, "AUTOSTOP", "DEFAULT")
}
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, header))

ruleLen := idCol + statusCol + autostopCol + defaultCol + 6
if showName {
ruleLen += nameCol + 2
}
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, strings.Repeat("─", ruleLen)))

for _, e := range entries {
id := e.SandboxID
def := ""
if id == defaultID {
def = cmdio.Cyan(ctx, "*")
if e.SandboxID == defaultID {
def = "*"
}
// Pad each cell manually so visible-width alignment is
// preserved after the helpers wrap them with ANSI escapes.
idPad := max(idCol-runewidth.StringWidth(id), 0)
st := status(ctx, e.Status)
stPad := max(statusCol-runewidth.StringWidth(e.Status), 0)
as := e.autoStopLabel()
asPad := max(autostopCol-runewidth.StringWidth(as), 0)
idStr := cmdio.Bold(ctx, id)
if strings.EqualFold(e.Status, "running") {
idStr = cmdio.Bold(ctx, cmdio.Cyan(ctx, id))
}
if showName {
nm := e.Name
if nm == "" || nm == id {
nm = "-"
}
nmPad := max(nameCol-runewidth.StringWidth(nm), 0)
nmStr := nm
if nm == "-" {
nmStr = cmdio.Faint(ctx, "-")
}
fmt.Fprintf(out, " %s%s %s%s %s%s %s%s %s\n",
idStr, strings.Repeat(" ", idPad),
nmStr, strings.Repeat(" ", nmPad),
st, strings.Repeat(" ", stPad),
cmdio.Faint(ctx, as), strings.Repeat(" ", asPad),
def)
} else {
fmt.Fprintf(out, " %s%s %s%s %s%s %s\n",
idStr, strings.Repeat(" ", idPad),
st, strings.Repeat(" ", stPad),
cmdio.Faint(ctx, as), strings.Repeat(" ", asPad),
def)
rows[i] = listRow{
sandboxEntry: e,
DisplayName: name,
AutoStop: e.autoStopLabel(),
Default: def,
}
}
blank(out)
return nil
return cmdio.Render(ctx, rows)
},
}

cmd.Flags().BoolVar(&outputJSON, "json", false, "Output as JSON")

return cmd
}
117 changes: 55 additions & 62 deletions cmd/lakebox/sshkey.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package lakebox

import (
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/mattn/go-runewidth"
"github.com/databricks/cli/libs/flags"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -61,9 +59,24 @@ Example:
return cmd
}

func newSSHKeyListCommand() *cobra.Command {
var outputJSON bool
// sshKeyRow embeds sshKeyEntry so JSON output stays byte-identical to
// the raw API response. Text-mode fields are tagged `json:"-"` so they
// don't leak when the user passes `-o json`.
type sshKeyRow struct {
sshKeyEntry
DisplayName string `json:"-"`
Created string `json:"-"`
LastUsed string `json:"-"`
Local string `json:"-"`
}

const (
sshKeyHeaderTemplate = `{{header "LOCAL"}} {{header "NAME"}} {{header "KEY HASH"}} {{header "CREATED"}} {{header "LAST USED"}}`
sshKeyRowTemplate = `{{range .}}{{.Local | cyan}} {{.DisplayName}} {{.KeyHash}} {{.Created | faint}} {{.LastUsed | faint}}
{{end}}`
)

func newSSHKeyListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List SSH keys registered with the lakebox service",
Expand All @@ -72,12 +85,16 @@ func newSSHKeyListCommand() *cobra.Command {
Each row shows the server-assigned key hash (the identifier used to
delete the key), the user-supplied name, and create / last-use
timestamps. The locally-registered key (from 'databricks lakebox
register') is marked when its hash matches one of the listed entries.
register') is marked with a '*' in the LOCAL column.

Examples:
databricks lakebox ssh-key list
databricks lakebox ssh-key list --json`,
databricks lakebox ssh-key list -o json`,
PreRunE: root.MustWorkspaceClient,
Annotations: map[string]string{
"headerTemplate": sshKeyHeaderTemplate,
"template": sshKeyRowTemplate,
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
w := cmdctx.WorkspaceClient(ctx)
Expand All @@ -91,10 +108,8 @@ Examples:
return fmt.Errorf("failed to list ssh keys: %w", err)
}

if jsonOutput(cmd, outputJSON) {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(keys)
if root.OutputType(cmd) == flags.OutputJSON {
return cmdio.Render(ctx, keys)
}

if len(keys) == 0 {
Expand All @@ -103,78 +118,56 @@ Examples:
return nil
}

// Best-effort: compute the hash of the locally-registered key so
// we can highlight which row belongs to this machine. Missing key
// file or read errors are non-fatal — just skip the marker.
// Best-effort: compute the hash of the locally-registered key
// so we can highlight which row belongs to this machine.
// Missing key file or read errors are non-fatal — just skip
// the marker.
localHash := ""
if path, err := lakeboxKeyPath(ctx); err == nil {
if data, err := os.ReadFile(path + ".pub"); err == nil {
localHash = keyHash(string(data))
}
}

out := cmd.OutOrStdout()
blank(out)

// Measure in terminal cells (runewidth) so wide / emoji
// glyphs in `--name` don't misalign the row.
nameCol := 4
for _, k := range keys {
if l := runewidth.StringWidth(k.Name); l > nameCol {
nameCol = l
}
}
nameCol += 2
const hashCol = 32
const timeCol = 20

// Leading 4-char gutter reserves space for a per-row `*` marker on
// the key matching this machine; header and separator leave it blank.
header := fmt.Sprintf("%-*s %-*s %-*s %s",
nameCol, "NAME", hashCol, "KEY HASH", timeCol, "CREATED", "LAST USED")
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, header))
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, strings.Repeat("─", nameCol+hashCol+timeCol+timeCol+6)))

rows := make([]sshKeyRow, len(keys))
localFound := false
for _, k := range keys {
// Pad NAME manually from the raw width because cmdio.Faint
// wraps the cell in ANSI escapes that throw off `%-*s`.
displayName, visibleNameLen := k.Name, runewidth.StringWidth(k.Name)
if displayName == "" {
displayName = cmdio.Faint(ctx, "(unset)")
visibleNameLen = runewidth.StringWidth("(unset)")
for i, k := range keys {
name := k.Name
if name == "" {
name = "-"
}
namePad := max(nameCol-visibleNameLen, 0)
gutter := " "
local := ""
if localHash != "" && k.KeyHash == localHash {
gutter = " " + cmdio.Cyan(ctx, "*") + " "
local = "*"
localFound = true
}
fmt.Fprintf(out, "%s%s%s %-*s %-*s %s\n",
gutter,
displayName, strings.Repeat(" ", namePad),
hashCol, k.KeyHash,
timeCol, formatTimeShort(k.CreateTime),
formatTimeShort(k.LastUseTime))
rows[i] = sshKeyRow{
sshKeyEntry: k,
DisplayName: name,
Created: formatTimeShort(k.CreateTime),
LastUsed: formatTimeShort(k.LastUseTime),
Local: local,
}
}

if err := cmdio.Render(ctx, rows); err != nil {
return err
}
// Without a legend the `*` (or its absence) is opaque. Print the
// meaning either way so users can tell "no `*` on any row" apart
// from "this terminal doesn't print the marker".
blank(out)

// Without a legend the `*` (or its absence) is opaque. Print
// the meaning either way so users can tell "no `*` on any
// row" apart from "this terminal doesn't print the marker".
switch {
case localFound:
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, cmdio.Cyan(ctx, "*")+" key matches the one on this machine"))
cmdio.LogString(ctx, " "+cmdio.Faint(ctx, cmdio.Cyan(ctx, "*")+" key matches the one on this machine"))
case localHash != "":
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, "(no registered key matches this machine's local key — run `databricks lakebox register` to register it)"))
cmdio.LogString(ctx, " "+cmdio.Faint(ctx, "(no registered key matches this machine's local key — run `databricks lakebox register` to register it)"))
default:
fmt.Fprintf(out, " %s\n", cmdio.Faint(ctx, "(no local lakebox key on this machine — run `databricks lakebox register` to create and register one)"))
cmdio.LogString(ctx, " "+cmdio.Faint(ctx, "(no local lakebox key on this machine — run `databricks lakebox register` to create and register one)"))
}
blank(out)
return nil
},
}

cmd.Flags().BoolVar(&outputJSON, "json", false, "Output as JSON")
return cmd
}

Expand Down
Loading