From 4e431e5d47ada014fcaf45131aa510adfcfd5b75 Mon Sep 17 00:00:00 2001 From: Akshay Singla Date: Fri, 15 May 2026 15:23:03 +0000 Subject: [PATCH 1/2] [CJ-75152][lakebox] CLI: fix CreateSandbox wire format + add --name + surface name/timestamps The auto-create path on `lakebox ssh` fails with: INVALID_PARAMETER_VALUE: JSON decode error: unknown field `public_key`, expected `sandbox` `CreateSandboxRequest { Sandbox sandbox = 1 }` has `body: "*"`, so the wire body must be wrapped as `{"sandbox": {...}}`. The CLI was sending an unwrapped `{"public_key": "..."}` payload. `Sandbox` has no `public_key` field anywhere on the create path (proto or handler), so the field was dead end-to-end. `lakebox create` worked only because its default empty publicKey was stripped by `omitempty` before the request went out. While here, plug a few related gaps in the CLI surface: - Wrap the create body as `{"sandbox": {...}}`; drop dead `public_key` field and `--public-key-file` flag. - Surface `name`, `createTime`, `lastStartTime` on `sandboxEntry` so `lakebox status --json` and `lakebox list --json` stop silently dropping these fields. - Add `--name` to `lakebox create` and `lakebox config` (proto + handler accept name on create + update; CLI had no way to set it). - Print `name` in human `lakebox status` output when set. Jira: CJ-75152 Co-authored-by: Isaac --- cmd/lakebox/api.go | 42 ++++++++++++++++++++++++++---------------- cmd/lakebox/config.go | 28 ++++++++++++++++++++++------ cmd/lakebox/create.go | 21 ++++++--------------- cmd/lakebox/ssh.go | 6 +----- cmd/lakebox/status.go | 3 +++ 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/cmd/lakebox/api.go b/cmd/lakebox/api.go index acaeff47e8b..504bffe0802 100644 --- a/cmd/lakebox/api.go +++ b/cmd/lakebox/api.go @@ -23,15 +23,18 @@ type lakeboxAPI struct { w *databricks.WorkspaceClient } +// sandboxCreateBody is the inner `Sandbox` message in the create payload. +// Only `name` is caller-settable today (see `LakeboxApi::create_sandbox` in +// `lakebox/src/api/service.rs`); all other fields are server-chosen. +type sandboxCreateBody struct { + Name string `json:"name,omitempty"` +} + // createRequest is the JSON body for POST /api/2.0/lakebox/sandboxes. -// -// The proto-defined `CreateSandboxRequest` carries a `Sandbox sandbox = 1` -// field today (every member is server-chosen), but JSON transcoding accepts -// the unwrapped form for forward-compatible callers. Keep `public_key` here -// as a no-op compat shim so older `lakebox create --public-key-file=...` -// invocations don't error — the manager ignores it on the wire. +// `CreateSandboxRequest { Sandbox sandbox = 1 }` has `body: "*"`, so the +// wire body is the full request with a `sandbox` wrapper. type createRequest struct { - PublicKey string `json:"public_key,omitempty"` + Sandbox sandboxCreateBody `json:"sandbox"` } // createResponse is the JSON body returned by POST /api/2.0/lakebox/sandboxes. @@ -53,11 +56,14 @@ type createResponse struct { // form serializes Duration as a string with an `s` suffix (e.g. // `"900s"`), so the Go field is `*string` and we parse on read. type sandboxEntry struct { - SandboxID string `json:"sandboxId"` - Status string `json:"status"` - FQDN string `json:"fqdn"` - IdleTimeout *string `json:"idleTimeout,omitempty"` - NoAutostop *bool `json:"noAutostop,omitempty"` + SandboxID string `json:"sandboxId"` + Status string `json:"status"` + FQDN string `json:"fqdn"` + Name string `json:"name,omitempty"` + CreateTime string `json:"createTime,omitempty"` + LastStartTime string `json:"lastStartTime,omitempty"` + IdleTimeout *string `json:"idleTimeout,omitempty"` + NoAutostop *bool `json:"noAutostop,omitempty"` } // idleTimeoutSecs parses the proto3-canonical Duration string off @@ -140,10 +146,11 @@ func newLakeboxAPI(w *databricks.WorkspaceClient) *lakeboxAPI { return &lakeboxAPI{w: w} } -// create calls POST /api/2.0/lakebox with an optional public key. -func (a *lakeboxAPI) create(ctx context.Context, publicKey string) (*createResponse, error) { - body := createRequest{PublicKey: publicKey} - jsonBody, err := json.Marshal(body) +// create calls POST /api/2.0/lakebox/sandboxes. An empty `name` is omitted +// from the wire payload so the server treats it as "unset" rather than +// "explicit empty string." +func (a *lakeboxAPI) create(ctx context.Context, name string) (*createResponse, error) { + jsonBody, err := json.Marshal(createRequest{Sandbox: sandboxCreateBody{Name: name}}) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } @@ -216,6 +223,7 @@ func (a *lakeboxAPI) get(ctx context.Context, id string) (*sandboxEntry, error) // `google.protobuf.Duration`. type updateBody struct { SandboxID string `json:"sandbox_id"` + Name *string `json:"name,omitempty"` IdleTimeout *string `json:"idle_timeout,omitempty"` NoAutostop *bool `json:"no_autostop,omitempty"` } @@ -227,6 +235,7 @@ type updateBody struct { func (a *lakeboxAPI) update( ctx context.Context, id string, + name *string, idleTimeoutSecs *int64, noAutostop *bool, ) (*sandboxEntry, error) { @@ -237,6 +246,7 @@ func (a *lakeboxAPI) update( } body := updateBody{ SandboxID: id, + Name: name, IdleTimeout: idleTimeout, NoAutostop: noAutostop, } diff --git a/cmd/lakebox/config.go b/cmd/lakebox/config.go index fe3b80ddf29..9aa54eff482 100644 --- a/cmd/lakebox/config.go +++ b/cmd/lakebox/config.go @@ -19,13 +19,17 @@ const ( func newConfigCommand() *cobra.Command { var idleTimeoutFlag string var noAutostopFlag bool + var nameFlag string cmd := &cobra.Command{ Use: "config ", - Short: "Configure a Lakebox's auto-stop policy", - Long: `Configure a Lakebox's auto-stop policy. + Short: "Configure a Lakebox's name and auto-stop policy", + Long: `Configure a Lakebox's name and auto-stop policy. -Two knobs are independent — pass either or both: +Three knobs are independent — pass any combination: + + --name