mirror of https://github.com/databricks/cli.git
180 lines
5.8 KiB
Go
180 lines
5.8 KiB
Go
package permissions
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/databricks/cli/bundle"
|
|
"github.com/databricks/cli/libs/diag"
|
|
"github.com/databricks/cli/libs/log"
|
|
)
|
|
|
|
const CheckPermissionsFilename = "permissions.check"
|
|
|
|
type reportPermissionErrors struct{}
|
|
|
|
func PermissionDiagnostics() bundle.Mutator {
|
|
return &reportPermissionErrors{}
|
|
}
|
|
|
|
func (m *reportPermissionErrors) Name() string {
|
|
return "CheckPermissions"
|
|
}
|
|
|
|
func (m *reportPermissionErrors) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
|
if len(b.Config.Permissions) == 0 {
|
|
// Only warn if there is an explicit top-level permissions section
|
|
return nil
|
|
}
|
|
|
|
canManageBundle, _ := analyzeBundlePermissions(b)
|
|
if canManageBundle {
|
|
return nil
|
|
}
|
|
|
|
return diag.Diagnostics{{
|
|
Severity: diag.Warning,
|
|
Summary: fmt.Sprintf("permissions section should include %s or one of their groups with CAN_MANAGE permissions", b.Config.Workspace.CurrentUser.UserName),
|
|
Location: b.Config.GetLocation("permissions"),
|
|
ID: diag.PermissionNotIncluded,
|
|
}}
|
|
}
|
|
|
|
// analyzeBundlePermissions analyzes the top-level permissions of the bundle.
|
|
// This permission set is important since it determines the permissions of the
|
|
// target workspace folder.
|
|
//
|
|
// Returns:
|
|
// - isManager: true if the current user is can manage the bundle resources.
|
|
// - assistance: advice on who to contact as to manage this project
|
|
func analyzeBundlePermissions(b *bundle.Bundle) (bool, string) {
|
|
canManageBundle := false
|
|
otherManagers := make(map[string]bool)
|
|
if b.Config.RunAs != nil && b.Config.RunAs.UserName != "" {
|
|
// The run_as user is another human that could be contacted
|
|
// about this bundle.
|
|
otherManagers[b.Config.RunAs.UserName] = true
|
|
}
|
|
|
|
currentUser := b.Config.Workspace.CurrentUser.UserName
|
|
targetPermissions := b.Config.Permissions
|
|
for _, p := range targetPermissions {
|
|
if p.Level != CAN_MANAGE && p.Level != IS_OWNER {
|
|
continue
|
|
}
|
|
|
|
if p.UserName == currentUser || p.ServicePrincipalName == currentUser {
|
|
canManageBundle = true
|
|
continue
|
|
}
|
|
|
|
if isGroupOfCurrentUser(b, p.GroupName) {
|
|
canManageBundle = true
|
|
continue
|
|
}
|
|
|
|
// Permission doesn't apply to current user; add to otherManagers
|
|
otherManager := p.UserName
|
|
if otherManager == "" {
|
|
otherManager = p.GroupName
|
|
}
|
|
if otherManager == "" {
|
|
// Skip service principals
|
|
continue
|
|
}
|
|
otherManagers[otherManager] = true
|
|
}
|
|
|
|
var managersSlice []string
|
|
for manager := range otherManagers {
|
|
managersSlice = append(managersSlice, manager)
|
|
}
|
|
|
|
assistance := "For assistance, contact the owners of this project."
|
|
if len(managersSlice) > 0 {
|
|
assistance = fmt.Sprintf("For assistance, users or groups with appropriate permissions may include: %s.", strings.Join(managersSlice, ", "))
|
|
}
|
|
return canManageBundle, assistance
|
|
}
|
|
|
|
func isGroupOfCurrentUser(b *bundle.Bundle, groupName string) bool {
|
|
currentUserGroups := b.Config.Workspace.CurrentUser.User.Groups
|
|
|
|
for _, g := range currentUserGroups {
|
|
if g.Display == groupName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func ReportPermissionDenied(ctx context.Context, b *bundle.Bundle, path string) diag.Diagnostics {
|
|
log.Errorf(ctx, "Failed to update %v", path)
|
|
|
|
user := b.Config.Workspace.CurrentUser.DisplayName
|
|
canManageBundle, assistance := analyzeBundlePermissions(b)
|
|
|
|
if !canManageBundle {
|
|
return diag.Diagnostics{{
|
|
Summary: fmt.Sprintf("deployment permission denied for %s.\n"+
|
|
"Please make sure the current user or one of their groups is listed under the permissions of this bundle.\n"+
|
|
"%s\n"+
|
|
"They may need to redeploy the bundle to apply the new permissions.\n"+
|
|
"Please refer to https://docs.databricks.com/dev-tools/bundles/permissions.html for more on managing permissions.",
|
|
user, assistance),
|
|
Severity: diag.Error,
|
|
ID: diag.PathPermissionDenied,
|
|
}}
|
|
}
|
|
|
|
// According databricks.yml, the current user has the right permissions.
|
|
// But we're still seeing permission errors. So someone else will need
|
|
// to redeploy the bundle with the right set of permissions.
|
|
return diag.Diagnostics{{
|
|
Summary: fmt.Sprintf("access denied while updating deployment permissions for %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.",
|
|
user, assistance),
|
|
Severity: diag.Error,
|
|
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,
|
|
}}
|
|
}
|