Skip to content
Open
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
3 changes: 3 additions & 0 deletions acceptance/bundle/invariant/configs/cluster_policy.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ resources:
foo:
name: test-cluster-policy-$UNIQUE_NAME
definition: '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}'
permissions:
- level: CAN_USE
group_name: users
6 changes: 6 additions & 0 deletions acceptance/bundle/refschema/out.fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ resources.cluster_policies.*.policy_family_definition_overrides string ALL
resources.cluster_policies.*.policy_family_id string ALL
resources.cluster_policies.*.policy_id string REMOTE
resources.cluster_policies.*.url string INPUT
resources.cluster_policies.*.permissions.object_id string ALL
resources.cluster_policies.*.permissions[*] dresources.StatePermission ALL
resources.cluster_policies.*.permissions[*].group_name string ALL
resources.cluster_policies.*.permissions[*].level iam.PermissionLevel ALL
resources.cluster_policies.*.permissions[*].service_principal_name string ALL
resources.cluster_policies.*.permissions[*].user_name string ALL
resources.clusters.*.apply_policy_default_values bool ALL
resources.clusters.*.autoscale *compute.AutoScale ALL
resources.clusters.*.autoscale.max_workers int ALL
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
bundle:
name: test_cluster_policy_permissions

resources:
cluster_policies:
test_cluster_policy:
name: my_cluster_policy
definition: '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}'
permissions:
- level: CAN_USE
group_name: users
- level: CAN_USE # TO_REMOVE
user_name: viewer@example.com # TO_REMOVE

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,84 @@

=== Validate: permissions parse onto the cluster policy
>>> [CLI] bundle validate -o json
[
{
"group_name": "users",
"level": "CAN_USE"
},
{
"level": "CAN_USE",
"user_name": "viewer@example.com"
}
]

=== Deploy: policy created and CAN_USE grants applied
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions/default/files...
Created cluster_policies.test_cluster_policy
Created cluster_policies.test_cluster_policy.permissions
Files: 5 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> print_requests.py //permissions/cluster-policies
{
"method": "PUT",
"path": "/api/2.0/permissions/cluster-policies/[UUID]",
"body": {
"access_control_list": [
{
"group_name": "users",
"permission_level": "CAN_USE"
},
{
"permission_level": "CAN_USE",
"user_name": "viewer@example.com"
}
]
}
}

>>> [CLI] bundle summary
Name: test_cluster_policy_permissions
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions/default
Resources:
Cluster Policies:
test_cluster_policy:
Name: my_cluster_policy
URL: [DATABRICKS_URL]/compute/policies/[UUID]?w=[NUMID]

=== Plan is a no-op immediately after deploy
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

=== Remove one grant and redeploy: the ACL is set again without it
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions/default/files...
Updated cluster_policies.test_cluster_policy.permissions
Files: 3 uploaded, 0 deleted
Resources: 0 created, 1 changed, 0 deleted, 1 unchanged

>>> print_requests.py //permissions/cluster-policies
{
"method": "PUT",
"path": "/api/2.0/permissions/cluster-policies/[UUID]",
"body": {
"access_control_list": [
{
"group_name": "users",
"permission_level": "CAN_USE"
}
]
}
}

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.cluster_policies.test_cluster_policy

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions/default

Destroy: 1 deleted
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
cleanup() {
trace $CLI bundle destroy --auto-approve
rm -f out.requests.txt
}
trap cleanup EXIT

title "Validate: permissions parse onto the cluster policy"
trace $CLI bundle validate -o json | jq ".resources.cluster_policies.test_cluster_policy.permissions"

title "Deploy: policy created and CAN_USE grants applied"
trace $CLI bundle deploy
trace print_requests.py //permissions/cluster-policies

trace $CLI bundle summary

title "Plan is a no-op immediately after deploy"
trace $CLI bundle plan

