From d85c527c042998cd3a4a334dda3f031c2ee1fa8a Mon Sep 17 00:00:00 2001 From: Akshay Singla Date: Thu, 21 May 2026 03:25:13 +0000 Subject: [PATCH] lakebox: add stop command Wires up `databricks lakebox stop ` against the existing StopSandbox proto RPC (POST /api/2.0/lakebox/sandboxes/{id}/stop). Terminates the backing microVM, preserves the sandbox record and storage. The gateway auto-starts a stopped sandbox on the next `lakebox ssh`, so a paired `start` command isn't required today. Co-authored-by: Isaac --- cmd/lakebox/api.go | 14 ++++++++++++ cmd/lakebox/lakebox.go | 1 + cmd/lakebox/stop.go | 51 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 cmd/lakebox/stop.go diff --git a/cmd/lakebox/api.go b/cmd/lakebox/api.go index 241c27f711..219a7db142 100644 --- a/cmd/lakebox/api.go +++ b/cmd/lakebox/api.go @@ -288,6 +288,20 @@ func (a *lakeboxAPI) delete(ctx context.Context, id string) error { return a.c.Do(ctx, http.MethodDelete, lakeboxAPIPath+"/"+id, a.headers(), nil, nil, nil) } +// stop calls POST /api/2.0/lakebox/sandboxes/{id}/stop and returns the +// refreshed sandbox. The proto's `StopSandboxRequest` carries `sandbox_id` +// (redundant with the URL path) under `body: "*"`, so we mirror it +// explicitly even though the transcoder fills the field from the path. +func (a *lakeboxAPI) stop(ctx context.Context, id string) (*sandboxEntry, error) { + body := map[string]string{"sandbox_id": id} + var resp sandboxEntry + err := a.c.Do(ctx, http.MethodPost, lakeboxAPIPath+"/"+id+"/stop", a.headers(), nil, body, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + // registerKey calls POST /api/2.0/lakebox/ssh-keys. An empty `name` is // omitted from the wire payload so the server records "unset" rather than // an explicit empty string. diff --git a/cmd/lakebox/lakebox.go b/cmd/lakebox/lakebox.go index 8745757a77..b104af080f 100644 --- a/cmd/lakebox/lakebox.go +++ b/cmd/lakebox/lakebox.go @@ -38,6 +38,7 @@ Common workflows: cmd.AddCommand(newListCommand()) cmd.AddCommand(newCreateCommand()) cmd.AddCommand(newDeleteCommand()) + cmd.AddCommand(newStopCommand()) cmd.AddCommand(newStatusCommand()) cmd.AddCommand(newConfigCommand()) diff --git a/cmd/lakebox/stop.go b/cmd/lakebox/stop.go new file mode 100644 index 0000000000..98e28f412b --- /dev/null +++ b/cmd/lakebox/stop.go @@ -0,0 +1,51 @@ +package lakebox + +import ( + "fmt" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +func newStopCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "stop ", + Short: "Stop a running Lakebox environment", + Long: `Stop a running Lakebox environment. + +Terminates the backing microVM but preserves the sandbox record and its +persistent storage. To restart, run 'databricks lakebox ssh' — the +gateway auto-starts a stopped sandbox on connection. + +Stopping an already-stopped sandbox is a no-op. + +Example: + databricks lakebox stop happy-panda-1234`, + Args: cobra.ExactArgs(1), + PreRunE: root.MustWorkspaceClient, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + api, err := newLakeboxAPI(w) + if err != nil { + return err + } + + lakeboxID := args[0] + s := spin(ctx, "Stopping "+lakeboxID+"…") + defer s.Close() + + updated, err := api.stop(ctx, lakeboxID) + if err != nil { + s.fail("Failed to stop " + lakeboxID) + return fmt.Errorf("failed to stop lakebox %s: %w", lakeboxID, err) + } + s.ok("Stopped " + cmdio.Bold(ctx, updated.SandboxID)) + return nil + }, + } + + return cmd +}