Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dms_compat_auto true
experimental.use_legacy_run_as false
has_classic_interactive_compute false
has_classic_job_compute false
Expand All @@ -8,3 +9,4 @@ presets_name_prefix_is_set false
python_wheel_wrapper_is_set false
run_as_set false
skip_artifact_cleanup false
state_path_is_shared false
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dms_compat_auto true
experimental.use_legacy_run_as false
has_classic_interactive_compute false
has_classic_job_compute false
Expand All @@ -8,3 +9,4 @@ presets_name_prefix_is_set false
python_wheel_wrapper_is_set false
run_as_set false
skip_artifact_cleanup false
state_path_is_shared false
2 changes: 2 additions & 0 deletions acceptance/bundle/resource_deps/resources_var/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"[dev [USERNAME]] pipeline for mycatalog.myschema.myname"

>>> print_telemetry_bool_values
dms_compat_auto true
experimental.use_legacy_run_as false
has_classic_interactive_compute false
has_classic_job_compute false
Expand All @@ -46,3 +47,4 @@ presets_name_prefix_is_set true
python_wheel_wrapper_is_set false
run_as_set false
skip_artifact_cleanup false
state_path_is_shared false
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dms_compat_auto true
experimental.use_legacy_run_as false
has_classic_interactive_compute false
has_classic_job_compute false
Expand All @@ -9,3 +10,4 @@ presets_name_prefix_is_set false
python_wheel_wrapper_is_set false
run_as_set false
skip_artifact_cleanup false
state_path_is_shared false
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down
16 changes: 16 additions & 0 deletions acceptance/bundle/telemetry/deploy-compute-type/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": true
Expand Down Expand Up @@ -83,6 +91,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": true
Expand Down
8 changes: 8 additions & 0 deletions acceptance/bundle/telemetry/deploy-experimental/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down
16 changes: 16 additions & 0 deletions acceptance/bundle/telemetry/deploy-whl-artifacts/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down Expand Up @@ -84,6 +92,14 @@ Deployment complete!
"key": "skip_artifact_cleanup",
"value": true
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
bundle:
name: test-bundle

# The deploying user is tester@databricks.com. A workspace folder under
# /Workspace/Users/<owner> grants <owner> CAN_MANAGE, which the deploy observes as
# inherited access in the SetPermissions response.
targets:
# No permissions section: the migration mirrors the state folder's ACLs onto the
# deployment, so access is preserved wherever the state lives.
no_permissions:
default: true

# The deploying user (the home owner of the state folder) is declared.
user_declared:
permissions:
- user_name: ${workspace.current_user.userName}
level: CAN_MANAGE

# The deploying user (the home owner, with inherited CAN_MANAGE) is the only
# undeclared writer, so the migration could preserve them by granting the deployer.
user_not_declared:
permissions:
- group_name: team
level: CAN_MANAGE

# A shared state folder is writable by all workspace users; that access is declared
# via group_name: users CAN_MANAGE.
shared_users_can_manage:
permissions:
- group_name: users
level: CAN_MANAGE
workspace:
state_path: /Workspace/Shared/test-bundle-state

# A shared state folder without group_name: users CAN_MANAGE declared.
shared_not_declared:
permissions:
- user_name: ${workspace.current_user.userName}
level: CAN_MANAGE
workspace:
state_path: /Workspace/Shared/test-bundle-state

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

>>> [CLI] bundle deploy -t no_permissions
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/no_permissions/files...
Deploying resources...
Deployment complete!

>>> print_telemetry_bool_values
dms_compat_auto true
state_path_is_shared false

>>> [CLI] bundle deploy -t user_declared
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/user_declared/files...
Deploying resources...
Deployment complete!

>>> print_telemetry_bool_values
dms_compat_auto true
state_path_is_shared false

>>> [CLI] bundle deploy -t user_not_declared
Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups
If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy.

Consider using a adding a top-level permissions section such as the following:

permissions:
- user_name: [USERNAME]
level: CAN_MANAGE

See https://docs.databricks.com/dev-tools/bundles/permissions.html to learn more about permission configuration.
in databricks.yml:23:7

Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/user_not_declared/files...
Deploying resources...
Deployment complete!

>>> print_telemetry_bool_values
dms_compat_only_self_undeclared true
state_path_is_shared false

>>> [CLI] bundle deploy -t shared_users_can_manage
Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups
If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy.

Consider using a adding a top-level permissions section such as the following:

permissions:
- user_name: [USERNAME]
level: CAN_MANAGE

See https://docs.databricks.com/dev-tools/bundles/permissions.html to learn more about permission configuration.
in databricks.yml:30:7

Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/shared_users_can_manage/files...
Deploying resources...
Deployment complete!

>>> print_telemetry_bool_values
dms_compat_auto true
state_path_is_shared true

>>> [CLI] bundle deploy -t shared_not_declared
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/shared_not_declared/files...
Deploying resources...
Deployment complete!

