Skip to content
Merged
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
2 changes: 0 additions & 2 deletions acceptance/bundle/user_agent/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,10 @@ MISS run.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-
MISS summary.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat'
MISS summary.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat'
MISS summary.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat'
OK summary.direct /api/2.0/preview/scim/v2/Me engine/direct
MISS summary.direct /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]'
MISS summary.terraform /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat'
MISS summary.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat'
MISS summary.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat'
OK summary.terraform /api/2.0/preview/scim/v2/Me engine/terraform
MISS summary.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]'
MISS validate.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_validate cmd-exec-id/[UUID] interactive/none auth/pat'
MISS validate.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_validate cmd-exec-id/[UUID] interactive/none auth/pat'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,6 @@
"return_export_info": "true"
}
}
{
"headers": {
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none engine/direct auth/pat"
]
},
"method": "GET",
"path": "/api/2.0/preview/scim/v2/Me"
}
{
"headers": {
"User-Agent": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,6 @@
"return_export_info": "true"
}
}
{
"headers": {
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none engine/terraform auth/pat"
]
},
"method": "GET",
"path": "/api/2.0/preview/scim/v2/Me"
}
{
"headers": {
"User-Agent": [
Expand Down
7 changes: 3 additions & 4 deletions bundle/config/mutator/initialize_urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package mutator
import (
"context"
"net/url"
"strconv"
"strings"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/auth"
"github.com/databricks/cli/libs/diag"
)

Expand All @@ -25,13 +25,12 @@ func (m *initializeURLs) Name() string {
}

func (m *initializeURLs) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
workspaceId, err := b.WorkspaceClient(ctx).CurrentWorkspaceID(ctx)
workspaceID, err := auth.ResolveWorkspaceID(ctx, b.WorkspaceClient(ctx))
if err != nil {
return diag.FromErr(err)
}
workspaceIDStr := strconv.FormatInt(workspaceId, 10)
host := b.WorkspaceClient(ctx).Config.CanonicalHostName()
err = initializeForWorkspace(b, workspaceIDStr, host)
err = initializeForWorkspace(b, workspaceID, host)
if err != nil {
return diag.FromErr(err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/apps/run_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const SHUTDOWN_TIMEOUT = 15 * time.Second
func setupWorkspaceAndConfig(cmd *cobra.Command, entryPoint string, appPort int) (*runlocal.Config, *runlocal.AppSpec, error) {
ctx := cmd.Context()
w := cmdctx.WorkspaceClient(ctx)
workspaceID, err := w.CurrentWorkspaceID(ctx)
workspaceID, err := auth.ResolveWorkspaceID(ctx, w)
if err != nil {
return nil, nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions cmd/experimental/workspace_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import (
"github.com/spf13/cobra"

"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/auth"
"github.com/databricks/cli/libs/browser"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/log"
"github.com/databricks/cli/libs/workspaceurls"
)

var currentWorkspaceID = func(ctx context.Context) (int64, error) {
return cmdctx.WorkspaceClient(ctx).CurrentWorkspaceID(ctx)
var resolveWorkspaceID = func(ctx context.Context) (string, error) {
return auth.ResolveWorkspaceID(ctx, cmdctx.WorkspaceClient(ctx))
}

var openWorkspaceURL = browser.Open
Expand Down Expand Up @@ -49,7 +50,7 @@ Examples:
resourceType := args[0]
id := args[1]

workspaceID, err := currentWorkspaceID(ctx)
workspaceID, err := resolveWorkspaceID(ctx)
if err != nil {
log.Warnf(ctx, "Could not determine workspace ID: %v", err)
}
Expand Down
40 changes: 20 additions & 20 deletions cmd/experimental/workspace_open_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestBuildWorkspaceURLPathBasedResources(t *testing.T) {

for _, tt := range tests {
t.Run(tt.resourceType+"/"+tt.id, func(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", tt.resourceType, tt.id, 0)
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", tt.resourceType, tt.id, "")
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
Expand All @@ -57,46 +57,46 @@ func TestBuildWorkspaceURLFragmentBasedResources(t *testing.T) {

for _, tt := range tests {
t.Run(tt.id, func(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", tt.resourceType, tt.id, 0)
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", tt.resourceType, tt.id, "")
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}

func TestBuildWorkspaceURLUnknownResourceType(t *testing.T) {
_, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "unknown", "123", 0)
_, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "unknown", "123", "")
assert.ErrorContains(t, err, "unknown resource type \"unknown\"")
assert.ErrorContains(t, err, "alerts, apps, catalogs, clusters, dashboards, database_catalogs, database_instances, experiments, jobs, model_serving_endpoints, models, notebooks, pipelines, postgres_catalogs, postgres_synced_tables, quality_monitors, queries, registered_models, schemas, synced_database_tables, vector_search_endpoints, vector_search_indexes, volumes, warehouses")
}

func TestBuildWorkspaceURLHostWithTrailingSlash(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com/", "jobs", "123", 0)
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com/", "jobs", "123", "")
require.NoError(t, err)
assert.Equal(t, "https://myworkspace.databricks.com/jobs/123", got)
}

func TestBuildWorkspaceURLWithWorkspaceID(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "jobs", "123", 123456)
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "jobs", "123", "123456")
require.NoError(t, err)
assert.Equal(t, "https://myworkspace.databricks.com/jobs/123?w=123456", got)
}

func TestBuildWorkspaceURLWithWorkspaceIDInHostname(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://adb-123456.azuredatabricks.net", "jobs", "123", 123456)
got, err := workspaceurls.BuildResourceURL("https://adb-123456.azuredatabricks.net", "jobs", "123", "123456")
require.NoError(t, err)
// Workspace ID is already in the hostname, so ?w= should not be appended.
assert.Equal(t, "https://adb-123456.azuredatabricks.net/jobs/123", got)
}

func TestBuildWorkspaceURLWithWorkspaceIDInVanityHostname(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://workspace-123456.example.com", "jobs", "123", 123456)
got, err := workspaceurls.BuildResourceURL("https://workspace-123456.example.com", "jobs", "123", "123456")
require.NoError(t, err)
assert.Equal(t, "https://workspace-123456.example.com/jobs/123?w=123456", got)
}

func TestBuildWorkspaceURLFragmentWithWorkspaceID(t *testing.T) {
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "notebooks", "12345", 789)
got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "notebooks", "12345", "789")
require.NoError(t, err)
assert.Equal(t, "https://myworkspace.databricks.com/?w=789#notebook/12345", got)
}
Expand Down Expand Up @@ -158,15 +158,15 @@ func TestWorkspaceOpenCommandHelpText(t *testing.T) {
}

func TestWorkspaceOpenCommandOpensBrowserByDefault(t *testing.T) {
originalCurrentWorkspaceID := currentWorkspaceID
originalResolveWorkspaceID := resolveWorkspaceID
originalOpenWorkspaceURL := openWorkspaceURL
t.Cleanup(func() {
currentWorkspaceID = originalCurrentWorkspaceID
resolveWorkspaceID = originalResolveWorkspaceID
openWorkspaceURL = originalOpenWorkspaceURL
})

currentWorkspaceID = func(context.Context) (int64, error) {
return 0, nil
resolveWorkspaceID = func(context.Context) (string, error) {
return "", nil
}

var gotURL string
Expand Down Expand Up @@ -197,15 +197,15 @@ func TestWorkspaceOpenCommandOpensBrowserByDefault(t *testing.T) {
}

func TestWorkspaceOpenCommandURLFlag(t *testing.T) {
originalCurrentWorkspaceID := currentWorkspaceID
originalResolveWorkspaceID := resolveWorkspaceID
originalOpenWorkspaceURL := openWorkspaceURL
t.Cleanup(func() {
currentWorkspaceID = originalCurrentWorkspaceID
resolveWorkspaceID = originalResolveWorkspaceID
openWorkspaceURL = originalOpenWorkspaceURL
})

currentWorkspaceID = func(context.Context) (int64, error) {
return 789, nil
resolveWorkspaceID = func(context.Context) (string, error) {
return "789", nil
}

browserOpened := false
Expand Down Expand Up @@ -238,15 +238,15 @@ func TestWorkspaceOpenCommandURLFlag(t *testing.T) {
}

func TestWorkspaceOpenCommandWarnsWhenWorkspaceIDLookupFails(t *testing.T) {
originalCurrentWorkspaceID := currentWorkspaceID
originalResolveWorkspaceID := resolveWorkspaceID
originalOpenWorkspaceURL := openWorkspaceURL
t.Cleanup(func() {
currentWorkspaceID = originalCurrentWorkspaceID
resolveWorkspaceID = originalResolveWorkspaceID
openWorkspaceURL = originalOpenWorkspaceURL
})

currentWorkspaceID = func(context.Context) (int64, error) {
return 0, errors.New("lookup failed")
resolveWorkspaceID = func(context.Context) (string, error) {
return "", errors.New("lookup failed")
}

openWorkspaceURL = func(ctx context.Context, targetURL string) error {
Expand Down
5 changes: 3 additions & 2 deletions experimental/ssh/internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/databricks/cli/experimental/ssh/internal/vscode"
sshWorkspace "github.com/databricks/cli/experimental/ssh/internal/workspace"
"github.com/databricks/cli/internal/build"
"github.com/databricks/cli/libs/auth"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go"
Expand Down Expand Up @@ -438,11 +439,11 @@ func getServerMetadata(ctx context.Context, client *databricks.WorkspaceClient,
return 0, "", "", errors.Join(errServerMetadata, errors.New("cluster ID not available in metadata"))
}

workspaceID, err := client.CurrentWorkspaceID(ctx)
workspaceID, err := auth.ResolveWorkspaceID(ctx, client)
if err != nil {
return 0, "", "", err
}
metadataURL := fmt.Sprintf("%s/driver-proxy-api/o/%d/%s/%d/metadata", client.Config.Host, workspaceID, effectiveClusterID, wsMetadata.Port)
metadataURL := fmt.Sprintf("%s/driver-proxy-api/o/%s/%s/%d/metadata", client.Config.Host, workspaceID, effectiveClusterID, wsMetadata.Port)
log.Debugf(ctx, "Metadata URL: %s", metadataURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataURL, nil)
if err != nil {
Expand Down
8 changes: 6 additions & 2 deletions experimental/ssh/internal/client/websockets.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"

"github.com/databricks/cli/libs/auth"
"github.com/databricks/databricks-sdk-go"
"github.com/gorilla/websocket"
)
Expand Down Expand Up @@ -38,11 +39,14 @@ func createWebsocketConnection(ctx context.Context, client *databricks.Workspace
}

func getProxyURL(ctx context.Context, client *databricks.WorkspaceClient, connID, clusterID string, serverPort int) (string, error) {
workspaceID, err := client.CurrentWorkspaceID(ctx)
workspaceID, err := auth.ResolveWorkspaceID(ctx, client)
if err != nil {
return "", fmt.Errorf("failed to get current workspace ID: %w", err)
}
host := client.Config.Host
url := fmt.Sprintf("%s/driver-proxy-api/o/%d/%s/%d/ssh?id=%s", host, workspaceID, clusterID, serverPort, connID)
// The /driver-proxy-api/o/<workspace-id>/... path is a legacy URL form on
// the driver-proxy endpoint and uses an "o" path segment regardless of
// whether the workspace ID itself is the legacy or new shape.
url := fmt.Sprintf("%s/driver-proxy-api/o/%s/%s/%d/ssh?id=%s", host, workspaceID, clusterID, serverPort, connID)
return url, nil
}
4 changes: 2 additions & 2 deletions libs/apps/runlocal/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
type Config struct {
AppName string
AppURL string
WorkspaceID int64
WorkspaceID string
ServerName string
Host string
WorkspaceHost string
Expand All @@ -24,7 +24,7 @@ const (
DEFAULT_PORT = 8000
)

func NewConfig(workspaceHost string, workspaceID int64, appDir, host string, port int) *Config {
func NewConfig(workspaceHost, workspaceID, appDir, host string, port int) *Config {
c := &Config{
AppName: DEFAULT_APP_NAME,
AppURL: "http://" + net.JoinHostPort(host, strconv.Itoa(port)),
Expand Down
2 changes: 1 addition & 1 deletion libs/apps/runlocal/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func GetBaseEnvVars(config *Config) []EnvVar {
{Name: "PYTHONUNBUFFERED", Value: "1"},
{Name: "DATABRICKS_APP_NAME", Value: config.AppName},
{Name: "DATABRICKS_APP_URL", Value: config.AppURL},
{Name: "DATABRICKS_WORKSPACE_ID", Value: strconv.FormatInt(config.WorkspaceID, 10)},
{Name: "DATABRICKS_WORKSPACE_ID", Value: config.WorkspaceID},
{Name: "DATABRICKS_HOST", Value: config.WorkspaceHost},
{Name: "DATABRICKS_APP_PORT", Value: strconv.Itoa(config.Port)},
{Name: "GRADIO_SERVER_NAME", Value: config.ServerName},
Expand Down
36 changes: 36 additions & 0 deletions libs/auth/workspace_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package auth

import (
"context"
"strconv"

"github.com/databricks/databricks-sdk-go"
)

// ResolveWorkspaceID returns the workspace ID as a string, preferring the
// value already configured on the client and falling back to a /Me probe.
//
// The fast path short-circuits the API call when w.Config.WorkspaceID is
// set (via --workspace-id, DATABRICKS_WORKSPACE_ID, workspace_id in
// .databrickscfg, ?o=/?w= on a host URL, or any other config source). The
// CLI-only "none" sentinel is treated as unset so it never leaks as a
// routing identifier.
//
// The fallback path delegates to w.CurrentWorkspaceID, which reads the
// X-Databricks-Org-Id response header on /api/2.0/preview/scim/v2/Me and
// parses it as int64. The numeric constraint is enforced by the SDK on
// that path; the helper just stringifies the result.
//
// Compared to calling w.CurrentWorkspaceID directly, the string return
// type lets callers pass the value to URL builders, env vars, and other
// string-typed sinks without a manual strconv.FormatInt step.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, maybe we can make more "louder"/clear that the user set workspace ID may NOT match the response workspace ID from /Me when a Connection IDs is provided. So defaulting to provided Workspace ID is preferable.

func ResolveWorkspaceID(ctx context.Context, w *databricks.WorkspaceClient) (string, error) {
if id := w.Config.WorkspaceID; id != "" && id != WorkspaceIDNone {
return id, nil
}
id, err := w.CurrentWorkspaceID(ctx)
if err != nil {
return "", err
}
return strconv.FormatInt(id, 10), nil
}
Loading
Loading