diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 7a5209601e..eef50437c0 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -145,6 +145,22 @@ func TestInprocessMode(t *testing.T) { require.Equal(t, 1, testAccept(t, true, "selftest/server")) } +// Configure replacements for environment variables we read from test environments. +func setReplsForTestEnvVars(t *testing.T, repls *testdiff.ReplacementsContext) { + envVars := []string{ + "TEST_USER_EMAIL", + "TEST_GROUP_NAME", + "TEST_SP_APPLICATION_ID", + "TEST_DEFAULT_WAREHOUSE_ID", + "TEST_INSTANCE_POOL_ID", + } + for _, envVar := range envVars { + if value := os.Getenv(envVar); value != "" { + repls.Set(value, "["+envVar+"]") + } + } +} + func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { repls := testdiff.ReplacementsContext{} cwd, err := os.Getwd() @@ -234,10 +250,7 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { } } - testDefaultWarehouseId := os.Getenv("TEST_DEFAULT_WAREHOUSE_ID") - if testDefaultWarehouseId != "" { - repls.Set(testDefaultWarehouseId, "[TEST_DEFAULT_WAREHOUSE_ID]") - } + setReplsForTestEnvVars(t, &repls) terraformrcPath := filepath.Join(terraformDir, ".terraformrc") t.Setenv("TF_CLI_CONFIG_FILE", terraformrcPath) @@ -252,8 +265,6 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { // do it last so that full paths match first: repls.SetPath(buildDir, "[BUILD_DIR]") - repls.Set(os.Getenv("TEST_INSTANCE_POOL_ID"), "[TEST_INSTANCE_POOL_ID]") - testdiff.PrepareReplacementsDevVersion(t, &repls) testdiff.PrepareReplacementSdkVersion(t, &repls) testdiff.PrepareReplacementsGoVersion(t, &repls) diff --git a/acceptance/bundle/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index fd65a5a59f..7d16e50986 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -439,6 +439,13 @@ resources.dashboards.*.serialized_dashboard string ALL resources.dashboards.*.update_time string ALL resources.dashboards.*.url string INPUT resources.dashboards.*.warehouse_id string ALL +resources.dashboards.*.permissions.object_id string ALL +resources.dashboards.*.permissions.permissions []iam.AccessControlRequest ALL +resources.dashboards.*.permissions.permissions[*] iam.AccessControlRequest ALL +resources.dashboards.*.permissions.permissions[*].group_name string ALL +resources.dashboards.*.permissions.permissions[*].permission_level iam.PermissionLevel ALL +resources.dashboards.*.permissions.permissions[*].service_principal_name string ALL +resources.dashboards.*.permissions.permissions[*].user_name string ALL resources.database_catalogs.*.create_database_if_not_exists bool ALL resources.database_catalogs.*.database_instance_name string ALL resources.database_catalogs.*.database_name string ALL diff --git a/acceptance/bundle/resources/permissions/dashboards/create/databricks.yml.tmpl b/acceptance/bundle/resources/permissions/dashboards/create/databricks.yml.tmpl new file mode 100644 index 0000000000..0b9577d46d --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/databricks.yml.tmpl @@ -0,0 +1,19 @@ +bundle: + name: dashboard-perm-$UNIQUE_NAME + +resources: + dashboards: + foo: + display_name: test-dashboard-$UNIQUE_NAME + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID + parent_path: /Users/$CURRENT_USER_NAME/folder1 + serialized_dashboard: '{"pages":[{"name":"page1","displayName":"Page 1"}]}' + permissions: + - level: CAN_READ + user_name: $TEST_USER_EMAIL + - level: CAN_MANAGE + group_name: $TEST_GROUP_NAME + - level: CAN_MANAGE + service_principal_name: $TEST_SP_APPLICATION_ID + - level: CAN_MANAGE + user_name: $CURRENT_USER_NAME diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.plan.direct.json b/acceptance/bundle/resources/permissions/dashboards/create/out.plan.direct.json new file mode 100644 index 0000000000..ee73865ea7 --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.plan.direct.json @@ -0,0 +1,49 @@ +{ + "plan": { + "resources.dashboards.foo": { + "action": "create", + "new_state": { + "config": { + "display_name": "test-dashboard-[UNIQUE_NAME]", + "parent_path": "/Users/[USERNAME]/folder1", + "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" + } + } + }, + "resources.dashboards.foo.permissions": { + "depends_on": [ + { + "node": "resources.dashboards.foo", + "label": "${resources.dashboards.foo.id}" + } + ], + "action": "create", + "new_state": { + "config": { + "object_id": "", + "permissions": [ + { + "permission_level": "CAN_READ", + "user_name": "[TEST_USER_EMAIL]" + }, + { + "group_name": "[TEST_GROUP_NAME]", + "permission_level": "CAN_MANAGE" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[TEST_SP_APPLICATION_ID]" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + }, + "vars": { + "object_id": "/dashboards/${resources.dashboards.foo.id}" + } + } + } + } +} diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.plan.terraform.json b/acceptance/bundle/resources/permissions/dashboards/create/out.plan.terraform.json new file mode 100644 index 0000000000..ff37f53606 --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.plan.terraform.json @@ -0,0 +1,7 @@ +{ + "plan": { + "resources.dashboards.foo": { + "action": "create" + } + } +} diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.requests.deploy.direct.json b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.deploy.direct.json new file mode 100644 index 0000000000..04c8cf8a9a --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.deploy.direct.json @@ -0,0 +1,24 @@ +{ + "method": "PUT", + "path": "/api/2.0/permissions/dashboards/[DASHBOARD_ID]", + "body": { + "access_control_list": [ + { + "permission_level": "CAN_READ", + "user_name": "[TEST_USER_EMAIL]" + }, + { + "group_name": "[TEST_GROUP_NAME]", + "permission_level": "CAN_MANAGE" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[TEST_SP_APPLICATION_ID]" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + } +} diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.requests.deploy.terraform.json b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.deploy.terraform.json new file mode 100644 index 0000000000..f20658a00a --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.deploy.terraform.json @@ -0,0 +1,24 @@ +{ + "method": "PUT", + "path": "/api/2.0/permissions/dashboards/[DASHBOARD_ID]", + "body": { + "access_control_list": [ + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[TEST_SP_APPLICATION_ID]" + }, + { + "group_name": "[TEST_GROUP_NAME]", + "permission_level": "CAN_MANAGE" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + }, + { + "permission_level": "CAN_READ", + "user_name": "[TEST_USER_EMAIL]" + } + ] + } +} diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.requests.destroy.direct.json b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.destroy.direct.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.requests.destroy.terraform.json b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.destroy.terraform.json new file mode 100644 index 0000000000..317d4dbc93 --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.requests.destroy.terraform.json @@ -0,0 +1,5 @@ +{ + "method": "PUT", + "path": "/api/2.0/permissions/dashboards/[DASHBOARD_ID]", + "body": {} +} diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml b/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml new file mode 100644 index 0000000000..a50e6a7eed --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml @@ -0,0 +1,6 @@ +Local = false +Cloud = true +RequiresWarehouse = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/dashboards/create/output.txt b/acceptance/bundle/resources/permissions/dashboards/create/output.txt new file mode 100644 index 0000000000..a80a102323 --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/output.txt @@ -0,0 +1,35 @@ + +>>> [CLI] bundle validate -o json +[ + { + "level": "CAN_READ", + "user_name": "[TEST_USER_EMAIL]" + }, + { + "group_name": "[TEST_GROUP_NAME]", + "level": "CAN_MANAGE" + }, + { + "level": "CAN_MANAGE", + "service_principal_name": "[TEST_SP_APPLICATION_ID]" + }, + { + "level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } +] + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dashboard-perm-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete dashboard foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/dashboard-perm-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/permissions/dashboards/create/script b/acceptance/bundle/resources/permissions/dashboards/create/script new file mode 100644 index 0000000000..d9fbbde1db --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/script @@ -0,0 +1,27 @@ +if [ -z "$CLOUD_ENV" ]; then + export TEST_USER_EMAIL="viewer@databricks.com" + export TEST_GROUP_NAME="data-team" + export TEST_SP_APPLICATION_ID="f37d18cd-98a8-4db5-8112-12dd0a6bfe38" + + echo "$TEST_USER_EMAIL:TEST_USER_EMAIL" >> ACC_REPLS + echo "$TEST_GROUP_NAME:TEST_GROUP_NAME" >> ACC_REPLS + echo "$TEST_SP_APPLICATION_ID:TEST_SP_APPLICATION_ID" >> ACC_REPLS +fi + +envsubst < databricks.yml.tmpl > databricks.yml + +trace $CLI bundle validate -o json | jq .resources.dashboards.foo.permissions +rm out.requests.txt + +$CLI bundle debug plan > out.plan.$DATABRICKS_BUNDLE_ENGINE.json + +rm out.requests.txt +trace $CLI bundle deploy + +dashboard_id=$($CLI bundle summary --output json | jq -r '.resources.dashboards.foo.id') +echo "$dashboard_id:DASHBOARD_ID" >> ACC_REPLS + +print_requests.py //permissions > out.requests.deploy.$DATABRICKS_BUNDLE_ENGINE.json + +trace $CLI bundle destroy --auto-approve +print_requests.py //permissions > out.requests.destroy.$DATABRICKS_BUNDLE_ENGINE.json diff --git a/acceptance/bundle/resources/permissions/dashboards/create/test.toml b/acceptance/bundle/resources/permissions/dashboards/create/test.toml new file mode 100644 index 0000000000..eff14eed2e --- /dev/null +++ b/acceptance/bundle/resources/permissions/dashboards/create/test.toml @@ -0,0 +1,3 @@ +Local = false +RequiresWarehouse = true +Cloud = true diff --git a/acceptance/bundle/resources/permissions/output.txt b/acceptance/bundle/resources/permissions/output.txt index de0af0bbf7..5da10b6609 100644 --- a/acceptance/bundle/resources/permissions/output.txt +++ b/acceptance/bundle/resources/permissions/output.txt @@ -55,6 +55,18 @@ DIFF clusters/current_can_manage/out.requests.destroy.direct.json + "path": "/api/2.0/permissions/clusters/[UUID]" + } +] +MATCH dashboards/create/out.requests.deploy.direct.json +DIFF dashboards/create/out.requests.destroy.direct.json +--- dashboards/create/out.requests.destroy.direct.json ++++ dashboards/create/out.requests.destroy.terraform.json +@@ -1 +1,7 @@ +-[]+[ ++ { ++ "body": {}, ++ "method": "PUT", ++ "path": "/api/2.0/permissions/dashboards/[DASHBOARD_ID]" ++ } ++] MATCH database_instances/current_can_manage/out.requests.deploy.direct.json DIFF database_instances/current_can_manage/out.requests.destroy.direct.json --- database_instances/current_can_manage/out.requests.destroy.direct.json diff --git a/bundle/direct/dresources/all.go b/bundle/direct/dresources/all.go index d87549e81d..421b61641a 100644 --- a/bundle/direct/dresources/all.go +++ b/bundle/direct/dresources/all.go @@ -27,6 +27,7 @@ var SupportedResources = map[string]any{ "jobs.permissions": (*ResourcePermissions)(nil), "pipelines.permissions": (*ResourcePermissions)(nil), "apps.permissions": (*ResourcePermissions)(nil), + "dashboards.permissions": (*ResourcePermissions)(nil), "clusters.permissions": (*ResourcePermissions)(nil), "database_instances.permissions": (*ResourcePermissions)(nil), "experiments.permissions": (*ResourcePermissions)(nil), diff --git a/bundle/direct/dresources/all_test.go b/bundle/direct/dresources/all_test.go index 230463ce74..237184929f 100644 --- a/bundle/direct/dresources/all_test.go +++ b/bundle/direct/dresources/all_test.go @@ -19,6 +19,7 @@ import ( "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/databricks/databricks-sdk-go/service/dashboards" "github.com/databricks/databricks-sdk-go/service/database" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -252,6 +253,36 @@ var testDeps = map[string]prepareWorkspace{ }}, }, nil }, + + "dashboards.permissions": func(client *databricks.WorkspaceClient) (any, error) { + ctx := context.Background() + parentPath := "/Workspace/Users/user@example.com" + + // Create parent directory if it doesn't exist + err := client.Workspace.MkdirsByPath(ctx, parentPath) + if err != nil { + return nil, err + } + + resp, err := client.Lakeview.Create(ctx, dashboards.CreateDashboardRequest{ + Dashboard: dashboards.Dashboard{ + DisplayName: "dashboard-permissions", + ParentPath: parentPath, + SerializedDashboard: `{"pages":[{"name":"page1","displayName":"Page 1"}]}`, + }, + }) + if err != nil { + return nil, err + } + + return &PermissionsState{ + ObjectID: "/dashboards/" + resp.DashboardId, + Permissions: []iam.AccessControlRequest{{ + PermissionLevel: "CAN_MANAGE", + UserName: "user@example.com", + }}, + }, nil + }, } func TestAll(t *testing.T) {