Skip to content

Commit e8c6639

Browse files
authoredMar 24, 2025··
Add support for dashboards in deployment bind/unbind commands (#2516)
## Changes 1. Changed `FindResourceByConfigKey` to return dashboard resources 2. Changed deploy phase to detect dashboard recreation and request user's approval before deploying ## Why This PR adds support for dashboard resources in deployment operations, enabling users to: - Bind experiments using `databricks bundle deployment bind <mydashboard_key> <mydashboard_id>` - Unbind experiments using `databricks bundle deployment unbind <mydashboard_key>` Where: - `mydashboard_key` is a resource key defined in the bundle's .yml file - `mydashboard_id` references an existing dashboard in the Databricks workspace These capabilities allow for more flexible resource management of dashboards within bundles. ## Tests Added two new acceptance tests that tests bind and unbind methods together with bundle deployment and destruction, including a case of dashboard recreation
1 parent 63fbbd4 commit e8c6639

14 files changed

+218
-2
lines changed
 

‎NEXT_CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
### CLI
66

77
### Bundles
8-
98
* Processing 'artifacts' section is now done in "bundle validate" (adding defaults, inferring "build", asserting required fields) ([#2526])(https://github.com/databricks/cli/pull/2526))
109
* When uploading artifacts, include relative path in log message ([#2539])(https://github.com/databricks/cli/pull/2539))
1110
* Added support for clusters in deployment bind/unbind commands ([#2536](https://github.com/databricks/cli/pull/2536))
1211
* Added support for volumes in deployment bind/unbind commands ([#2527](https://github.com/databricks/cli/pull/2527))
12+
* Added support for dashboards in deployment bind/unbind commands ([#2516](https://github.com/databricks/cli/pull/2516))
1313

1414
### API Changes

‎acceptance/acceptance_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ func getSkipReason(config *internal.TestConfig, configPath string) string {
290290
return fmt.Sprintf("Disabled via RequiresUnityCatalog setting in %s (TEST_METASTORE_ID=%s)", configPath, os.Getenv("TEST_METASTORE_ID"))
291291
}
292292

293+
if isTruePtr(config.RequiresWarehouse) && os.Getenv("TEST_DEFAULT_WAREHOUSE_ID") == "" {
294+
return fmt.Sprintf("Disabled via RequiresWarehouse setting in %s (TEST_DEFAULT_WAREHOUSE_ID=%s)", configPath, os.Getenv("TEST_DEFAULT_WAREHOUSE_ID"))
295+
}
296+
293297
if isTruePtr(config.RequiresCluster) && os.Getenv("TEST_DEFAULT_CLUSTER_ID") == "" {
294298
return fmt.Sprintf("Disabled via RequiresCluster setting in %s (TEST_DEFAULT_CLUSTER_ID=%s)", configPath, os.Getenv("TEST_DEFAULT_CLUSTER_ID"))
295299
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
bundle:
2+
name: bind-dashboard-test-$UNIQUE_NAME
3+
4+
resources:
5+
dashboards:
6+
dashboard1:
7+
display_name: $DASHBOARD_DISPLAY_NAME
8+
warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID
9+
embed_credentials: true
10+
file_path: "sample-dashboard.lvdash.json"
11+
parent_path: /Users/$CURRENT_USER_NAME
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
>>> [CLI] bundle deployment bind dashboard1 [DASHBOARD_ID] --auto-approve
3+
Updating deployment state...
4+
Successfully bound databricks_dashboard with an id '[DASHBOARD_ID]'. Run 'bundle deploy' to deploy changes to your workspace
5+
6+
>>> [CLI] bundle deploy
7+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/bind-dashboard-test-[UNIQUE_NAME]/default/files...
8+
Deploying resources...
9+
Updating deployment state...
10+
Deployment complete!
11+
12+
>>> [CLI] lakeview get [DASHBOARD_ID]
13+
{
14+
"display_name": "test dashboard [UUID]",
15+
"lifecycle_state": "ACTIVE",
16+
"path": "/Users/[USERNAME]/test dashboard [UUID].lvdash.json",
17+
"parent_path": "/Users/[USERNAME]",
18+
"serialized_dashboard": "{\"pages\":[{\"name\":\"02724bf2\",\"displayName\":\"Page One\"}]}"
19+
}
20+
21+
>>> [CLI] bundle deployment unbind dashboard1
22+
Updating deployment state...
23+
24+
>>> [CLI] bundle destroy --auto-approve
25+
All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/bind-dashboard-test-[UNIQUE_NAME]/default
26+
27+
Deleting files...
28+
Destroy complete!
29+
30+
>>> [CLI] lakeview get [DASHBOARD_ID]
31+
{
32+
"display_name": "test dashboard [UUID]",
33+
"lifecycle_state": "ACTIVE",
34+
"path": "/Users/[USERNAME]/test dashboard [UUID].lvdash.json",
35+
"parent_path": "/Users/[USERNAME]",
36+
"serialized_dashboard": "{\"pages\":[{\"name\":\"02724bf2\",\"displayName\":\"Page One\"}]}"
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
bundle:
2+
name: bind-dashboard-recreation-test-$UNIQUE_NAME
3+
4+
resources:
5+
dashboards:
6+
dashboard1:
7+
display_name: $DASHBOARD_DISPLAY_NAME
8+
warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID
9+
embed_credentials: true
10+
file_path: "sample-dashboard.lvdash.json"
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
>>> [CLI] bundle deployment bind dashboard1 [DASHBOARD_ID] --auto-approve
3+
Updating deployment state...
4+
Successfully bound databricks_dashboard with an id '[DASHBOARD_ID]'. Run 'bundle deploy' to deploy changes to your workspace
5+
6+
>>> errcode [CLI] bundle deploy
7+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/bind-dashboard-recreation-test-[UNIQUE_NAME]/default/files...
8+
9+
This action will result in the deletion or recreation of the following dashboards.
10+
This will result in changed IDs and permanent URLs of the dashboards that will be recreated:
11+
recreate dashboard dashboard1
12+
Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed
13+
14+
15+
Exit code: 1
16+
17+
>>> [CLI] bundle deployment unbind dashboard1
18+
Updating deployment state...
19+
20+
>>> [CLI] bundle deploy --auto-approve
21+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/bind-dashboard-recreation-test-[UNIQUE_NAME]/default/files...
22+
Deploying resources...
23+
Updating deployment state...
24+
Deployment complete!
25+
26+
>>> [CLI] lakeview get [DASHBOARD_ID]
27+
{
28+
"display_name": "test dashboard [UUID]",
29+
"lifecycle_state": "ACTIVE"
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pages":[{"name":"02724bf2","displayName":"Page One"}]}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
DASHBOARD_DISPLAY_NAME="test dashboard $(uuid)"
2+
if [ -z "$CLOUD_ENV" ]; then
3+
DASHBOARD_DISPLAY_NAME="test dashboard 6260d50f-e8ff-4905-8f28-812345678903" # use hard-coded uuid when running locally
4+
export TEST_DEFAULT_WAREHOUSE_ID="warehouse-1234"
5+
fi
6+
7+
export DASHBOARD_DISPLAY_NAME
8+
envsubst < databricks.yml.tmpl > databricks.yml
9+
10+
# Create a pre-defined dashboard:
11+
DASHBOARD_ID=$($CLI lakeview create --display-name "${DASHBOARD_DISPLAY_NAME}" --warehouse-id "${TEST_DEFAULT_WAREHOUSE_ID}" --serialized-dashboard '{"pages":[{"name":"02724bf2","displayName":"Untitled page"}]}' | jq -r '.dashboard_id')
12+
13+
cleanupRemoveDashboard() {
14+
$CLI lakeview trash "${DASHBOARD_ID}"
15+
}
16+
trap cleanupRemoveDashboard EXIT
17+
18+
trace $CLI bundle deployment bind dashboard1 "${DASHBOARD_ID}" --auto-approve
19+
20+
trace errcode $CLI bundle deploy
21+
22+
trace $CLI bundle deployment unbind dashboard1
23+
24+
trace $CLI bundle deploy --auto-approve
25+
26+
trace $CLI lakeview get "${DASHBOARD_ID}" | jq '{display_name, lifecycle_state}'
27+
28+
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pages":[{"name":"02724bf2","displayName":"Page One"}]}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
DASHBOARD_DISPLAY_NAME="test dashboard $(uuid)"
2+
if [ -z "$CLOUD_ENV" ]; then
3+
DASHBOARD_DISPLAY_NAME="test dashboard 6260d50f-e8ff-4905-8f28-812345678903" # use hard-coded uuid when running locally
4+
export TEST_DEFAULT_WAREHOUSE_ID="warehouse-1234"
5+
fi
6+
7+
export DASHBOARD_DISPLAY_NAME
8+
envsubst < databricks.yml.tmpl > databricks.yml
9+
10+
# Create a pre-defined dashboard:
11+
DASHBOARD_ID=$($CLI lakeview create --display-name "${DASHBOARD_DISPLAY_NAME}" --warehouse-id "${TEST_DEFAULT_WAREHOUSE_ID}" --serialized-dashboard '{"pages":[{"name":"02724bf2","displayName":"Untitled page"}]}' | jq -r '.dashboard_id')
12+
13+
cleanupRemoveDashboard() {
14+
$CLI lakeview trash "${DASHBOARD_ID}"
15+
}
16+
trap cleanupRemoveDashboard EXIT
17+
18+
trace $CLI bundle deployment bind dashboard1 "${DASHBOARD_ID}" --auto-approve
19+
20+
trace $CLI bundle deploy
21+
22+
trace $CLI lakeview get "${DASHBOARD_ID}" | jq '{display_name, lifecycle_state, path, parent_path, serialized_dashboard}'
23+
24+
trace $CLI bundle deployment unbind dashboard1
25+
26+
trace $CLI bundle destroy --auto-approve
27+
28+
# Read the pre-defined dashboard again (expecting it still exists and is not deleted):
29+
trace $CLI lakeview get "${DASHBOARD_ID}" | jq '{display_name, lifecycle_state, path, parent_path, serialized_dashboard}'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Local = true
2+
Cloud = true
3+
RequiresWarehouse = true
4+
5+
Ignore = [
6+
"databricks.yml",
7+
]
8+
9+
[[Repls]]
10+
Old = "[0-9a-f]{32}"
11+
New = "[DASHBOARD_ID]"
12+
13+
[[Server]]
14+
Pattern = "POST /api/2.0/lakeview/dashboards"
15+
Response.Body = '''
16+
{
17+
"dashboard_id":"1234567890abcdef1234567890abcdef"
18+
}
19+
'''
20+
21+
[[Server]]
22+
Pattern = "POST /api/2.0/lakeview/dashboards/{dashboard_id}/published"
23+
24+
[[Server]]
25+
Pattern = "PATCH /api/2.0/lakeview/dashboards/{dashboard_id}"
26+
27+
[[Server]]
28+
Pattern = "GET /api/2.0/lakeview/dashboards/{dashboard_id}"
29+
Response.Body = '''
30+
{
31+
"dashboard_id":"1234567890abcdef1234567890abcdef",
32+
"display_name": "test dashboard 6260d50f-e8ff-4905-8f28-812345678903",
33+
"lifecycle_state": "ACTIVE",
34+
"path": "/Users/[USERNAME]/test dashboard [UUID].lvdash.json",
35+
"parent_path": "/Users/tester@databricks.com",
36+
"serialized_dashboard": "{\"pages\":[{\"name\":\"02724bf2\",\"displayName\":\"Page One\"}]}"
37+
}
38+
'''
39+
40+
[[Server]]
41+
Pattern = "DELETE /api/2.0/lakeview/dashboards/{dashboard_id}"

‎acceptance/internal/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type TestConfig struct {
4343
// If true and Cloud=true, run this test only if a default test cluster is available in the cloud environment
4444
RequiresCluster *bool
4545

46+
// If true and Cloud=true, run this test only if a default warehouse is available in the cloud environment
47+
RequiresWarehouse *bool
48+
4649
// List of additional replacements to apply on this test.
4750
// Old is a regexp, New is a replacement expression.
4851
Repls []testdiff.Replacement

‎bundle/config/resources.go

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ func (r *Resources) FindResourceByConfigKey(key string) (ConfigResource, error)
136136
}
137137
}
138138

139+
for k := range r.Dashboards {
140+
if k == key {
141+
found = append(found, r.Dashboards[k])
142+
}
143+
}
144+
139145
if len(found) == 0 {
140146
return nil, fmt.Errorf("no such resource: %s", key)
141147
}

‎bundle/phases/deploy.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) {
6969
schemaActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_schema")
7070
dltActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_pipeline")
7171
volumeActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_volume")
72+
dashboardActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_dashboard")
7273

7374
// We don't need to display any prompts in this case.
74-
if len(schemaActions) == 0 && len(dltActions) == 0 && len(volumeActions) == 0 {
75+
if len(schemaActions) == 0 && len(dltActions) == 0 && len(volumeActions) == 0 && len(dashboardActions) == 0 {
7576
return true, nil
7677
}
7778

@@ -109,6 +110,17 @@ is removed from the catalog, but the underlying files are not deleted:`
109110
}
110111
}
111112

113+
// One or more dashboards is being recreated.
114+
if len(dashboardActions) != 0 {
115+
msg := `
116+
This action will result in the deletion or recreation of the following dashboards.
117+
This will result in changed IDs and permanent URLs of the dashboards that will be recreated:`
118+
cmdio.LogString(ctx, msg)
119+
for _, action := range dashboardActions {
120+
cmdio.Log(ctx, action)
121+
}
122+
}
123+
112124
if b.AutoApprove {
113125
return true, nil
114126
}

0 commit comments

Comments
 (0)
Please sign in to comment.