title "Remove one grant and redeploy: the ACL is set again without it"
grep -v TO_REMOVE databricks.yml > updated.yml && mv updated.yml databricks.yml
trace $CLI bundle deploy
trace print_requests.py //permissions/cluster-policies
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ignore = [".databricks", "databricks.yml", "updated.yml"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
bundle:
name: cluster-policy-permission-levels-$UNIQUE_NAME

resources:
cluster_policies:
test_cluster_policy:
name: my_cluster_policy-$UNIQUE_NAME
definition: '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}'
permissions:
- level: CAN_USE
group_name: users

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,30 @@

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/cluster-policy-permission-levels-[UNIQUE_NAME]/default/files...
Created cluster_policies.test_cluster_policy
Created cluster_policies.test_cluster_policy.permissions
Files: 6 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> print_requests.py //permissions/cluster-policies
{
"method": "PUT",
"path": "/api/2.0/permissions/cluster-policies/[TEST_CLUSTER_POLICY_ID]",
"body": {
"access_control_list": [
{
"group_name": "users",
"permission_level": "CAN_USE"
}
]
}
}

=== CAN_MANAGE is rejected
>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.cluster_policies.test_cluster_policy

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/cluster-policy-permission-levels-[UNIQUE_NAME]/default

Destroy: 1 deleted
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cleanup() {
trace $CLI bundle destroy --auto-approve
rm -f out.requests.txt
}
trap cleanup EXIT

envsubst < databricks.yml.tmpl > databricks.yml

# CAN_USE is the only level cluster policies accept: the deploy succeeds and the
# permissions PUT carries CAN_USE.
trace $CLI bundle deploy
policy_id="$(read_id.py test_cluster_policy)"
trace print_requests.py //permissions/cluster-policies

# CAN_MANAGE is rejected by the permissions API. The backend error text differs
# between the fake server and a real workspace, so it is routed to a LOG file;
# musterr asserts only that the request fails.
title "CAN_MANAGE is rejected"
musterr $CLI cluster-policies set-permissions "$policy_id" --json '{"access_control_list":[{"group_name":"users","permission_level":"CAN_MANAGE"}]}' &> LOG.can_manage
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Cloud = true
Ignore = [".databricks", "databricks.yml"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
bundle:
name: test_cluster_policy_permissions_drift

resources:
cluster_policies:
test_cluster_policy:
name: my_cluster_policy
definition: '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}'
permissions:
- level: CAN_USE
group_name: users

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,78 @@

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions_drift/default/files...
Created cluster_policies.test_cluster_policy
Created cluster_policies.test_cluster_policy.permissions
Files: 5 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

=== Plan is a no-op immediately after deploy
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

=== Grant an extra principal out of band
>>> [CLI] cluster-policies set-permissions [TEST_CLUSTER_POLICY_ID] --json {"access_control_list":[{"group_name":"users","permission_level":"CAN_USE"},{"user_name":"intruder@example.com","permission_level":"CAN_USE"}]}
{
"access_control_list": [
{
"all_permissions": [
{
"inherited": false,
"permission_level": "CAN_USE"
}
],
"group_name": "users"
},
{
"all_permissions": [
{
"inherited": false,
"permission_level": "CAN_USE"
}
],
"display_name": "intruder@example.com",
"user_name": "intruder@example.com"
}
],
"object_id": "/cluster-policies/[TEST_CLUSTER_POLICY_ID]",
"object_type": "cluster-policy"
}

=== Plan detects the permission drift
>>> [CLI] bundle plan
update cluster_policies.test_cluster_policy.permissions

Plan: 0 to add, 1 to change, 0 to delete, 1 unchanged

=== Redeploy reconciles the ACL back to the configured grants
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions_drift/default/files...
Updated cluster_policies.test_cluster_policy.permissions
Files: 2 uploaded, 0 deleted
Resources: 0 created, 1 changed, 0 deleted, 1 unchanged

>>> print_requests.py //permissions/cluster-policies
{
"method": "PUT",
"path": "/api/2.0/permissions/cluster-policies/[TEST_CLUSTER_POLICY_ID]",
"body": {
"access_control_list": [
{
"group_name": "users",
"permission_level": "CAN_USE"
}
]
}
}

=== Plan is a no-op again
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.cluster_policies.test_cluster_policy

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions_drift/default

Destroy: 1 deleted
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
cleanup() {
trace $CLI bundle destroy --auto-approve
rm -f out.requests.txt
}
trap cleanup EXIT

trace $CLI bundle deploy

title "Plan is a no-op immediately after deploy"
trace $CLI bundle plan

policy_id="$(read_id.py test_cluster_policy)"

# Simulate an out-of-band ACL change the way an admin would in the UI: grant an
# extra principal directly through the permissions API without touching
# databricks.yml. The recorded bundle state is now stale, so the next plan must
# detect the drift.
title "Grant an extra principal out of band"
trace $CLI cluster-policies set-permissions "$policy_id" --json '{"access_control_list":[{"group_name":"users","permission_level":"CAN_USE"},{"user_name":"intruder@example.com","permission_level":"CAN_USE"}]}'

# Discard the out-of-band request so the verification below captures only the
# reconciling Set issued by the redeploy.
rm -f out.requests.txt

title "Plan detects the permission drift"
trace $CLI bundle plan

title "Redeploy reconciles the ACL back to the configured grants"
trace $CLI bundle deploy
trace print_requests.py //permissions/cluster-policies

title "Plan is a no-op again"
trace $CLI bundle plan
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ignore = [".databricks", "databricks.yml"]
3 changes: 3 additions & 0 deletions bundle/config/mutator/resourcemutator/fix_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ var hasIsOwner = map[string]bool{

var ignoredResources = map[string]bool{
"secret_scopes": true,
// Cluster policies only support CAN_USE; injecting the current user as
// CAN_MANAGE/IS_OWNER would be rejected by the permissions API.
"cluster_policies": true,
}

// When processing permissions, we need to implement these constraints:
Expand Down
2 changes: 2 additions & 0 deletions bundle/config/resources/cluster_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type ClusterPolicy struct {
// Shadows the embedded compute.CreatePolicy.PolicyFamilyDefinitionOverrides (a string),
// same as Definition: also a policy document authorable as inline YAML.
PolicyFamilyDefinitionOverrides any `json:"policy_family_definition_overrides,omitempty"`

Permissions []ClusterPolicyPermission `json:"permissions,omitempty"`
}

func (s *ClusterPolicy) UnmarshalJSON(b []byte) error {
Expand Down
1 change: 1 addition & 0 deletions bundle/config/resources/permission_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func (p Permission) String() string {
type (
AppPermission PermissionT[apps.AppPermissionLevel]
ClusterPermission PermissionT[compute.ClusterPermissionLevel]
ClusterPolicyPermission PermissionT[compute.ClusterPolicyPermissionLevel]
InstancePoolPermission PermissionT[compute.InstancePoolPermissionLevel]
JobPermission PermissionT[jobs.JobPermissionLevel]
MlflowExperimentPermission PermissionT[ml.ExperimentPermissionLevel]
Expand Down
1 change: 1 addition & 0 deletions bundle/direct/dresources/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var SupportedResources = map[string]any{
"apps.permissions": (*ResourcePermissions)(nil),
"alerts.permissions": (*ResourcePermissions)(nil),
"clusters.permissions": (*ResourcePermissions)(nil),
"cluster_policies.permissions": (*ResourcePermissions)(nil),
"database_instances.permissions": (*ResourcePermissions)(nil),
"postgres_projects.permissions": (*ResourcePermissions)(nil),
"experiments.permissions": (*ResourcePermissions)(nil),
Expand Down
10 changes: 10 additions & 0 deletions bundle/direct/dresources/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,16 @@ var testDeps = map[string]prepareWorkspace{
}, nil
},

"cluster_policies.permissions": func(ctx context.Context, client *databricks.WorkspaceClient) (any, error) {
return &PermissionsState{
ObjectID: "/cluster-policies/cluster-policy-permissions",
EmbeddedSlice: []StatePermission{{
Level: "CAN_USE",
UserName: "user@example.com",
}},
}, nil
},

"instance_pools.permissions": func(ctx context.Context, client *databricks.WorkspaceClient) (any, error) {
return &PermissionsState{
ObjectID: "/instance-pools/pool-permissions",
Expand Down
Loading
Loading