Skip to content
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

WIP: Add databricks permissions command #314

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2088c40
Add databricks permissions command
Aug 17, 2020
407abe7
Remove extra file
Aug 17, 2020
631f20b
Fix acidental removal of tests/__init__.py
Aug 17, 2020
c5075e1
tests/permissions/__init__.py was missing the license
Aug 17, 2020
18daab4
Merge branch 'master' into add-permissions
Sep 18, 2020
6f08afd
WIP: Add missing bits in the workspace API
Sep 18, 2020
4e17f06
Fix an issue where the if statements were checking type but raising a…
Sep 18, 2020
123a805
Remove WorkspaceApi import
Sep 18, 2020
c00458b
Fix a typo in WorkspaceFileInfo. We cannot change the order of argum…
Sep 18, 2020
4b7302c
get_id_for_directory was moved from the permissions api to the worksp…
Sep 18, 2020
e10f6f6
Fix lint errors about un-grouped imports
Sep 22, 2020
e8d55c0
Add some documentation showing all of the various urls that the permi…
Sep 22, 2020
849944c
databricks permissions:
Sep 22, 2020
5b59855
databricks cli permissions
Sep 22, 2020
12da975
Fix a lint error in databricks_cli/sdk/permissions.py caused by the t…
Sep 22, 2020
2c9af5e
databricks permissions:
Sep 22, 2020
eef1210
databricks permssions
Sep 23, 2020
0d9e5eb
Fix an issue with OneOfOption where the error would say Missing None.
Sep 23, 2020
d49cd78
databricks permssions
Sep 23, 2020
cf37d75
databricks permissions
Sep 23, 2020
cccf174
databricks permissions
Sep 23, 2020
25e710f
Add more tests of the permissions cli, still need to test the permiss…
Sep 23, 2020
6ca0a00
Fix lint errors
Sep 23, 2020
cfe232e
Merge remote-tracking branch 'upstream/master' into add-permissions
Sep 23, 2020
d9ba966
databricks permissions
Sep 23, 2020
fb5f354
databricks permissions:
Sep 23, 2020
83c6688
Checkpoint trying to cleanup how the permissions are managed to make …
Sep 24, 2020
b275af6
Merge branch 'master' into add-permissions
areese Oct 2, 2020
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
2 changes: 2 additions & 0 deletions databricks_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from databricks_cli.configure.config import profile_option, debug_option
from databricks_cli.libraries.cli import libraries_group
from databricks_cli.permissions.cli import permissions_group
from databricks_cli.version import print_version_callback, version
from databricks_cli.utils import CONTEXT_SETTINGS
from databricks_cli.configure.cli import configure_cli
Expand Down Expand Up @@ -63,6 +64,7 @@ def cli():
cli.add_command(groups_group, name='groups')
cli.add_command(instance_pools_group, name="instance-pools")
cli.add_command(pipelines_group, name='pipelines')
cli.add_command(permissions_group, name='permissions')

if __name__ == "__main__":
cli()
Empty file.
224 changes: 224 additions & 0 deletions databricks_cli/permissions/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Databricks CLI
# Copyright 2017 Databricks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"), except
# that the use of services to which certain application programming
# interfaces (each, an "API") connect requires that the user first obtain
# a license for the use of the APIs from Databricks, Inc. ("Databricks"),
# by creating an account at www.databricks.com and agreeing to either (a)
# the Community Edition Terms of Service, (b) the Databricks Terms of
# Service, or (c) another written agreement between Licensee and Databricks
# for the use of the APIs.
#
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import Enum

from databricks_cli.sdk.permissions_service import PermissionsService

from .exceptions import PermissionsError
from ..workspace.api import WorkspaceApi


class PermissionTargets(Enum):
clusters = 'clusters'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logically this should rather be a dictionary or list of tuples, because it's mixing two different things under same enumeration.

e.g. cluster is the object type and clusters is the prefix for object it,

cluster = clusters
directories = 'directories'
directory = directories
instance_pools = 'instance-pools'
instance_pool = instance_pools
jobs = 'jobs'
job = jobs
notebooks = 'notebooks'
notebook = notebooks
registered_models = 'registered-models'
registered_model = registered_models
model = registered_models
models = registered_models

def to_name(self):
return self[self.value]

@classmethod
def values(cls):
return [e.value for e in PermissionTargets]

@classmethod
def help_values(cls):
return ', '.join([e.value for e in PermissionTargets])


class PermissionLevel(Enum):
manage = 'CAN_MANAGE'
restart = 'CAN_RESTART'
attach = 'CAN_ATTACH_TO'
manage_run = 'CAN_MANAGE_RUN'
owner = 'IS_OWNER'
view = 'CAN_VIEW'
read = 'CAN_READ'
run = 'CAN_RUN'
edit = 'CAN_EDIT'

