-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add new permissions support for volumes
- Loading branch information
1 parent
b4591ef
commit f6c3862
Showing
10 changed files
with
748 additions
and
390 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
aws-lambda/src/databricks_cdk/resources/permissions/changes.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from databricks.sdk.service.catalog import PermissionsChange, PermissionsList, Privilege, PrivilegeAssignment | ||
|
||
|
||
def get_assignment_dict_from_permissions_list(permissions_list: PermissionsList) -> dict[str, list[Privilege]]: | ||
"""Converts PermissionsList to dict with key of principal and list of associated privileges as value""" | ||
privilige_assignments = permissions_list.privilege_assignments or {} | ||
|
||
if privilige_assignments is None: | ||
return {} | ||
|
||
return { | ||
x.principal: [Privilege(y) for y in x.privileges] | ||
for x in privilige_assignments | ||
if permissions_list.privilege_assignments is not None and x.principal is not None and x.privileges is not None | ||
} | ||
|
||
|
||
def get_assignment_dict_from_privilege_assignments( | ||
privilege_assignments: list[PrivilegeAssignment], | ||
) -> dict[str, list[Privilege]]: | ||
"""Converts list of PrivilegeAssignment to dict with key of principal and list of associated privileges as value""" | ||
return { | ||
x.principal: x.privileges for x in privilege_assignments if x.principal is not None and x.privileges is not None | ||
} | ||
|
||
|
||
def get_permission_changes_principals( | ||
assignments_on_databricks_dict: dict[str, list[Privilege]], | ||
assignments_from_properties_dict: dict[str, list[Privilege]], | ||
) -> list[PermissionsChange]: | ||
"""See if there are new principals that need to be added""" | ||
permission_changes = [] | ||
|
||
principals_on_databricks = set(assignments_on_databricks_dict.keys()) | ||
principals_from_properties = set(assignments_from_properties_dict.keys()) | ||
|
||
principals_to_add = principals_from_properties.difference(principals_on_databricks) | ||
principals_to_remove = principals_on_databricks.difference(principals_from_properties) | ||
|
||
for principal in principals_to_add: | ||
permission_changes.append( | ||
PermissionsChange(principal=principal, add=assignments_from_properties_dict[principal]) | ||
) | ||
|
||
for principal in principals_to_remove: | ||
permission_changes.append( | ||
PermissionsChange(principal=principal, remove=assignments_on_databricks_dict[principal]) | ||
) | ||
|
||
return permission_changes | ||
|
||
|
||
def get_permission_changes_assignments_changed( | ||
assignments_on_databricks_dict: dict[str, list[Privilege]], | ||
assignments_from_properties_dict: dict[str, list[Privilege]], | ||
) -> list[PermissionsChange]: | ||
"""See if there are principal assignemnts that need to be updated""" | ||
permission_changes = [] | ||
for principal, privileges in assignments_from_properties_dict.items(): | ||
privileges_databricks = set(assignments_on_databricks_dict.get(principal, [])) | ||
privileges_properties = set(privileges) | ||
|
||
if privileges_databricks != privileges_properties and len(privileges_databricks) > 0: | ||
to_remove = privileges_databricks.difference(privileges_properties) | ||
to_add = privileges_properties.difference(privileges_databricks) | ||
permission_changes.append(PermissionsChange(principal=principal, add=list(to_add), remove=list(to_remove))) | ||
|
||
return permission_changes | ||
|
||
|
||
def get_permission_changes( | ||
assignments_on_databricks: PermissionsList, assignments_from_properties: list[PrivilegeAssignment] | ||
) -> list[PermissionsChange]: | ||
"""Get the changes between the existing grants and the new grants and create PermissionsChange object""" | ||
# Convert to dict for easier lookup | ||
assignments_on_databricks_dict = get_assignment_dict_from_permissions_list(assignments_on_databricks) | ||
assignments_from_properties_dict = get_assignment_dict_from_privilege_assignments(assignments_from_properties) | ||
|
||
permission_changes: list[PermissionsChange] = [] | ||
permission_changes += get_permission_changes_principals( | ||
assignments_on_databricks_dict, assignments_from_properties_dict | ||
) | ||
permission_changes += get_permission_changes_assignments_changed( | ||
assignments_on_databricks_dict, assignments_from_properties_dict | ||
) | ||
|
||
return permission_changes |
50 changes: 50 additions & 0 deletions
50
aws-lambda/src/databricks_cdk/resources/permissions/volume_permissions.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from databricks.sdk.service.catalog import PermissionsList, PrivilegeAssignment, SecurableType | ||
from pydantic import BaseModel | ||
|
||
from databricks_cdk.resources.permissions.changes import get_permission_changes | ||
from databricks_cdk.utils import CnfResponse, get_workspace_client | ||
|
||
|
||
class VolumePermissionsProperties(BaseModel): | ||
workspace_url: str | ||
volume_name: str | ||
privilege_assignments: list[PrivilegeAssignment] = [] | ||
|
||
|
||
def create_or_update_volume_permissions( | ||
properties: VolumePermissionsProperties, | ||
) -> CnfResponse: | ||
"""Create volume permissions on volume at databricks""" | ||
|
||
workspace_client = get_workspace_client(properties.workspace_url) | ||
existing_grants: PermissionsList = workspace_client.grants.get( | ||
securable_type=SecurableType.VOLUME, full_name=properties.volume_name | ||
) | ||
|
||
permission_changes = get_permission_changes(existing_grants, properties.privilege_assignments) | ||
|
||
workspace_client.grants.update( | ||
securable_type=SecurableType.VOLUME, full_name=properties.volume_name, changes=permission_changes | ||
) | ||
|
||
return CnfResponse( | ||
physical_resource_id=f"{properties.volume_name}/permissions", | ||
) | ||
|
||
|
||
def delete_volume_permissions(properties: VolumePermissionsProperties, physical_resource_id: str) -> CnfResponse: | ||
"""Deletes all volume permissions on volume at databricks""" | ||
workspace_client = get_workspace_client(properties.workspace_url) | ||
existing_grants: PermissionsList = workspace_client.grants.get( | ||
securable_type=SecurableType.VOLUME, full_name=properties.volume_name | ||
) | ||
|
||
permission_changes = get_permission_changes(existing_grants, []) | ||
|
||
workspace_client.grants.update( | ||
securable_type=SecurableType.VOLUME, full_name=properties.volume_name, changes=permission_changes | ||
) | ||
|
||
return CnfResponse( | ||
physical_resource_id=physical_resource_id, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
from databricks.sdk.service.catalog import PermissionsChange, PermissionsList, Privilege, PrivilegeAssignment | ||
|
||
from databricks_cdk.resources.permissions.changes import ( | ||
get_assignment_dict_from_permissions_list, | ||
get_assignment_dict_from_privilege_assignments, | ||
get_permission_changes, | ||
get_permission_changes_assignments_changed, | ||
get_permission_changes_principals, | ||
) | ||
|
||
|
||
def test_get_assignment_dict_from_permissions_list(): | ||
permissions_list = PermissionsList( | ||
**{ | ||
"privilege_assignments": [ | ||
PrivilegeAssignment(**{"principal": "principal1", "privileges": ["APPLY_TAG", "WRITE_FILES"]}), | ||
PrivilegeAssignment(**{"principal": "principal2", "privileges": ["READ_FILES"]}), | ||
] | ||
} | ||
) | ||
|
||
result = get_assignment_dict_from_permissions_list(permissions_list) | ||
|
||
assert result == { | ||
"principal1": [Privilege.APPLY_TAG, Privilege.WRITE_FILES], | ||
"principal2": [Privilege.READ_FILES], | ||
} | ||
|
||
|
||
def test_get_assignment_dict_from_permissions_list_empty(): | ||
permissions_list = PermissionsList(**{"privilege_assignments": None}) | ||
|
||
result = get_assignment_dict_from_permissions_list(permissions_list) | ||
|
||
assert result == {} | ||
|
||
|
||
def test_get_assignment_dict_from_privilege_assignments(): | ||
privilege_assignments = [ | ||
PrivilegeAssignment(**{"principal": "principal1", "privileges": [Privilege.APPLY_TAG, Privilege.WRITE_FILES]}), | ||
PrivilegeAssignment(**{"principal": "principal2", "privileges": [Privilege.READ_FILES]}), | ||
] | ||
|
||
result = get_assignment_dict_from_privilege_assignments(privilege_assignments) | ||
|
||
assert result == { | ||
"principal1": [Privilege.APPLY_TAG, Privilege.WRITE_FILES], | ||
"principal2": [Privilege.READ_FILES], | ||
} | ||
|
||
|
||
def test_get_assignment_dict_from_privilege_assignments_empty(): | ||
privilege_assignments = [ | ||
PrivilegeAssignment(**{"principal": "principal1", "privileges": None}), | ||
PrivilegeAssignment(**{"principal": None, "privileges": [Privilege.READ_FILES]}), | ||
] | ||
result = get_assignment_dict_from_privilege_assignments(privilege_assignments) | ||
|
||
assert result == {} | ||
|
||
|
||
def test_get_permission_changes_assignments_changed(): | ||
assignments_on_databricks_dict = {"principal": list(tuple([Privilege.APPLY_TAG, Privilege.WRITE_FILES]))} | ||
assignments_from_properties_dict = {"principal": list(tuple([Privilege.APPLY_TAG, Privilege.READ_FILES]))} | ||
|
||
result = get_permission_changes_assignments_changed( | ||
assignments_on_databricks_dict, assignments_from_properties_dict | ||
) | ||
|
||
# READ_FILES should be added WRITE_FILES should be removed | ||
assert result == [ | ||
PermissionsChange(add=[Privilege.READ_FILES], principal="principal", remove=[Privilege.WRITE_FILES]) | ||
] | ||
|
||
|
||
def test_get_permission_changes_assignments_changed_no_changes(): | ||
assignments_on_databricks_dict = {"principal": list(tuple([Privilege.APPLY_TAG, Privilege.WRITE_FILES]))} | ||
assignments_from_properties_dict = {"principal": list(tuple([Privilege.APPLY_TAG, Privilege.WRITE_FILES]))} | ||
|
||
result = get_permission_changes_assignments_changed( | ||
assignments_on_databricks_dict, assignments_from_properties_dict | ||
) | ||
|
||
# No changes | ||
assert result == [] | ||
|
||
|
||
def test_get_permission_changes_new_principals(): | ||
assignments_on_databricks_dict = { | ||
"principal_to_remove": list(tuple([Privilege.APPLY_TAG])), | ||
"principal_to_stay": list(tuple([Privilege.APPLY_TAG])), | ||
} | ||
assignments_from_properties_dict = { | ||
"principal_to_add": list(tuple([Privilege.APPLY_TAG])), | ||
"principal_to_stay": list(tuple([Privilege.APPLY_TAG])), | ||
} | ||
|
||
result = get_permission_changes_principals(assignments_on_databricks_dict, assignments_from_properties_dict) | ||
|
||
# No new principals | ||
assert result == [ | ||
PermissionsChange(add=[Privilege.APPLY_TAG], principal="principal_to_add", remove=None), | ||
PermissionsChange(add=None, principal="principal_to_remove", remove=[Privilege.APPLY_TAG]), | ||
] | ||
|
||
|
||
def test_get_permission_changes_no_principals(): | ||
assignments_on_databricks_dict = {} | ||
assignments_from_properties_dict = {} | ||
|
||
result = get_permission_changes_principals(assignments_on_databricks_dict, assignments_from_properties_dict) | ||
|
||
# No new principals | ||
assert result == [] | ||
|
||
|
||
def test_get_permission_changes(): | ||
sample_permissions_list = PermissionsList( | ||
privilege_assignments=[ | ||
PrivilegeAssignment(principal="user1", privileges=[Privilege.APPLY_TAG]), | ||
PrivilegeAssignment(principal="user2", privileges=[Privilege.CREATE]), | ||
PrivilegeAssignment(principal="userToRemove", privileges=[Privilege.CREATE]), | ||
] | ||
) | ||
sample_privilege_assignments = [ | ||
PrivilegeAssignment(principal="user1", privileges=[Privilege.APPLY_TAG]), | ||
PrivilegeAssignment(principal="user2", privileges=[Privilege.APPLY_TAG]), | ||
PrivilegeAssignment(principal="userToAdd", privileges=[Privilege.CREATE]), | ||
] | ||
|
||
result = get_permission_changes( | ||
assignments_on_databricks=sample_permissions_list, assignments_from_properties=sample_privilege_assignments | ||
) | ||
|
||
assert result == [ | ||
PermissionsChange(add=[Privilege.CREATE], principal="userToAdd", remove=None), | ||
PermissionsChange(add=None, principal="userToRemove", remove=[Privilege.CREATE]), | ||
PermissionsChange(add=[Privilege.APPLY_TAG], principal="user2", remove=[Privilege.CREATE]), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import {CustomResource} from "aws-cdk-lib"; | ||
import {Construct} from "constructs"; | ||
|
||
export enum PrivilegeVolume { | ||
APPLY_TAG = "APPLY_TAG", | ||
READ_VOLUME = "READ_VOLUME", | ||
WRITE_VOLUME = "WRITE_VOLUME", | ||
ALL_PRIVILEGES = "ALL_PRIVILEGES" | ||
} | ||
|
||
export interface PrivilegeAssignmentVolume { | ||
principal: string | ||
priviliges: Array<PrivilegeVolume> | ||
} | ||
|
||
export interface VolumePermissionsProperties { | ||
workspaceUrl: string | ||
privilege_assignments: Array<PrivilegeAssignmentVolume> | ||
} | ||
|
||
export interface VolumePermissionsProps extends VolumePermissionsProperties { | ||
readonly serviceToken: string | ||
} | ||
|
||
export class VolumePermissions extends CustomResource { | ||
constructor(scope: Construct, id: string, props: VolumePermissionsProps) { | ||
super(scope, id, { | ||
serviceToken: props.serviceToken, | ||
properties: { | ||
action: "volume-permissions", | ||
workspace_url: props.workspaceUrl, | ||
privilege_assignments: props.privilege_assignments, | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.