diff --git a/bundle/config/mutator/run_as_test.go b/bundle/config/mutator/run_as_test.go index 498171582..236749550 100644 --- a/bundle/config/mutator/run_as_test.go +++ b/bundle/config/mutator/run_as_test.go @@ -179,6 +179,8 @@ func TestRunAsErrorForUnsupportedResources(t *testing.T) { Config: *r, } diags := bundle.Apply(context.Background(), b, SetRunAs()) - assert.Contains(t, diags.Error().Error(), "identity", rt) + assert.Contains(t, diags.Error().Error(), "do not support a setting a run_as user that is different from the owner.\n"+ + "Current identity: alice. Run as identity: bob.\n"+ + "See https://docs.databricks.com/dev-tools/bundles/run-as.html to learn more about the run_as property.", rt) } } diff --git a/bundle/deploy/terraform/apply.go b/bundle/deploy/terraform/apply.go index f08c60fba..08eb80336 100644 --- a/bundle/deploy/terraform/apply.go +++ b/bundle/deploy/terraform/apply.go @@ -32,7 +32,7 @@ func (w *apply) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { err = tf.Apply(ctx) if err != nil { - diagnosis := permissions.TryReportTerraformPermissionError(ctx, b, err) + diagnosis := permissions.TryExtendTerraformPermissionError(ctx, b, err) if diagnosis != nil { return diagnosis } diff --git a/bundle/permissions/permission_diagnostics.go b/bundle/permissions/permission_diagnostics.go index 8e1558a4e..4f84ad18f 100644 --- a/bundle/permissions/permission_diagnostics.go +++ b/bundle/permissions/permission_diagnostics.go @@ -143,14 +143,13 @@ func ReportPermissionDenied(ctx context.Context, b *bundle.Bundle, path string) }} } -func TryReportTerraformPermissionError(ctx context.Context, b *bundle.Bundle, err error) diag.Diagnostics { +func TryExtendTerraformPermissionError(ctx context.Context, b *bundle.Bundle, err error) diag.Diagnostics { _, assistance := analyzeBundlePermissions(b) - // In a best-effort attempt to provide curated error messages, we match + // In a best-effort attempt to provide actionable error messages, we match // against a few specific error messages that come from the Jobs and Pipelines API. - // Matching against messages isn't ideal but it's the best we can do right now. - // In the event one of these messages changes, we just show the direct API - // error instead. + // For matching errors we provide a more specific error message that includes + // details on how to resolve the issue. if !strings.Contains(err.Error(), "cannot update permissions") && !strings.Contains(err.Error(), "permissions on pipeline") && !strings.Contains(err.Error(), "cannot read permissions") && diff --git a/bundle/permissions/permission_diagnostics_test.go b/bundle/permissions/permission_diagnostics_test.go index 31085248d..e4aca2f7b 100644 --- a/bundle/permissions/permission_diagnostics_test.go +++ b/bundle/permissions/permission_diagnostics_test.go @@ -82,7 +82,7 @@ func TestReportTerraformError1(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, }) - err := TryReportTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 + err := TryExtendTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 Error: cannot update permissions: ... @@ -97,7 +97,7 @@ func TestReportTerraformError2(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, }) - err := TryReportTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 + err := TryExtendTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 Error: cannot read pipeline: User xyz does not have View permissions on pipeline 4521dbb6-42aa-418c-b94d-b5f4859a3454. @@ -112,7 +112,7 @@ func TestReportTerraformError3(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, }) - err := TryReportTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 + err := TryExtendTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 Error: cannot read permissions: 1706906c-c0a2-4c25-9f57-3a7aa3cb8b90 does not have Owner permissions on Job with ID: ElasticJobId(28263044278868). Please contact the owner or an administrator for access. @@ -130,7 +130,7 @@ func TestReportTerraformErrorNotOwner(t *testing.T) { b.Config.RunAs = &jobs.JobRunAs{ UserName: "testuser@databricks.com", } - err := TryReportTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 + err := TryExtendTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 Error: cannot read pipeline: User xyz does not have View permissions on pipeline 4521dbb6-42aa-418c-b94d-b5f4859a3454. diff --git a/bundle/tests/bundle_permissions/databricks.yml b/bundle/tests/bundle_permissions/databricks.yml index 794b49a9e..78f3d3d76 100644 --- a/bundle/tests/bundle_permissions/databricks.yml +++ b/bundle/tests/bundle_permissions/databricks.yml @@ -1,42 +1,35 @@ -# This is a Databricks asset bundle definition for my_project. -# See https://docs.databricks.com/dev-tools/bundles/index.html for documentation. bundle: - name: default_python + name: bundle_permissions -include: - - resources/*.yml +permissions: + - level: CAN_RUN + user_name: test@company.com targets: - # The 'dev' target, for development purposes. This target is the default. - dev: - # We use 'mode: development' to indicate this is a personal development copy: - # - Deployed resources get prefixed with '[dev my_user_name]' - # - Any job schedules and triggers are paused by default - # - The 'development' mode is used for Delta Live Tables pipelines - mode: development - default: true - workspace: - host: https://myworkspace.databricks.com - - ## Optionally, there could be a 'staging' target here. - ## (See Databricks docs on CI/CD at https://docs.databricks.com/dev-tools/bundles/ci-cd.html.) - # - # staging: - # workspace: - # host: https://myworkspace.databricks.com - - # The 'prod' target, used for production deployment. - prod: - # We use 'mode: production' to indicate this is a production deployment. - # Doing so enables strict verification of the settings below. - mode: production - workspace: - host: https://e2-dogfood.staging.cloud.databricks.com - # We always use /Users/user@company.com for all resources to make sure we only have a single copy. - # If this path results in an error, please make sure you have a recent version of the CLI installed. - root_path: /Users/user@company.com/.bundle/${bundle.name}/${bundle.target} - run_as: - user_name: user@company.com + development: permissions: - level: CAN_MANAGE - group_name: data_scientists@company.com + group_name: devs + - level: CAN_VIEW + service_principal_name: 1234-abcd + - level: CAN_RUN + user_name: bot@company.com + +resources: + pipelines: + nyc_taxi_pipeline: + target: nyc_taxi_production + development: false + photon: true + + jobs: + pipeline_schedule: + name: Daily refresh of production pipeline + + schedule: + quartz_cron_expression: 6 6 11 * * ? + timezone_id: UTC + + tasks: + - pipeline_task: + pipeline_id: "to be interpolated" diff --git a/bundle/tests/run_as_test.go b/bundle/tests/run_as_test.go index c92260bfc..37eed2896 100644 --- a/bundle/tests/run_as_test.go +++ b/bundle/tests/run_as_test.go @@ -114,7 +114,9 @@ func TestRunAsErrorForPipelines(t *testing.T) { err := diags.Error() configPath := filepath.FromSlash("run_as/not_allowed/pipelines/databricks.yml") - assert.ErrorContains(t, err, "pipelines do not support", configPath) + assert.ErrorContains(t, err, "pipelines do not support a setting a run_as user that is different from the owner.\n"+ + "Current identity: jane@doe.com. Run as identity: my_service_principal.\n"+ + "See https://docs", configPath) } func TestRunAsNoErrorForPipelines(t *testing.T) { @@ -152,7 +154,9 @@ func TestRunAsErrorForModelServing(t *testing.T) { diags := bundle.Apply(ctx, b, mutator.SetRunAs()) err := diags.Error() - assert.ErrorContains(t, err, "model_serving_endpoints do not support") + assert.ErrorContains(t, err, "model_serving_endpoints do not support a setting a run_as user that is different from the owner.\n"+ + "Current identity: jane@doe.com. Run as identity: my_service_principal.\n"+ + "See https://docs") } func TestRunAsNoErrorForModelServingEndpoints(t *testing.T) { diff --git a/libs/filer/workspace_files_client.go b/libs/filer/workspace_files_client.go index 1f91a4eea..eb5369561 100644 --- a/libs/filer/workspace_files_client.go +++ b/libs/filer/workspace_files_client.go @@ -270,7 +270,7 @@ func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]fs.D } // This API returns a 404 if the specified path does not exist, - // or if we don't have access to write ot the path. + // or if we don't have access to write to the path. if aerr.StatusCode == http.StatusNotFound { return nil, NoSuchDirectoryError{path.Dir(absPath)} }