1717from databricks .sdk .core import DatabricksError
1818from databricks .sdk .errors import ResourceConflict
1919from databricks .sdk .retries import retried
20- from databricks .sdk .service import compute , iam , jobs , pipelines , workspace
20+ from databricks .sdk .service import compute , iam , jobs , pipelines , sql , workspace
2121from databricks .sdk .service .catalog import (
2222 CatalogInfo ,
2323 DataSourceFormat ,
2727)
2828from databricks .sdk .service .sql import (
2929 CreateWarehouseRequestWarehouseType ,
30+ GetResponse ,
31+ ObjectTypePlural ,
3032 Query ,
3133 QueryInfo ,
3234)
@@ -271,6 +273,47 @@ def _path(ws, path):
271273 ]
272274
273275
276+ def _redash_permissions_mapping ():
277+ def _simple (_ , object_id ):
278+ return object_id
279+
280+ return [
281+ (
282+ "query" ,
283+ ObjectTypePlural .QUERIES ,
284+ [
285+ sql .PermissionLevel .CAN_VIEW ,
286+ sql .PermissionLevel .CAN_RUN ,
287+ sql .PermissionLevel .CAN_MANAGE ,
288+ sql .PermissionLevel .CAN_EDIT ,
289+ ],
290+ _simple ,
291+ ),
292+ (
293+ "alert" ,
294+ ObjectTypePlural .ALERTS ,
295+ [
296+ sql .PermissionLevel .CAN_VIEW ,
297+ sql .PermissionLevel .CAN_RUN ,
298+ sql .PermissionLevel .CAN_MANAGE ,
299+ sql .PermissionLevel .CAN_EDIT ,
300+ ],
301+ _simple ,
302+ ),
303+ (
304+ "dashboard" ,
305+ ObjectTypePlural .DASHBOARDS ,
306+ [
307+ sql .PermissionLevel .CAN_VIEW ,
308+ sql .PermissionLevel .CAN_RUN ,
309+ sql .PermissionLevel .CAN_MANAGE ,
310+ sql .PermissionLevel .CAN_EDIT ,
311+ ],
312+ _simple ,
313+ ),
314+ ]
315+
316+
274317class _PermissionsChange :
275318 def __init__ (self , object_id : str , before : list [iam .AccessControlRequest ], after : list [iam .AccessControlRequest ]):
276319 self ._object_id = object_id
@@ -293,6 +336,26 @@ def __repr__(self):
293336 return f"{ self ._object_id } [{ self ._list (self ._before )} ] -> [{ self ._list (self ._after )} ]"
294337
295338
339+ class _RedashPermissionsChange :
340+ def __init__ (self , object_id : str , before : list [sql .AccessControl ], after : list [sql .AccessControl ]):
341+ self ._object_id = object_id
342+ self ._before = before
343+ self ._after = after
344+
345+ @staticmethod
346+ def _principal (acr : sql .AccessControl ) -> str :
347+ if acr .user_name is not None :
348+ return f"user_name { acr .user_name } "
349+ else :
350+ return f"group_name { acr .group_name } "
351+
352+ def _list (self , acl : list [sql .AccessControl ]):
353+ return ", " .join (f"{ self ._principal (_ )} { _ .permission_level .value } " for _ in acl )
354+
355+ def __repr__ (self ):
356+ return f"{ self ._object_id } [{ self ._list (self ._before )} ] -> [{ self ._list (self ._after )} ]"
357+
358+
296359def _make_permissions_factory (name , resource_type , levels , id_retriever ):
297360 def _non_inherited (x : iam .ObjectPermissions ):
298361 out : list [iam .AccessControlRequest ] = []
@@ -337,14 +400,29 @@ def create(
337400 names = ", " .join (_ .value for _ in levels )
338401 msg = f"invalid permission level: { permission_level .value } . Valid levels: { names } "
339402 raise ValueError (msg )
340- access_control_list = [
341- iam .AccessControlRequest (
342- group_name = group_name ,
343- user_name = user_name ,
344- service_principal_name = service_principal_name ,
345- permission_level = permission_level ,
403+
404+ access_control_list = []
405+ if group_name is not None :
406+ access_control_list .append (
407+ iam .AccessControlRequest (
408+ group_name = group_name ,
409+ permission_level = permission_level ,
410+ )
411+ )
412+ if user_name is not None :
413+ access_control_list .append (
414+ iam .AccessControlRequest (
415+ user_name = user_name ,
416+ permission_level = permission_level ,
417+ )
418+ )
419+ if service_principal_name is not None :
420+ access_control_list .append (
421+ iam .AccessControlRequest (
422+ service_principal_name = service_principal_name ,
423+ permission_level = permission_level ,
424+ )
346425 )
347- ]
348426 ws .permissions .update (resource_type , object_id , access_control_list = access_control_list )
349427 return _PermissionsChange (object_id , initial , access_control_list )
350428
@@ -356,12 +434,86 @@ def remove(change: _PermissionsChange):
356434 return _make_permissions
357435
358436
437+ def _make_redash_permissions_factory (name , resource_type , levels , id_retriever ):
438+ def _non_inherited (x : GetResponse ):
439+ out : list [sql .AccessControl ] = []
440+ assert x .access_control_list is not None
441+ for access_control in x .access_control_list :
442+ out .append (
443+ sql .AccessControl (
444+ permission_level = access_control .permission_level ,
445+ group_name = access_control .group_name ,
446+ user_name = access_control .user_name ,
447+ )
448+ )
449+ return out
450+
451+ def _make_permissions (ws ):
452+ def create (
453+ * ,
454+ object_id : str ,
455+ permission_level : sql .PermissionLevel | None = None ,
456+ group_name : str | None = None ,
457+ user_name : str | None = None ,
458+ access_control_list : Optional ["list[sql.AccessControl]" ] = None ,
459+ ):
460+ nothing_specified = permission_level is None and access_control_list is None
461+ both_specified = permission_level is not None and access_control_list is not None
462+ if nothing_specified or both_specified :
463+ msg = "either permission_level or access_control_list has to be specified"
464+ raise ValueError (msg )
465+
466+ object_id = id_retriever (ws , object_id )
467+ initial = _non_inherited (ws .dbsql_permissions .get (resource_type , object_id ))
468+
469+ if access_control_list is None :
470+ if permission_level not in levels :
471+ assert permission_level is not None
472+ names = ", " .join (_ .value for _ in levels )
473+ msg = f"invalid permission level: { permission_level .value } . Valid levels: { names } "
474+ raise ValueError (msg )
475+
476+ access_control_list = []
477+ if group_name is not None :
478+ access_control_list .append (
479+ sql .AccessControl (
480+ group_name = group_name ,
481+ permission_level = permission_level ,
482+ )
483+ )
484+ if user_name is not None :
485+ access_control_list .append (
486+ sql .AccessControl (
487+ user_name = user_name ,
488+ permission_level = permission_level ,
489+ )
490+ )
491+
492+ ws .dbsql_permissions .set (resource_type , object_id , access_control_list = access_control_list )
493+ return _RedashPermissionsChange (object_id , initial , access_control_list )
494+
495+ def remove (change : _RedashPermissionsChange ):
496+ ws .dbsql_permissions .set (
497+ sql .ObjectTypePlural (resource_type ), change ._object_id , access_control_list = change ._before
498+ )
499+
500+ yield from factory (f"{ name } permissions" , create , remove )
501+
502+ return _make_permissions
503+
504+
359505for name , resource_type , levels , id_retriever in _permissions_mapping ():
360506 # wrap function factory, otherwise loop scope sticks the wrong way
361507 locals ()[f"make_{ name } _permissions" ] = pytest .fixture (
362508 _make_permissions_factory (name , resource_type , levels , id_retriever )
363509 )
364510
511+ for name , resource_type , levels , id_retriever in _redash_permissions_mapping ():
512+ # wrap function factory, otherwise loop scope sticks the wrong way
513+ locals ()[f"make_{ name } _permissions" ] = pytest .fixture (
514+ _make_redash_permissions_factory (name , resource_type , levels , id_retriever )
515+ )
516+
365517
366518@pytest .fixture
367519def make_secret_scope (ws , make_random ):
0 commit comments