diff --git a/integration/bundle/bundles/dashboards/template/dashboard.lvdash.json b/acceptance/bundle/deploy/dashboard/detect-change/dashboard.lvdash.json similarity index 100% rename from integration/bundle/bundles/dashboards/template/dashboard.lvdash.json rename to acceptance/bundle/deploy/dashboard/detect-change/dashboard.lvdash.json diff --git a/acceptance/bundle/deploy/dashboard/detect-change/databricks.yml.tmpl b/acceptance/bundle/deploy/dashboard/detect-change/databricks.yml.tmpl new file mode 100644 index 0000000000..b50dd9d186 --- /dev/null +++ b/acceptance/bundle/deploy/dashboard/detect-change/databricks.yml.tmpl @@ -0,0 +1,12 @@ +bundle: + name: dashboards + +workspace: + root_path: ~/.bundle/$UNIQUE_NAME + +resources: + dashboards: + file_reference: + display_name: test-dashboard-$UNIQUE_NAME + file_path: ./dashboard.lvdash.json + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID diff --git a/acceptance/bundle/deploy/dashboard/detect-change/output.txt b/acceptance/bundle/deploy/dashboard/detect-change/output.txt new file mode 100644 index 0000000000..e33902d2a9 --- /dev/null +++ b/acceptance/bundle/deploy/dashboard/detect-change/output.txt @@ -0,0 +1,56 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Assert that the dashboard exists at the expected path and is, indeed, a dashboard: +>>> [CLI] workspace get-status /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/resources/test-dashboard-[UNIQUE_NAME].lvdash.json +{ + "object_type": "DASHBOARD", + "path": "/Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/resources/test-dashboard-[UNIQUE_NAME].lvdash.json" +} + +=== Load the dashboard by its ID and confirm its display name: { + "display_name": "test-dashboard-[UNIQUE_NAME]", + "serialized_dashboard": "{\"pages\":[{\"name\":\"fdd21a3c\",\"displayName\":\"New Page\",\"layout\":[{\"widget\":{\"name\":\"82eb9107\",\"textbox_spec\":\"# I'm a title\"},\"position\":{\"x\":0,\"y\":0,\"width\":6,\"height\":2}},{\"widget\":{\"name\":\"ffa6de4f\",\"textbox_spec\":\"Text\"},\"position\":{\"x\":0,\"y\":2,\"width\":6,\"height\":2}}],\"pageType\":\"PAGE_TYPE_CANVAS\"}]}" +} + +=== Make an out of band modification to the dashboard and confirm that it is detected: +{ + "lifecycle_state": "ACTIVE" +} + +=== Try to redeploy the bundle and confirm that the out of band modification is detected: +>>> errcode [CLI] bundle deploy +Error: dashboard "file_reference" has been modified remotely + at resources.dashboards.file_reference + in databricks.yml:10:7 + +This dashboard has been modified remotely since the last bundle deployment. +These modifications are untracked and will be overwritten on deploy. + +Make sure that the local dashboard definition matches what you intend to deploy +before proceeding with the deployment. + +Run `databricks bundle deploy --force` to bypass this error. + + +Exit code: 1 + +=== Redeploy the bundle with the --force flag and confirm that the out of band modification is ignored: +>>> errcode [CLI] bundle deploy --force +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete dashboard file_reference + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/deploy/dashboard/detect-change/script b/acceptance/bundle/deploy/dashboard/detect-change/script new file mode 100644 index 0000000000..7d1ef4cbaf --- /dev/null +++ b/acceptance/bundle/deploy/dashboard/detect-change/script @@ -0,0 +1,28 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +trace $CLI bundle deploy + +title "Assert that the dashboard exists at the expected path and is, indeed, a dashboard:" +RESOURCE_PATH=$($CLI bundle validate -o json | jq -r '.workspace.resource_path') +DASHBOARD_PATH="${RESOURCE_PATH}/test-dashboard-${UNIQUE_NAME}.lvdash.json" +trace $CLI workspace get-status "${DASHBOARD_PATH}" | jq '{object_type,path}' + +title "Load the dashboard by its ID and confirm its display name: " +DASHBOARD_ID=$($CLI bundle summary --output json | jq -r '.resources.dashboards.file_reference.id') +$CLI lakeview get $DASHBOARD_ID | jq '{display_name,serialized_dashboard}' + +title "Make an out of band modification to the dashboard and confirm that it is detected:\n" +RESOURCE_ID=$($CLI workspace get-status "${DASHBOARD_PATH}" | jq -r '.resource_id') +DASHBOARD_JSON=$($CLI bundle summary --output json | jq '{serialized_dashboard: .resources.dashboards.file_reference.serialized_dashboard}') +$CLI lakeview update "${RESOURCE_ID}" --json "${DASHBOARD_JSON}" | jq '{lifecycle_state}' + +title "Try to redeploy the bundle and confirm that the out of band modification is detected:" +trace errcode $CLI bundle deploy + +title "Redeploy the bundle with the --force flag and confirm that the out of band modification is ignored:" +trace errcode $CLI bundle deploy --force diff --git a/acceptance/bundle/deploy/dashboard/detect-change/test.toml b/acceptance/bundle/deploy/dashboard/detect-change/test.toml new file mode 100644 index 0000000000..f60aa89c84 --- /dev/null +++ b/acceptance/bundle/deploy/dashboard/detect-change/test.toml @@ -0,0 +1,16 @@ +Local = false +Cloud = true + +Ignore = [ + "databricks.yml", +] + +[Env] +# MSYS2 automatically converts absolute paths like /Users/$username/$UNIQUE_NAME to +# C:/Program Files/Git/Users/$username/UNIQUE_NAME before passing it to the CLI +# Setting this environment variable prevents that conversion on windows. +MSYS_NO_PATHCONV = "1" + +[[Repls]] +Old = "[0-9a-z]{16,}" +New = "[ALPHANUMID]" diff --git a/integration/bundle/bundles/dashboards/databricks_template_schema.json b/integration/bundle/bundles/dashboards/databricks_template_schema.json deleted file mode 100644 index 1aa5728fc1..0000000000 --- a/integration/bundle/bundles/dashboards/databricks_template_schema.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "properties": { - "unique_id": { - "type": "string", - "description": "Unique ID for job name" - }, - "warehouse_id": { - "type": "string", - "description": "The SQL warehouse ID to use for the dashboard" - } - } -} diff --git a/integration/bundle/bundles/dashboards/template/databricks.yml.tmpl b/integration/bundle/bundles/dashboards/template/databricks.yml.tmpl deleted file mode 100644 index e777123818..0000000000 --- a/integration/bundle/bundles/dashboards/template/databricks.yml.tmpl +++ /dev/null @@ -1,12 +0,0 @@ -bundle: - name: dashboards - -workspace: - root_path: "~/.bundle/{{.unique_id}}" - -resources: - dashboards: - file_reference: - display_name: test-dashboard-{{.unique_id}} - file_path: ./dashboard.lvdash.json - warehouse_id: {{.warehouse_id}} diff --git a/integration/bundle/dashboards_test.go b/integration/bundle/dashboards_test.go deleted file mode 100644 index d383b0afc1..0000000000 --- a/integration/bundle/dashboards_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package bundle_test - -import ( - "fmt" - "testing" - - "github.com/databricks/cli/integration/internal/acc" - "github.com/databricks/cli/internal/testutil" - "github.com/databricks/databricks-sdk-go/service/dashboards" - "github.com/databricks/databricks-sdk-go/service/workspace" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDashboards(t *testing.T) { - ctx, wt := acc.WorkspaceTest(t) - - warehouseID := testutil.GetEnvOrSkipTest(t, "TEST_DEFAULT_WAREHOUSE_ID") - uniqueID := uuid.New().String() - root := initTestTemplate(t, ctx, "dashboards", map[string]any{ - "unique_id": uniqueID, - "warehouse_id": warehouseID, - }) - - t.Cleanup(func() { - destroyBundle(t, ctx, root) - }) - - deployBundle(t, ctx, root) - - // Load bundle configuration by running the validate command. - b := unmarshalConfig(t, mustValidateBundle(t, ctx, root)) - - // Assert that the dashboard exists at the expected path and is, indeed, a dashboard. - oi, err := wt.W.Workspace.GetStatusByPath(ctx, fmt.Sprintf("%s/test-dashboard-%s.lvdash.json", b.Config.Workspace.ResourcePath, uniqueID)) - require.NoError(t, err) - assert.EqualValues(t, workspace.ObjectTypeDashboard, oi.ObjectType) - - // Load the dashboard by its ID and confirm its display name. - dashboard, err := wt.W.Lakeview.GetByDashboardId(ctx, oi.ResourceId) - require.NoError(t, err) - assert.Equal(t, "test-dashboard-"+uniqueID, dashboard.DisplayName) - - // Make an out of band modification to the dashboard and confirm that it is detected. - _, err = wt.W.Lakeview.Update(ctx, dashboards.UpdateDashboardRequest{ - DashboardId: oi.ResourceId, - Dashboard: dashboards.Dashboard{ - SerializedDashboard: dashboard.SerializedDashboard, - }, - }) - require.NoError(t, err) - - // Try to redeploy the bundle and confirm that the out of band modification is detected. - stdout, _, err := deployBundleWithArgsErr(t, ctx, root) - require.Error(t, err) - assert.Contains(t, stdout, `Error: dashboard "file_reference" has been modified remotely`+"\n") - - // Redeploy the bundle with the --force flag and confirm that the out of band modification is ignored. - _, stderr := deployBundleWithArgs(t, ctx, root, "--force") - assert.Contains(t, stderr, `Deployment complete!`+"\n") -} diff --git a/integration/bundle/helpers_test.go b/integration/bundle/helpers_test.go index eb75e01ffe..75397ee51f 100644 --- a/integration/bundle/helpers_test.go +++ b/integration/bundle/helpers_test.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strings" - "github.com/databricks/cli/bundle" "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/cmdctx" @@ -72,19 +71,6 @@ func validateBundle(t testutil.TestingT, ctx context.Context, path string) ([]by return stdout.Bytes(), err } -func mustValidateBundle(t testutil.TestingT, ctx context.Context, path string) []byte { - data, err := validateBundle(t, ctx, path) - require.NoError(t, err) - return data -} - -func unmarshalConfig(t testutil.TestingT, data []byte) *bundle.Bundle { - bundle := &bundle.Bundle{} - err := json.Unmarshal(data, &bundle.Config) - require.NoError(t, err) - return bundle -} - func deployBundle(t testutil.TestingT, ctx context.Context, path string) { ctx = env.Set(ctx, "BUNDLE_ROOT", path) c := testcli.NewRunner(t, ctx, "bundle", "deploy", "--force-lock", "--auto-approve") @@ -92,20 +78,6 @@ func deployBundle(t testutil.TestingT, ctx context.Context, path string) { require.NoError(t, err) } -func deployBundleWithArgsErr(t testutil.TestingT, ctx context.Context, path string, args ...string) (string, string, error) { - ctx = env.Set(ctx, "BUNDLE_ROOT", path) - args = append([]string{"bundle", "deploy"}, args...) - c := testcli.NewRunner(t, ctx, args...) - stdout, stderr, err := c.Run() - return stdout.String(), stderr.String(), err -} - -func deployBundleWithArgs(t testutil.TestingT, ctx context.Context, path string, args ...string) (string, string) { - stdout, stderr, err := deployBundleWithArgsErr(t, ctx, path, args...) - require.NoError(t, err) - return stdout, stderr -} - func runResource(t testutil.TestingT, ctx context.Context, path, key string) (string, error) { ctx = env.Set(ctx, "BUNDLE_ROOT", path) ctx = cmdio.NewContext(ctx, cmdio.Default())