diff --git a/acceptance/bundle/run_as/dashboard_embed_credentials/databricks.yml b/acceptance/bundle/run_as/dashboard_embed_credentials/databricks.yml new file mode 100644 index 0000000000..b62e0450c8 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed_credentials/databricks.yml @@ -0,0 +1,11 @@ +bundle: + name: "run_as" + +run_as: + service_principal_name: "my_service_principal" + +resources: + dashboards: + my_dashboard: + display_name: "Dashboard with embed" + embed_credentials: true diff --git a/acceptance/bundle/run_as/dashboard_embed_credentials/out.test.toml b/acceptance/bundle/run_as/dashboard_embed_credentials/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed_credentials/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/dashboard_embed_credentials/output.txt b/acceptance/bundle/run_as/dashboard_embed_credentials/output.txt new file mode 100644 index 0000000000..a7d4b682a6 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed_credentials/output.txt @@ -0,0 +1,12 @@ +Error: dashboards with embed_credentials set to true do not support a run_as identity that is different from the owner. +Current identity: [USERNAME]. Run as identity: my_service_principal. +See https://docs.databricks.com/dev-tools/bundles/run-as.html to learn more about the run_as property. + in databricks.yml:9:5 + +Name: run_as +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/run_as/default + +Found 1 error diff --git a/acceptance/bundle/run_as/dashboard_embed_credentials/script b/acceptance/bundle/run_as/dashboard_embed_credentials/script new file mode 100644 index 0000000000..26b424b480 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed_credentials/script @@ -0,0 +1 @@ +musterr $CLI bundle validate diff --git a/acceptance/bundle/run_as/dashboard_no_embed/databricks.yml b/acceptance/bundle/run_as/dashboard_no_embed/databricks.yml new file mode 100644 index 0000000000..b3c5c7f0c8 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_no_embed/databricks.yml @@ -0,0 +1,11 @@ +bundle: + name: "run_as" + +run_as: + service_principal_name: "my_service_principal" + +resources: + dashboards: + my_dashboard: + display_name: "Dashboard without embed" + embed_credentials: false diff --git a/acceptance/bundle/run_as/dashboard_no_embed/out.test.toml b/acceptance/bundle/run_as/dashboard_no_embed/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_no_embed/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/dashboard_no_embed/output.txt b/acceptance/bundle/run_as/dashboard_no_embed/output.txt new file mode 100644 index 0000000000..e1d564f209 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_no_embed/output.txt @@ -0,0 +1,7 @@ +Name: run_as +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/run_as/default + +Validation OK! diff --git a/acceptance/bundle/run_as/dashboard_no_embed/script b/acceptance/bundle/run_as/dashboard_no_embed/script new file mode 100644 index 0000000000..72555b332a --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_no_embed/script @@ -0,0 +1 @@ +$CLI bundle validate diff --git a/bundle/config/mutator/resourcemutator/run_as.go b/bundle/config/mutator/resourcemutator/run_as.go index d53d05ed13..42789d6e43 100644 --- a/bundle/config/mutator/resourcemutator/run_as.go +++ b/bundle/config/mutator/resourcemutator/run_as.go @@ -101,14 +101,21 @@ func validateRunAs(b *bundle.Bundle) diag.Diagnostics { )) } - // Dashboards do not support run_as in the API. - if len(b.Config.Resources.Dashboards) > 0 { - diags = diags.Extend(reportRunAsNotSupported( - "dashboards", - b.Config.GetLocation("resources.dashboards"), - b.Config.Workspace.CurrentUser.UserName, - identity, - )) + // Dashboards with embed_credentials set to true do not support run_as in the API. + // When embed_credentials is false (the default), the dashboard does not embed + // the owner's credentials, so run_as is irrelevant and we allow it. + for key, dashboard := range b.Config.Resources.Dashboards { + if dashboard.EmbedCredentials { + diags = diags.Extend(diag.Diagnostics{{ + Summary: fmt.Sprintf("dashboards with embed_credentials set to true do not support a run_as identity "+ + "that is different from the owner.\n"+ + "Current identity: %s. Run as identity: %s.\n"+ + "See https://docs.databricks.com/dev-tools/bundles/run-as.html to learn more about the run_as property.", + b.Config.Workspace.CurrentUser.UserName, identity), + Locations: []dyn.Location{b.Config.GetLocation(fmt.Sprintf("resources.dashboards.%s", key))}, + Severity: diag.Error, + }}) + } } // Apps do not support run_as in the API. diff --git a/bundle/config/mutator/resourcemutator/run_as_test.go b/bundle/config/mutator/resourcemutator/run_as_test.go index fbc87f940c..e0374728ed 100644 --- a/bundle/config/mutator/resourcemutator/run_as_test.go +++ b/bundle/config/mutator/resourcemutator/run_as_test.go @@ -159,6 +159,7 @@ func TestRunAsWorksForAllowedResources(t *testing.T) { var allowList = []string{ "alerts", "clusters", + "dashboards", "database_catalogs", "database_instances", "synced_database_tables", @@ -277,3 +278,69 @@ func TestRunAsNoErrorForSupportedResources(t *testing.T) { require.NoError(t, diags.Error()) } } + +func TestRunAsErrorForDashboardsWithEmbedCredentials(t *testing.T) { + config := config.Root{ + Workspace: config.Workspace{ + CurrentUser: &config.User{ + User: &iam.User{ + UserName: "alice", + }, + }, + }, + RunAs: &jobs.JobRunAs{ + UserName: "bob", + }, + Resources: config.Resources{ + Dashboards: map[string]*resources.Dashboard{ + "dash_with_embed": { + DashboardConfig: resources.DashboardConfig{ + DisplayName: "Dashboard with embed", + EmbedCredentials: true, + }, + }, + }, + }, + } + + b := &bundle.Bundle{ + Config: config, + } + + diags := bundle.Apply(context.Background(), b, SetRunAs()) + require.Error(t, diags.Error()) + assert.Contains(t, diags.Error().Error(), "dashboards with embed_credentials set to true do not support a run_as identity") + assert.Contains(t, diags.Error().Error(), "Current identity: alice. Run as identity: bob.") +} + +func TestRunAsAllowsDashboardsWithoutEmbedCredentials(t *testing.T) { + config := config.Root{ + Workspace: config.Workspace{ + CurrentUser: &config.User{ + User: &iam.User{ + UserName: "alice", + }, + }, + }, + RunAs: &jobs.JobRunAs{ + UserName: "bob", + }, + Resources: config.Resources{ + Dashboards: map[string]*resources.Dashboard{ + "dash_without_embed": { + DashboardConfig: resources.DashboardConfig{ + DisplayName: "Dashboard without embed", + EmbedCredentials: false, + }, + }, + }, + }, + } + + b := &bundle.Bundle{ + Config: config, + } + + diags := bundle.Apply(context.Background(), b, SetRunAs()) + require.NoError(t, diags.Error()) +}