Skip to content

Commit b87b712

Browse files
committed
[lakebox] Support staging workspaces in CLI ssh + api routing
- ssh: auto-pick uw2.s.dbrx.dev when the workspace host has `.staging.` in it, otherwise keep using prod uw2.dbrx.dev. `--gateway` still overrides. - api: when the workspace host carries a `?o=<id>` selector or the SDK config has a workspace_id, send `X-Databricks-Org-Id` so multi-workspace gateways (dogfood.staging.databricks.com) route the request to the right workspace. Without it the gateway rejects PATs with "Credential was not sent or was of an unsupported type for this API". Co-authored-by: Isaac
1 parent 8cfe3bb commit b87b712

2 files changed

Lines changed: 48 additions & 7 deletions

File tree

cmd/lakebox/api.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"net/url"
1011
"strings"
1112
"time"
1213

@@ -277,10 +278,24 @@ func (a *lakeboxAPI) delete(ctx context.Context, id string) error {
277278

278279
// doRequest makes an authenticated HTTP request to the workspace.
279280
func (a *lakeboxAPI) doRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
280-
host := strings.TrimRight(a.w.Config.Host, "/")
281-
url := host + path
281+
// The configured host may be just a hostname or may carry a workspace
282+
// selector in the query (e.g. `https://dogfood.staging.databricks.com/?o=...`).
283+
// Parse it so we can append the API path while preserving the query, and so
284+
// we can pull the workspace ID out of `?o=<id>` when the SDK config doesn't
285+
// carry it on a separate `workspace_id` field.
286+
parsed, err := url.Parse(a.w.Config.Host)
287+
if err != nil {
288+
return nil, fmt.Errorf("failed to parse host %q: %w", a.w.Config.Host, err)
289+
}
290+
wsid := a.w.Config.WorkspaceID
291+
if wsid == "" {
292+
if v := parsed.Query().Get("o"); v != "" {
293+
wsid = v
294+
}
295+
}
296+
parsed.Path = strings.TrimRight(parsed.Path, "/") + path
282297

283-
req, err := http.NewRequestWithContext(ctx, method, url, body)
298+
req, err := http.NewRequestWithContext(ctx, method, parsed.String(), body)
284299
if err != nil {
285300
return nil, fmt.Errorf("failed to create request: %w", err)
286301
}
@@ -289,6 +304,15 @@ func (a *lakeboxAPI) doRequest(ctx context.Context, method, path string, body io
289304
return nil, fmt.Errorf("failed to authenticate: %w", err)
290305
}
291306

307+
// Multi-workspace gateways (e.g. dogfood.staging.databricks.com) need a
308+
// workspace selector to route the request — without it the gateway can't
309+
// scope the credential and rejects with "Credential was not sent or was of
310+
// an unsupported type for this API". `?o=<id>` in the URL works as a
311+
// fallback, but the explicit header is the well-defined contract.
312+
if wsid != "" {
313+
req.Header.Set("X-Databricks-Org-Id", wsid)
314+
}
315+
292316
if body != nil {
293317
req.Header.Set("Content-Type", "application/json")
294318
}

cmd/lakebox/ssh.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,28 @@ import (
55
"os"
66
"os/exec"
77
"runtime"
8+
"strings"
89

910
"github.com/databricks/cli/libs/cmdctx"
1011
"github.com/spf13/cobra"
1112
)
1213

1314
const (
14-
defaultGatewayHost = "uw2.dbrx.dev"
15-
defaultGatewayPort = "2222"
15+
defaultGatewayHost = "uw2.dbrx.dev"
16+
stagingDefaultGatewayHost = "uw2.s.dbrx.dev"
17+
defaultGatewayPort = "2222"
1618
)
1719

20+
// resolveGatewayHost picks the SSH gateway hostname based on the workspace host.
21+
// Staging workspaces (*.staging.cloud.databricks.com etc.) route through
22+
// uw2.s.dbrx.dev; everything else uses prod uw2.dbrx.dev.
23+
func resolveGatewayHost(workspaceHost string) string {
24+
if strings.Contains(workspaceHost, ".staging.") {
25+
return stagingDefaultGatewayHost
26+
}
27+
return defaultGatewayHost
28+
}
29+
1830
func newSSHCommand() *cobra.Command {
1931
var gatewayHost string
2032
var gatewayPort string
@@ -98,13 +110,18 @@ Examples:
98110
}
99111
}
100112

113+
host := gatewayHost
114+
if host == "" {
115+
host = resolveGatewayHost(w.Config.Host)
116+
}
117+
101118
s := spin(stderr, fmt.Sprintf("Connecting to %s…", bold(lakeboxID)))
102119
s.ok(fmt.Sprintf("Connected to %s", bold(lakeboxID)))
103-
return execSSHDirect(lakeboxID, gatewayHost, gatewayPort, keyPath, extraArgs)
120+
return execSSHDirect(lakeboxID, host, gatewayPort, keyPath, extraArgs)
104121
},
105122
}
106123

107-
cmd.Flags().StringVar(&gatewayHost, "gateway", defaultGatewayHost, "Lakebox gateway hostname")
124+
cmd.Flags().StringVar(&gatewayHost, "gateway", "", "Lakebox gateway hostname (auto-detected from profile if empty)")
108125
cmd.Flags().StringVar(&gatewayPort, "port", defaultGatewayPort, "Lakebox gateway SSH port")
109126

110127
return cmd

0 commit comments

Comments
 (0)