Skip to content

Commit 70b814e

Browse files
authored
Add experimental workspace open command (#4727)
## Why Opening workspace resources in the browser requires manually constructing URLs. A quick command to open jobs, notebooks, clusters, etc. by type and ID saves time during development. ## Changes **Before**: URL patterns for workspace resources were duplicated inside per-resource `InitializeURL` methods, and BROWSER environment variable handling was duplicated between `cmd/auth/login.go` and `cmd/auth/token.go` (and would have been duplicated again by this command). `bundle open` and `pipelines open` ignored BROWSER entirely. **Now**: `databricks experimental open RESOURCE_TYPE ID_OR_PATH` opens any supported resource directly, and all browser-opening commands share a single BROWSER-aware helper. - New `experimental open` command opens jobs, clusters, notebooks, pipelines, dashboards, warehouses, queries, apps, experiments, models, model_serving_endpoints, alerts, registered_models. For `registered_models`, dot-separated names (`catalog.schema.model`) are converted to slash-separated URL segments automatically. `sql_warehouses` is accepted as an alias for `warehouses` so bundle plural names Just Work. - `--url` flag prints the URL to stdout without opening the browser, useful for scripting and SSH sessions. - New `libs/workspaceurls` package centralizes the resource-type-to-URL-pattern mapping, `?o=<workspace-id>` handling with a stricter `adb-<id>` hostname check, and fragment vs. path URL formatting. Bundle `InitializeURL` methods call `workspaceurls.ResourceURL` instead of inlining patterns. The bundle `initialize_urls` mutator keeps its existing loose `strings.Contains` hostname check to avoid changing `bundle summary` URL output on non-Azure workspaces; that unification is a follow-up with its own integration test updates. - New `libs/browser` package centralizes BROWSER handling (empty → default browser, `none` → prints URL, any other value → runs the value as a command via `libs/exec` for Windows percent-encoding safety). Adopted in `cmd/auth/login.go`, `cmd/auth/token.go`, `cmd/experimental/workspace_open.go`, `cmd/bundle/open.go`, `cmd/pipelines/open.go`. Minor behavior change: `bundle open` and `pipelines open` now respect `BROWSER=none`; `auth token` does too. ## Test plan - [x] Unit tests for URL construction across all resource types (path-based and fragment-based) - [x] Unit tests for shell completion, `--url` flag, workspace ID hostname detection - [x] Unit tests for `libs/browser.Open` (BROWSER=none) and `libs/browser.IsDisabled` - [x] Unit tests for `sql_warehouses` alias resolution - [x] Acceptance test for `experimental open` (print-URL and completion) - [x] Drift test asserting every bundle plural name with a URL resolves via `workspaceurls.ResourceURL` (catches future rename drift) - [x] Existing bundle mutator and bundle/pipelines open acceptance tests still pass after refactor - [x] `make lintfull` passes - [x] `make checks` passes
1 parent 4ae8d02 commit 70b814e

27 files changed

Lines changed: 879 additions & 80 deletions

acceptance/experimental/open/out.test.toml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
=== print URL for a job
3+
>>> [CLI] experimental open --url jobs 123
4+
[DATABRICKS_URL]/jobs/123?o=[NUMID]
5+
6+
=== print URL for a notebook
7+
>>> [CLI] experimental open --url notebooks 12345
8+
[DATABRICKS_URL]/?o=[NUMID]#notebook/12345
9+
10+
=== unknown resource type
11+
>>> [CLI] experimental open --url unknown 123
12+
Error: unknown resource type "unknown", must be one of: alerts, apps, clusters, dashboards, experiments, jobs, model_serving_endpoints, models, notebooks, pipelines, queries, registered_models, warehouses
13+
14+
Exit code: 1
15+
16+
=== test auto-completion handler
17+
>>> [CLI] __complete experimental open ,
18+
alerts
19+
apps
20+
clusters
21+
dashboards
22+
experiments
23+
jobs
24+
model_serving_endpoints
25+
models
26+
notebooks
27+
pipelines
28+
queries
29+
registered_models
30+
warehouses
31+
:4
32+
Completion ended with directive: ShellCompDirectiveNoFileComp
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
title "print URL for a job"
2+
trace $CLI experimental open --url jobs 123
3+
4+
title "print URL for a notebook"
5+
trace $CLI experimental open --url notebooks 12345
6+
7+
title "unknown resource type"
8+
errcode trace $CLI experimental open --url unknown 123
9+
10+
title "test auto-completion handler"
11+
trace $CLI __complete experimental open ,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# No bundle engine needed for this command.
2+
[EnvMatrix]
3+
DATABRICKS_BUNDLE_ENGINE = []

bundle/config/resources/alerts.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66

77
"github.com/databricks/cli/libs/log"
8+
"github.com/databricks/cli/libs/workspaceurls"
89
"github.com/databricks/databricks-sdk-go"
910
"github.com/databricks/databricks-sdk-go/marshal"
1011
"github.com/databricks/databricks-sdk-go/service/sql"
@@ -52,8 +53,7 @@ func (a *Alert) InitializeURL(baseURL url.URL) {
5253
if a.ID == "" {
5354
return
5455
}
55-
baseURL.Path = "sql/alerts-v2/" + a.ID
56-
a.URL = baseURL.String()
56+
a.URL = workspaceurls.ResourceURL(baseURL, "alerts", a.ID)
5757
}
5858

5959
func (a *Alert) GetName() string {

bundle/config/resources/apps.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66

77
"github.com/databricks/cli/libs/log"
8+
"github.com/databricks/cli/libs/workspaceurls"
89
"github.com/databricks/databricks-sdk-go"
910
"github.com/databricks/databricks-sdk-go/marshal"
1011
"github.com/databricks/databricks-sdk-go/service/apps"
@@ -94,8 +95,7 @@ func (a *App) InitializeURL(baseURL url.URL) {
9495
if a.ModifiedStatus == "" || a.ModifiedStatus == ModifiedStatusCreated {
9596
return
9697
}
97-
baseURL.Path = "apps/" + a.GetName()
98-
a.URL = baseURL.String()
98+
a.URL = workspaceurls.ResourceURL(baseURL, "apps", a.GetName())
9999
}
100100

101101
func (a *App) GetName() string {

bundle/config/resources/clusters.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66

77
"github.com/databricks/cli/libs/log"
8+
"github.com/databricks/cli/libs/workspaceurls"
89
"github.com/databricks/databricks-sdk-go"
910
"github.com/databricks/databricks-sdk-go/marshal"
1011
"github.com/databricks/databricks-sdk-go/service/compute"
@@ -47,8 +48,7 @@ func (s *Cluster) InitializeURL(baseURL url.URL) {
4748
if s.ID == "" {
4849
return
4950
}
50-
baseURL.Path = "compute/clusters/" + s.ID
51-
s.URL = baseURL.String()
51+
s.URL = workspaceurls.ResourceURL(baseURL, "clusters", s.ID)
5252
}
5353

5454
func (s *Cluster) GetName() string {

bundle/config/resources/dashboard.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package resources
22

33
import (
44
"context"
5-
"fmt"
65
"net/url"
76

87
"github.com/databricks/cli/libs/log"
8+
"github.com/databricks/cli/libs/workspaceurls"
99
"github.com/databricks/databricks-sdk-go"
1010
"github.com/databricks/databricks-sdk-go/marshal"
1111
"github.com/databricks/databricks-sdk-go/service/dashboards"
@@ -114,8 +114,7 @@ func (r *Dashboard) InitializeURL(baseURL url.URL) {
114114
return
115115
}
116116

117-
baseURL.Path = fmt.Sprintf("dashboardsv3/%s/published", r.ID)
118-
r.URL = baseURL.String()
117+
r.URL = workspaceurls.ResourceURL(baseURL, "dashboards", r.ID)
119118
}
120119

121120
func (r *Dashboard) GetName() string {

bundle/config/resources/job.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strconv"
77

88
"github.com/databricks/cli/libs/log"
9+
"github.com/databricks/cli/libs/workspaceurls"
910
"github.com/databricks/databricks-sdk-go"
1011
"github.com/databricks/databricks-sdk-go/marshal"
1112
"github.com/databricks/databricks-sdk-go/service/jobs"
@@ -54,8 +55,7 @@ func (j *Job) InitializeURL(baseURL url.URL) {
5455
if j.ID == "" {
5556
return
5657
}
57-
baseURL.Path = "jobs/" + j.ID
58-
j.URL = baseURL.String()
58+
j.URL = workspaceurls.ResourceURL(baseURL, "jobs", j.ID)
5959
}
6060

6161
func (j *Job) GetName() string {

bundle/config/resources/mlflow_experiment.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66

77
"github.com/databricks/cli/libs/log"
8+
"github.com/databricks/cli/libs/workspaceurls"
89
"github.com/databricks/databricks-sdk-go"
910
"github.com/databricks/databricks-sdk-go/marshal"
1011
"github.com/databricks/databricks-sdk-go/service/ml"
@@ -49,8 +50,7 @@ func (s *MlflowExperiment) InitializeURL(baseURL url.URL) {
4950
if s.ID == "" {
5051
return
5152
}
52-
baseURL.Path = "ml/experiments/" + s.ID
53-
s.URL = baseURL.String()
53+
s.URL = workspaceurls.ResourceURL(baseURL, "experiments", s.ID)
5454
}
5555

5656
func (s *MlflowExperiment) GetName() string {

0 commit comments

Comments
 (0)