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
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 @@ -333,6 +333,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,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,34 @@

>>> [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
Warning: invalid value "CAN_MANAGE" for enum field. Valid values are [CAN_USE]
at resources.cluster_policies.test_cluster_policy.permissions[0].level
in databricks.yml:10:18

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,20 @@
cleanup() {
trace $CLI bundle destroy --auto-approve
rm -f out.requests.txt
}
trap cleanup EXIT

envsubst < databricks.yml.tmpl > databricks.yml

# CAN_USE is accepted: deploy succeeds and the PUT carries CAN_USE.
trace $CLI bundle deploy
read_id.py test_cluster_policy > /dev/null
trace print_requests.py //permissions/cluster-policies

# CAN_MANAGE is rejected, so the redeploy fails with the backend's error.
# Output goes to LOG (upload counts differ between the fake and cloud); the
# error message is deterministic and asserted here.
title "CAN_MANAGE is rejected"
update_file.py databricks.yml CAN_USE CAN_MANAGE
musterr $CLI bundle deploy &> LOG.deploy
cat LOG.deploy | contains.py "Unknown Cluster Policy Permission Level: CAN_MANAGE" > /dev/null
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_recreate

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

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test_cluster_policy_permissions_recreate/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

=== Permissions are set on the live policy
>>> [CLI] api get /api/2.0/permissions/cluster-policies/[TEST_CLUSTER_POLICY_ID]
{
"object_type": "cluster-policy",
"access_control_list": [
{
"all_permissions": [
{
"inherited": false,
"permission_level": "CAN_USE"
}
],
"group_name": "users"
}
]
}

=== Delete the policy out of band
>>> [CLI] cluster-policies delete [TEST_CLUSTER_POLICY_ID]

=== Permissions API still returns the ACLs for the deleted policy (no 404)
>>> [CLI] api get /api/2.0/permissions/cluster-policies/[TEST_CLUSTER_POLICY_ID]
{
"object_type": "cluster-policy",
"access_control_list": [
{
"all_permissions": [
{
"inherited": false,
"permission_level": "CAN_USE"
}
],
"group_name": "users"
}
]
}

=== Plan recreates the gone policy
>>> [CLI] bundle plan
create cluster_policies.test_cluster_policy
update cluster_policies.test_cluster_policy.permissions

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

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

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

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

trace $CLI bundle deploy
# Fetch the id via summary + add_repl (a shell function) rather than read_id.py:
# MSYS_NO_PATHCONV mangles the PATH-resolved python script location on Windows.
policy_id=$($CLI bundle summary --output json | jq -r '.resources.cluster_policies.test_cluster_policy.id')
add_repl "$policy_id" TEST_CLUSTER_POLICY_ID

title "Permissions are set on the live policy"
trace $CLI api get /api/2.0/permissions/cluster-policies/$policy_id | jq '{object_type, access_control_list}'

title "Delete the policy out of band"
trace $CLI cluster-policies delete "$policy_id"

# v1 quirk: the permissions endpoint keeps returning the ACLs for a deleted
# cluster policy; it does not 404 the way a v2 cascade-deleted resource would.
title "Permissions API still returns the ACLs for the deleted policy (no 404)"
trace $CLI api get /api/2.0/permissions/cluster-policies/$policy_id | jq '{object_type, access_control_list}'

title "Plan recreates the gone policy"
trace $CLI bundle plan
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Local-only: fixed policy name is not safe for concurrent cloud runs. Cloud
# coverage of the permission behavior lives in the levels test.
Cloud = false

# `api get` is passed a leading-slash path; without this, Git Bash on Windows
# rewrites /api/... to C:\Program Files\Git\api\... before the CLI sees it.
Env.MSYS_NO_PATHCONV = "1"

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,4 @@
# Local-only: fixed policy name is not safe for concurrent cloud runs. Cloud
# coverage of the permission behavior lives in the levels test.
Cloud = false
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
1 change: 1 addition & 0 deletions bundle/direct/dresources/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var permissionResourceToObjectType = map[string]string{
"alerts": "/alertsv2/",
"apps": "/apps/",
"clusters": "/clusters/",
"cluster_policies": "/cluster-policies/",
"instance_pools": "/instance-pools/",
"dashboards": "/dashboards/",
"genie_spaces": "/genie/",
Expand Down
Loading
Loading