2024-04-19 13:18:33 +00:00
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 ,
} }
}
2024-06-02 12:53:27 +00:00
func TryExtendTerraformPermissionError ( ctx context . Context , b * bundle . Bundle , err error ) diag . Diagnostics {
2024-04-19 13:18:33 +00:00
_ , assistance := analyzeBundlePermissions ( b )
2024-06-02 12:53:27 +00:00
// In a best-effort attempt to provide actionable error messages, we match
2024-04-19 13:18:33 +00:00
// against a few specific error messages that come from the Jobs and Pipelines API.
2024-06-02 12:53:27 +00:00
// For matching errors we provide a more specific error message that includes
// details on how to resolve the issue.
2024-04-19 13:18:33 +00:00
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 ,
} }
}