-
Notifications
You must be signed in to change notification settings - Fork 181
Support IS_OWNER as a top-level permission #1387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
2cce7f0
86b7a85
b9a03ce
6f0df28
e0fd22a
739df2f
ab04468
e4df0b9
6c6aaa8
ef6a43e
4df6fda
1870767
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,8 +154,15 @@ func validateProductionMode(ctx context.Context, b *bundle.Bundle, isPrincipalUs | |
| } | ||
| } | ||
|
|
||
| if !isPrincipalUsed && !isRunAsSet(r) { | ||
| return diag.Errorf("'run_as' must be set for all jobs when using 'mode: production'") | ||
| // We need to verify that there is only a single deployment of the current target. | ||
| // A good way to enforce this is to explicitly set root_path or run_as. | ||
| if !isExplicitRootSet(b) && !isRunAsSet(r) { | ||
| // Only show a warning in case a principal was used for backward compatibility | ||
| // with projects from before the DABs GA. | ||
| if isPrincipalUsed { | ||
| return diag.Warningf("target with 'mode: production' should specify explicit 'workspace.root_path' to make sure only one copy is deployed") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Service principals still have an exception, like they used to. |
||
| } | ||
| return diag.Errorf("target with 'mode: production' must specify explicit 'workspace.root_path' to make sure only one copy is deployed") | ||
| } | ||
| return nil | ||
| } | ||
|
|
@@ -172,6 +179,14 @@ func isRunAsSet(r config.Resources) bool { | |
| return true | ||
| } | ||
|
|
||
| func isExplicitRootSet(b *bundle.Bundle) bool { | ||
| targetConfig := b.Config.Targets[b.Config.Bundle.Target] | ||
| if targetConfig.Workspace == nil { | ||
| return false | ||
| } | ||
| return targetConfig.Workspace.RootPath != "" | ||
| } | ||
|
|
||
| func (m *processTargetMode) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { | ||
| switch b.Config.Bundle.Mode { | ||
| case config.Development: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,15 +9,15 @@ import ( | |
| "github.com/databricks/databricks-sdk-go/service/workspace" | ||
| ) | ||
|
|
||
| type workspaceRootPermissions struct { | ||
| type applyFolderPermissions struct { | ||
| } | ||
|
|
||
| func ApplyWorkspaceRootPermissions() bundle.Mutator { | ||
| return &workspaceRootPermissions{} | ||
| func ApplyFolderPermissions() bundle.Mutator { | ||
| return &applyFolderPermissions{} | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the rename? The mutator still applies only to the workspace root path.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That seems like a bug :( In any case I renamed this so
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw I don't think we should change the semantics of this module at this time, but we should include this in the upcoming work on permission warnings. |
||
|
|
||
| // Apply implements bundle.Mutator. | ||
| func (*workspaceRootPermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { | ||
| func (*applyFolderPermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { | ||
| err := giveAccessForWorkspaceRoot(ctx, b) | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
|
|
@@ -26,8 +26,8 @@ func (*workspaceRootPermissions) Apply(ctx context.Context, b *bundle.Bundle) di | |
| return nil | ||
| } | ||
|
|
||
| func (*workspaceRootPermissions) Name() string { | ||
| return "ApplyWorkspaceRootPermissions" | ||
| func (*applyFolderPermissions) Name() string { | ||
| return "ApplyFolderPermissions" | ||
| } | ||
|
|
||
| func giveAccessForWorkspaceRoot(ctx context.Context, b *bundle.Bundle) error { | ||
|
|
@@ -67,7 +67,7 @@ func giveAccessForWorkspaceRoot(ctx context.Context, b *bundle.Bundle) error { | |
|
|
||
| func getWorkspaceObjectPermissionLevel(bundlePermission string) (workspace.WorkspaceObjectPermissionLevel, error) { | ||
| switch bundlePermission { | ||
| case CAN_MANAGE: | ||
| case CAN_MANAGE, IS_OWNER: | ||
| return workspace.WorkspaceObjectPermissionLevelCanManage, nil | ||
| case CAN_RUN: | ||
| return workspace.WorkspaceObjectPermissionLevelCanRun, nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,47 +7,54 @@ import ( | |
| "strings" | ||
|
|
||
| "github.com/databricks/cli/bundle" | ||
| "github.com/databricks/cli/bundle/config/resources" | ||
| "github.com/databricks/cli/libs/diag" | ||
| ) | ||
|
|
||
| const CAN_MANAGE = "CAN_MANAGE" | ||
| const CAN_VIEW = "CAN_VIEW" | ||
| const CAN_RUN = "CAN_RUN" | ||
| const IS_OWNER = "IS_OWNER" | ||
|
|
||
| var allowedLevels = []string{CAN_MANAGE, CAN_VIEW, CAN_RUN} | ||
| var allowedLevels = []string{CAN_MANAGE, CAN_VIEW, CAN_RUN, IS_OWNER} | ||
| var levelsMap = map[string](map[string]string){ | ||
| "jobs": { | ||
| IS_OWNER: "IS_OWNER", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only a few resources, like jobs, actually have an "owner": https://docs.databricks.com/en/security/auth-authz/access-control/index.html. For almost all the others we don't distinguish between the owner and the other can-manage users. Note that clusters is a special case here. They don't have an owner permission but treat the creator as a kind of owner that has special privileges. There's even a special API for changing that notion of "owner": https://docs.databricks.com/api/workspace/clusters/changeowner. Once we do clusters we should discuss if we want this notion of an owner to affect how clusters are created, or if we perhaps want to show a warning when someone who doesn't have IS_OWNER would be the first creator of a cluster. |
||
| CAN_MANAGE: "CAN_MANAGE", | ||
| CAN_VIEW: "CAN_VIEW", | ||
| CAN_RUN: "CAN_MANAGE_RUN", | ||
| }, | ||
| "pipelines": { | ||
| IS_OWNER: "IS_OWNER", | ||
| CAN_MANAGE: "CAN_MANAGE", | ||
| CAN_VIEW: "CAN_VIEW", | ||
| CAN_RUN: "CAN_RUN", | ||
| }, | ||
| "mlflow_experiments": { | ||
| IS_OWNER: "CAN_MANAGE", | ||
| CAN_MANAGE: "CAN_MANAGE", | ||
| CAN_VIEW: "CAN_READ", | ||
| }, | ||
| "mlflow_models": { | ||
| IS_OWNER: "CAN_MANAGE", | ||
| CAN_MANAGE: "CAN_MANAGE", | ||
| CAN_VIEW: "CAN_READ", | ||
| }, | ||
| "model_serving_endpoints": { | ||
| IS_OWNER: "CAN_MANAGE", | ||
| CAN_MANAGE: "CAN_MANAGE", | ||
| CAN_VIEW: "CAN_VIEW", | ||
| CAN_RUN: "CAN_QUERY", | ||
| }, | ||
| } | ||
|
|
||
| type bundlePermissions struct{} | ||
| type applyResourcePermissions struct{} | ||
|
|
||
| func ApplyBundlePermissions() bundle.Mutator { | ||
| return &bundlePermissions{} | ||
| func ApplyResourcePermissions() bundle.Mutator { | ||
| return &applyResourcePermissions{} | ||
| } | ||
|
|
||
| func (m *bundlePermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { | ||
| func (m *applyResourcePermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { | ||
| err := validate(b) | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
|
|
@@ -74,64 +81,91 @@ func validate(b *bundle.Bundle) error { | |
|
|
||
| func applyForJobs(ctx context.Context, b *bundle.Bundle) { | ||
| for key, job := range b.Config.Resources.Jobs { | ||
| job.Permissions = append(job.Permissions, convert( | ||
| job.Permissions = extendPermissions(job.Permissions, convert( | ||
| ctx, | ||
| b.Config.Permissions, | ||
| job.Permissions, | ||
| key, | ||
| levelsMap["jobs"], | ||
| )...) | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| func applyForPipelines(ctx context.Context, b *bundle.Bundle) { | ||
| for key, pipeline := range b.Config.Resources.Pipelines { | ||
| pipeline.Permissions = append(pipeline.Permissions, convert( | ||
| pipeline.Permissions = extendPermissions(pipeline.Permissions, convert( | ||
| ctx, | ||
| b.Config.Permissions, | ||
| pipeline.Permissions, | ||
| key, | ||
| levelsMap["pipelines"], | ||
| )...) | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| func applyForMlExperiments(ctx context.Context, b *bundle.Bundle) { | ||
| for key, experiment := range b.Config.Resources.Experiments { | ||
| experiment.Permissions = append(experiment.Permissions, convert( | ||
| experiment.Permissions = extendPermissions(experiment.Permissions, convert( | ||
| ctx, | ||
| b.Config.Permissions, | ||
| experiment.Permissions, | ||
| key, | ||
| levelsMap["mlflow_experiments"], | ||
| )...) | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| func applyForMlModels(ctx context.Context, b *bundle.Bundle) { | ||
| for key, model := range b.Config.Resources.Models { | ||
| model.Permissions = append(model.Permissions, convert( | ||
| model.Permissions = extendPermissions(model.Permissions, convert( | ||
| ctx, | ||
| b.Config.Permissions, | ||
| model.Permissions, | ||
| key, | ||
| levelsMap["mlflow_models"], | ||
| )...) | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| func applyForModelServiceEndpoints(ctx context.Context, b *bundle.Bundle) { | ||
| for key, model := range b.Config.Resources.ModelServingEndpoints { | ||
| model.Permissions = append(model.Permissions, convert( | ||
| model.Permissions = extendPermissions(model.Permissions, convert( | ||
| ctx, | ||
| b.Config.Permissions, | ||
| model.Permissions, | ||
| key, | ||
| levelsMap["model_serving_endpoints"], | ||
| )...) | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| func (m *bundlePermissions) Name() string { | ||
| return "ApplyBundlePermissions" | ||
| func extendPermissions(permissions []resources.Permission, newPermissions []resources.Permission) []resources.Permission { | ||
| if !includesOwner(permissions) { | ||
| // Just merge the permissions, don't need to do anything special | ||
| return append(permissions, newPermissions...) | ||
| } | ||
|
|
||
| // Only apply the owner from top-level permissions if the local resource | ||
| // didn't have an owner. | ||
|
lennartkats-db marked this conversation as resolved.
Outdated
|
||
| for _, p := range newPermissions { | ||
| if p.Level == IS_OWNER { | ||
| continue | ||
| } | ||
| permissions = append(permissions, p) | ||
| } | ||
|
|
||
| return permissions | ||
| } | ||
|
|
||
| func includesOwner(permissions []resources.Permission) bool { | ||
| for _, p := range permissions { | ||
| if p.Level == IS_OWNER { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func (m *applyResourcePermissions) Name() string { | ||
| return "ApplyResourcePermissions" | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We used to require
but when considering collaborative deployment, we prefer
This change makes it so either restriction is allowed for
mode: production.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also warn if only
run_asis set? It doesn't prevent multiple deployments.