diff --git a/bundle/permissions/permission_diagnostics.go b/bundle/permissions/permission_diagnostics.go index 4f84ad18f..7a6220ec6 100644 --- a/bundle/permissions/permission_diagnostics.go +++ b/bundle/permissions/permission_diagnostics.go @@ -3,7 +3,6 @@ package permissions import ( "context" "fmt" - "regexp" "strings" "github.com/databricks/cli/bundle" @@ -142,38 +141,3 @@ func ReportPermissionDenied(ctx context.Context, b *bundle.Bundle, path string) ID: diag.CannotChangePathPermissions, }} } - -func TryExtendTerraformPermissionError(ctx context.Context, b *bundle.Bundle, err error) diag.Diagnostics { - _, assistance := analyzeBundlePermissions(b) - - // 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. - // 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") && - !strings.Contains(err.Error(), "annot set run_as to user") { - return nil - } - - log.Errorf(ctx, "Terraform error during deployment: %v", err.Error()) - - // Best-effort attempt to extract the resource name from the error message. - re := regexp.MustCompile(`databricks_(\w*)\.(\w*)`) - match := re.FindStringSubmatch(err.Error()) - resource := "resource" - if len(match) > 1 { - resource = match[2] - } - - return diag.Diagnostics{{ - Summary: fmt.Sprintf("permission denied creating or updating %s.\n"+ - "%s\n"+ - "They can redeploy the project to apply the latest set of permissions.\n"+ - "Please refer to https://docs.databricks.com/dev-tools/bundles/permissions.html for more on managing permissions.", - resource, assistance), - Severity: diag.Error, - ID: diag.ResourcePermissionDenied, - }} -} diff --git a/bundle/permissions/permission_diagnostics_test.go b/bundle/permissions/permission_diagnostics_test.go index 8a62b0b19..dca9c7762 100644 --- a/bundle/permissions/permission_diagnostics_test.go +++ b/bundle/permissions/permission_diagnostics_test.go @@ -1,145 +1,81 @@ -package permissions +package permissions_test import ( "context" - "errors" "testing" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/permissions" "github.com/databricks/cli/libs/diag" "github.com/databricks/databricks-sdk-go/service/iam" - "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/require" ) -func TestApplySuccess(t *testing.T) { +func TestPermissionDiagnosticsApplySuccess(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_MANAGE", UserName: "testuser@databricks.com"}, }) - diags := PermissionDiagnostics().Apply(context.Background(), b) + diags := permissions.PermissionDiagnostics().Apply(context.Background(), b) require.NoError(t, diags.Error()) } -func TestApplyFail(t *testing.T) { +func TestPermissionDiagnosticsApplyFail(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_VIEW", UserName: "testuser@databricks.com"}, }) - diags := PermissionDiagnostics().Apply(context.Background(), b) + diags := permissions.PermissionDiagnostics().Apply(context.Background(), b) require.Equal(t, diags[0].Severity, diag.Warning) require.Contains(t, diags[0].Summary, "testuser@databricks.com") } -func TestApplySuccessWithOwner(t *testing.T) { +func TestPermissionDiagnosticsApplySuccessWithOwner(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "IS_OWNER", UserName: "testuser@databricks.com"}, }) - diags := PermissionDiagnostics().Apply(context.Background(), b) + diags := permissions.PermissionDiagnostics().Apply(context.Background(), b) require.Empty(t, diags) } -func TestPermissionDeniedWithPermission(t *testing.T) { +func TestPermissionDiagnosticsPermissionDeniedWithPermission(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_MANAGE", GroupName: "testgroup"}, }) - diags := ReportPermissionDenied(context.Background(), b, "testpath") + diags := permissions.ReportPermissionDenied(context.Background(), b, "testpath") require.ErrorContains(t, diags.Error(), string(diag.CannotChangePathPermissions)) } -func TestPermissionDeniedWithoutPermission(t *testing.T) { +func TestPermissionDiagnosticsPermissionDeniedWithoutPermission(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_VIEW", UserName: "testuser@databricks.com"}, }) - diags := ReportPermissionDenied(context.Background(), b, "testpath") + diags := permissions.ReportPermissionDenied(context.Background(), b, "testpath") require.ErrorContains(t, diags.Error(), string(diag.PathPermissionDenied)) } -func TestPermissionDeniedNilPermission(t *testing.T) { +func TestPermissionDiagnosticsPermissionDeniedNilPermission(t *testing.T) { b := mockBundle(nil) - diags := ReportPermissionDenied(context.Background(), b, "testpath") + diags := permissions.ReportPermissionDenied(context.Background(), b, "testpath") require.ErrorContains(t, diags.Error(), string(diag.PathPermissionDenied)) } -func TestFindOtherOwners(t *testing.T) { +func TestPermissionDiagnosticsFindOtherOwners(t *testing.T) { b := mockBundle([]resources.Permission{ {Level: "CAN_MANAGE", GroupName: "testgroup"}, {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, }) - diags := ReportPermissionDenied(context.Background(), b, "testpath") + diags := permissions.ReportPermissionDenied(context.Background(), b, "testpath") require.ErrorContains(t, diags.Error(), "include: alice@databricks.com") } -func TestReportTerraformError1(t *testing.T) { - ctx := context.Background() - b := mockBundle([]resources.Permission{ - {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, - }) - err := TryExtendTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 - -Error: cannot update permissions: ... - - with databricks_pipeline.my_project_pipeline, - on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: - 39: }`)).Error() - require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) -} - -func TestReportTerraformError2(t *testing.T) { - ctx := context.Background() - b := mockBundle([]resources.Permission{ - {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, - }) - 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. - - with databricks_pipeline.my_project_pipeline, - on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: - 39: }`)).Error() - require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) -} - -func TestReportTerraformError3(t *testing.T) { - ctx := context.Background() - b := mockBundle([]resources.Permission{ - {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, - }) - 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. - - with databricks_pipeline.my_project_pipeline, - on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: - 39: }`)).Error() - require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) -} - -func TestReportTerraformErrorNotOwner(t *testing.T) { - ctx := context.Background() - b := mockBundle([]resources.Permission{ - {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, - }) - b.Config.RunAs = &jobs.JobRunAs{ - UserName: "testuser@databricks.com", - } - 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. - - with databricks_pipeline.my_project_pipeline, - on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: - 39: }`)).Error() - require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) -} - func mockBundle(permissions []resources.Permission) *bundle.Bundle { return &bundle.Bundle{ Config: config.Root{ diff --git a/bundle/permissions/terraform_errors.go b/bundle/permissions/terraform_errors.go new file mode 100644 index 000000000..ab7d07aa7 --- /dev/null +++ b/bundle/permissions/terraform_errors.go @@ -0,0 +1,47 @@ +package permissions + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/log" +) + +func TryExtendTerraformPermissionError(ctx context.Context, b *bundle.Bundle, err error) diag.Diagnostics { + _, assistance := analyzeBundlePermissions(b) + + // 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. + // 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") && + !strings.Contains(err.Error(), "annot set run_as to user") { + return nil + } + + log.Errorf(ctx, "Terraform error during deployment: %v", err.Error()) + + // Best-effort attempt to extract the resource name from the error message. + re := regexp.MustCompile(`databricks_(\w*)\.(\w*)`) + match := re.FindStringSubmatch(err.Error()) + resource := "resource" + if len(match) > 1 { + resource = match[2] + } + + return diag.Diagnostics{{ + Summary: fmt.Sprintf("permission denied creating or updating %s.\n"+ + "%s\n"+ + "They can redeploy the project to apply the latest set of permissions.\n"+ + "Please refer to https://docs.databricks.com/dev-tools/bundles/permissions.html for more on managing permissions.", + resource, assistance), + Severity: diag.Error, + ID: diag.ResourcePermissionDenied, + }} +} diff --git a/bundle/permissions/terraform_errors_test.go b/bundle/permissions/terraform_errors_test.go new file mode 100644 index 000000000..1a4ef6c43 --- /dev/null +++ b/bundle/permissions/terraform_errors_test.go @@ -0,0 +1,76 @@ +package permissions_test + +import ( + "context" + "errors" + "testing" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/permissions" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/stretchr/testify/require" +) + +func TestTryExtendTerraformPermissionError1(t *testing.T) { + ctx := context.Background() + b := mockBundle([]resources.Permission{ + {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, + }) + err := permissions.TryExtendTerraformPermissionError(ctx, b, errors.New(`Error: terraform apply: exit status 1 + +Error: cannot update permissions: ... + + with databricks_pipeline.my_project_pipeline, + on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: + 39: }`)).Error() + require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) +} + +func TestTryExtendTerraformPermissionError2(t *testing.T) { + ctx := context.Background() + b := mockBundle([]resources.Permission{ + {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, + }) + err := permissions.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. + + with databricks_pipeline.my_project_pipeline, + on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: + 39: }`)).Error() + require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) +} + +func TestTryExtendTerraformPermissionError3(t *testing.T) { + ctx := context.Background() + b := mockBundle([]resources.Permission{ + {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, + }) + err := permissions.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. + + with databricks_pipeline.my_project_pipeline, + on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: + 39: }`)).Error() + require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) +} + +func TestTryExtendTerraformPermissionErrorNotOwner(t *testing.T) { + ctx := context.Background() + b := mockBundle([]resources.Permission{ + {Level: "CAN_MANAGE", UserName: "alice@databricks.com"}, + }) + b.Config.RunAs = &jobs.JobRunAs{ + UserName: "testuser@databricks.com", + } + err := permissions.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. + + with databricks_pipeline.my_project_pipeline, + on bundle.tf.json line 39, in resource.databricks_pipeline.my_project_pipeline: + 39: }`)).Error() + require.ErrorContains(t, err, string(diag.ResourcePermissionDenied)) +}