>>> print_telemetry_bool_values
dms_compat_not true
state_path_is_shared true
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
for target in \
no_permissions \
user_declared \
user_not_declared \
shared_users_can_manage \
shared_not_declared; do
trace $CLI bundle deploy -t "$target"
trace print_telemetry_bool_values | grep -E "state_path|dms_compat"
rm out.requests.txt
done
8 changes: 8 additions & 0 deletions acceptance/bundle/telemetry/deploy/out.telemetry.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
"key": "skip_artifact_cleanup",
"value": false
},
{
"key": "state_path_is_shared",
"value": false
},
{
"key": "dms_compat_auto",
"value": true
},
{
"key": "has_serverless_compute",
"value": false
Expand Down
6 changes: 6 additions & 0 deletions bundle/config/resources/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ func (p PermissionT[L]) String() string {
return "level: " + string(p.Level)
}

// CAN_EDIT is the workspace folder ACL level marking write access. It is not a valid
// bundle permission level (those are CAN_MANAGE / CAN_VIEW / CAN_RUN, defined in the
// permissions package), but permission checks rank against it via GetLevelScore, so it
// is named here next to the level ordering it keys into.
const CAN_EDIT = "CAN_EDIT"

// PermissionOrder defines the hierarchy of permission levels.
// Higher numbers mean more permissive access.
// Based on https://docs.databricks.com/aws/en/security/auth/access-control
Expand Down
2 changes: 1 addition & 1 deletion bundle/config/validate/folder_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (f *folderPermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Di
return nil
}

bundlePaths := paths.CollectUniqueWorkspacePathPrefixes(b.Config.Workspace)
bundlePaths := paths.CollectUniqueWorkspacePathPrefixes(b.Config.Workspace).Paths

var diags diag.Diagnostics
g, ctx := errgroup.WithContext(ctx)
Expand Down
29 changes: 29 additions & 0 deletions bundle/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,33 @@ const (
ClusterLifecycleStarted = "cluster_lifecycle_started"
SqlWarehouseLifecycleStarted = "sql_warehouse_lifecycle_started"
SelectUsed = "select_used"

// Whether workspace.state_path is under /Workspace/Shared.
StatePathIsShared = "state_path_is_shared"

// Whether this deploy is compatible with an automatic migration of the deployment
// state to a dedicated state storage service (DMS). Deploying a bundle requires
// write access (CAN_EDIT or higher) to the state folder; after migration that is
// governed by the permissions on the deployment object instead.
//
// When the bundle has no permissions section, the migration can mirror the state
// folder's ACLs onto the deployment (CAN_EDIT -> CAN_EDIT, CAN_MANAGE ->
// CAN_MANAGE), preserving everyone's access wherever the state lives. When a
// permissions section is set, the migration applies exactly those permissions, so
// anyone with write access to the state folder who is not declared with
// CAN_MANAGE would lose the ability to deploy.
//
// Exactly one of the three keys below is recorded per deploy:
// - auto: no permissions section (folder ACLs are mirrored), or every principal
// with write access to the state folder is declared.
// - only_self_undeclared: a permissions section is set and the only principal
// with undeclared write access is the deploying user. The migration
// grants the deploying user CAN_MANAGE on the deployment object, so this
// is auto-migratable if we choose to preserve that grant on future
// deploys. Recorded separately to measure how common this case is.
// - not: a permissions section is set and the state folder has undeclared write
// access from a principal other than the deploying user.
DMSCompatAuto = "dms_compat_auto"
DMSCompatOnlySelfUndeclared = "dms_compat_only_self_undeclared"
DMSCompatNot = "dms_compat_not"
)
27 changes: 25 additions & 2 deletions bundle/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ import (
"github.com/databricks/cli/bundle/libraries"
)

func CollectUniqueWorkspacePathPrefixes(workspace config.Workspace) []string {
// WorkspacePaths holds the unique workspace folders that bundle permissions are applied
// to. A logical bundle path (artifact, file, state, resource) nested under another is
// represented only by its enclosing folder, so permissions are applied once and the
// nested paths inherit them.
type WorkspacePaths struct {
Paths []string
}

func CollectUniqueWorkspacePathPrefixes(workspace config.Workspace) WorkspacePaths {
rootPath := workspace.RootPath
var paths []string
if !libraries.IsVolumesPath(rootPath) {
Expand Down Expand Up @@ -35,5 +43,20 @@ func CollectUniqueWorkspacePathPrefixes(workspace config.Workspace) []string {
paths = append(paths, p)
}

return paths
return WorkspacePaths{Paths: paths}
}

// Governing returns the path whose ACL governs the given path: the path itself when it
// is one of the collected paths, or the enclosing path when it is nested under one (e.g.
// the state path nested under the root path, which is deduplicated out of Paths).
// Returns an empty string when no path governs it, e.g. a Volumes path that permissions
// don't apply to. Paths are /Workspace-normalized by PrependWorkspacePrefix.
func (w WorkspacePaths) Governing(path string) string {
for _, p := range w.Paths {
trimmed := strings.TrimSuffix(p, "/")
if path == trimmed || strings.HasPrefix(path, trimmed+"/") {
return p
}
}
return ""
}
Loading
Loading