def to_name(self):
return self[self.value]


class PermissionType(Enum):
user = 'user_name'
group = 'group_name'
service = 'service_principal_name'

def to_name(self):
return self[self.value]


class Lookups(object):
items = {
'CAN_MANAGE': PermissionLevel.manage,
'CAN_RESTART': PermissionLevel.restart,
'CAN_ATTACH_TO': PermissionLevel.attach,
'CAN_MANAGE_RUN': PermissionLevel.manage_run,
'IS_OWNER': PermissionLevel.owner,
'CAN_VIEW': PermissionLevel.view,
'CAN_READ': PermissionLevel.read,
'CAN_RUN': PermissionLevel.run,
'CAN_EDIT': PermissionLevel.edit,
'user_name': PermissionType.user,
'group_name': PermissionType.group,
'service_principal_name': PermissionType.service,

'clusters': PermissionTargets.clusters,
'cluster': PermissionTargets.cluster,
'directories': PermissionTargets.directories,
'directory': PermissionTargets.directory,
'instance-pools': PermissionTargets.instance_pools,
'instance_pools': PermissionTargets.instance_pool,
'jobs': PermissionTargets.jobs,
'job': PermissionTargets.job,
'notebooks': PermissionTargets.notebooks,
'notebook': PermissionTargets.notebook,
'registered-models': PermissionTargets.registered_models,
'registered_models': PermissionTargets.registered_model,
'model': PermissionTargets.model,
'models': PermissionTargets.models,

}

@classmethod
def from_name(cls, name):
return cls.items[name]


class Permission(object):
def __init__(self, permission_type, value, permission_level):
# type: (PermissionType, str, PermissionLevel) -> None
self.permission_type = permission_type
self.permission_level = permission_level
if value is None:
value = ''

self.value = value

def to_dict(self):
# type: () -> dict
if not self.permission_type or not self.permission_level:
return {}

return {
self.permission_type.value: self.value,
'permission_level': self.permission_level.value
}


class PermissionsObject(object):
def __init__(self):
self.permissions = []

def add(self, permission):
# type: (Permission) -> None
self.permissions.append(permission)

def user(self, name, level):
# type: (str, PermissionLevel) -> None
self.add(Permission(PermissionType.user, value=name, permission_level=level))

def group(self, name, level):
# type: (str, PermissionLevel) -> None
self.add(Permission(PermissionType.group, value=name, permission_level=level))

def service(self, name, level):
# type: (str, PermissionLevel) -> None
self.add(Permission(PermissionType.service, value=name, permission_level=level))

def to_dict(self):
# type: () -> dict
if not self.permissions:
return {}

return {
'access_control_list': [entry.to_dict() for entry in self.permissions]
}


class PermissionsApi(object):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class doesn't expose a way to set / update permissions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, that's a good point I'll fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add update, but looking at https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-notebook-permissions

I'm kinda skeptical of set_permissions. The issue I have is it's dangerous, you could wipe all permission out everywhere if you are not careful.

I'm going to leave that out of this pr, and start a thread with engineering to determine if that's something that should be exposed.

def __init__(self, api_client):
self.api_client = api_client
self.client = PermissionsService(api_client)

def get_permissions(self, object_type, object_id):
# type: (str, str) -> dict
if not object_type:
raise PermissionsError('object_type is invalid')
if not object_id:
object_id = ''
# raise PermissionsError('object_id is invalid')

return self.client.get_permissions(object_type=PermissionTargets[object_type].value,
object_id=object_id)

def get_possible_permissions(self, object_type, object_id):
# type: (str, str) -> dict
if not object_type:
raise PermissionsError('object_type is invalid')
if not object_id:
raise PermissionsError('object_id is invalid')

return self.client.get_possible_permissions(
object_type=PermissionTargets[object_type].value,
object_id=object_id)

def add_permissions(self, object_type, object_id, permissions):
# type: (str, str, PermissionsObject) -> dict
if not object_type:
raise PermissionsError('object_type is invalid')
if not object_id:
raise PermissionsError('object_id is invalid')

return self.client.add_permissions(object_type=PermissionTargets[object_type].value,
object_id=object_id, data=permissions.to_dict())

def get_id_for_directory(self, path):
areese marked this conversation as resolved.
Show resolved Hide resolved
# type: (str) -> list[str]
"""
Given a path, use the workspaces API to look up the object id.
:param path: path to a directory
:return: object id, [] if not found
"""

if not path:
return []

objects = WorkspaceApi(self.api_client).list_directory_info(path)
if not objects:
return []

# ls -d already filtered for us
return [workspace_object.object_id for workspace_object in objects]
Loading