databricks-cli/bundle/permissions/permission_diagnostics.go

151 lines
4.8 KiB
Go
Raw Normal View History

package permissions
import (
"context"
"fmt"
"strings"
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/log"
2024-07-28 20:45:51 +00:00
"github.com/databricks/cli/libs/set"
)
const CheckPermissionsFilename = "permissions.check"
2024-07-28 20:45:51 +00:00
type permissionDiagnostics struct{}
func PermissionDiagnostics() bundle.Mutator {
2024-07-28 20:45:51 +00:00
return &permissionDiagnostics{}
}
2024-07-28 20:45:51 +00:00
func (m *permissionDiagnostics) Name() string {
return "CheckPermissions"
}
2024-07-28 20:45:51 +00:00
func (m *permissionDiagnostics) 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),
Locations: []dyn.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
2024-07-28 20:45:51 +00:00
otherManagers := set.NewSet[string]()
2024-08-22 19:34:09 +00:00
if b.Config.RunAs != nil && b.Config.RunAs.UserName != "" && b.Config.RunAs.UserName != b.Config.Workspace.CurrentUser.UserName {
// The run_as user is another human that could be contacted
// about this bundle.
2024-07-28 20:45:51 +00:00
otherManagers.Add(b.Config.RunAs.UserName)
}
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
}
2024-07-28 20:45:51 +00:00
otherManagers.Add(otherManager)
}
assistance := "For assistance, contact the owners of this project."
2024-07-28 20:45:51 +00:00
if otherManagers.Size() > 0 {
assistance = fmt.Sprintf(
"For assistance, users or groups with appropriate permissions may include: %s.",
strings.Join(otherManagers.Values(), ", "),
)
}
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
}
// ReportPossiblePermissionDenied generates a diagnostic message when a permission denied error is encountered.
//
// Note that since the workspace API doesn't always distinguish between permission denied and path errors,
// we must treat this as a "possible permission error". See acquire.go for more about this.
func ReportPossiblePermissionDenied(ctx context.Context, b *bundle.Bundle, path string) diag.Diagnostics {
log.Errorf(ctx, "Failed to update, encountered possible permission error: %v", path)
user := b.Config.Workspace.CurrentUser.DisplayName
2024-08-22 19:34:09 +00:00
if user == "" {
user = b.Config.Workspace.CurrentUser.UserName
}
canManageBundle, assistance := analyzeBundlePermissions(b)
if !canManageBundle {
return diag.Diagnostics{{
Summary: fmt.Sprintf("unable to deploy to %s as %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.",
path, 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 as %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,
}}
}