zipher-sdk 0.3.9__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zipher-sdk-0.3.9/zipher_sdk.egg-info → zipher-sdk-0.4.2}/PKG-INFO +1 -1
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/setup.py +2 -2
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_acl/test_acl.py +63 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_client.py +0 -3
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/acl/models.py +1 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/acl/zipher_installer.py +28 -2
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/cli.py +9 -1
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2/zipher_sdk.egg-info}/PKG-INFO +1 -1
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/requires.txt +1 -1
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/LICENSE +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/README.md +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/setup.cfg +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/__init__.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_acl/__init__.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_acl/sample_job.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/__init__.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/acl/__init__.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/client.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/exceptions.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/models.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/utils.py +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/SOURCES.txt +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/dependency_links.txt +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/entry_points.txt +0 -0
- {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8') as f:
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name='zipher-sdk',
|
|
11
|
-
version='0.
|
|
11
|
+
version='0.4.2',
|
|
12
12
|
description='Python SDK for programmatic access to the Zipher API',
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type='text/markdown',
|
|
@@ -16,7 +16,7 @@ setup(
|
|
|
16
16
|
author_email='yoav@zipher.cloud',
|
|
17
17
|
packages=find_packages(),
|
|
18
18
|
install_requires=[
|
|
19
|
-
"pydantic
|
|
19
|
+
"pydantic==2.11.3",
|
|
20
20
|
"requests==2.32.3",
|
|
21
21
|
"databricks-sdk==0.52.0"
|
|
22
22
|
],
|
|
@@ -351,3 +351,66 @@ class TestACL(TestCase):
|
|
|
351
351
|
cls.admin_dbx.dbfs.delete(path=f'dbfs:/{cls.dbfs_test_folder}', recursive=True)
|
|
352
352
|
cls.admin_dbx.queries.delete(cls.query.id)
|
|
353
353
|
cls.admin_dbx.warehouses.delete(cls.warehouse.id)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class TestExistingServicePrincipal(TestCase):
|
|
357
|
+
"""Separate test class for testing existing_service_principal_id without running expensive setUpClass."""
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def setUpClass(cls):
|
|
361
|
+
cls.host = os.environ['DATABRICKS_HOST_TEST']
|
|
362
|
+
cls.admin_dbx: WorkspaceClient = WorkspaceClient(host=cls.host, token=os.environ['DATABRICKS_TOKEN_TEST'])
|
|
363
|
+
|
|
364
|
+
cls.test_uuid = uuid4()
|
|
365
|
+
entitlements = [iam.ComplexValue(value=ent) for ent in ['allow-cluster-create']]
|
|
366
|
+
groups = [iam.ComplexValue(value=g.id) for g in cls.admin_dbx.groups.list() if g.display_name in ['users']]
|
|
367
|
+
cls.sp = cls.admin_dbx.service_principals.create(display_name=f"test_existing_sp_{cls.test_uuid}",
|
|
368
|
+
groups=groups,
|
|
369
|
+
entitlements=entitlements)
|
|
370
|
+
|
|
371
|
+
permissions_to_grant = [TokenAccessControlRequest(service_principal_name=cls.sp.application_id,
|
|
372
|
+
permission_level=TokenPermissionLevel.CAN_USE)]
|
|
373
|
+
cls.admin_dbx.token_management.update_permissions(access_control_list=permissions_to_grant)
|
|
374
|
+
|
|
375
|
+
def test_existing_service_principal(self):
|
|
376
|
+
"""Test that setup uses existing service principal when existing_service_principal_id is provided and doesn't create a new one."""
|
|
377
|
+
test_uuid = uuid4()
|
|
378
|
+
zipher_sp_name = f'zipher_sp_existing_{test_uuid}'
|
|
379
|
+
|
|
380
|
+
installer_config = InstallerConfig(
|
|
381
|
+
zipher_group_name=f'zipher_existing_sp_{test_uuid}',
|
|
382
|
+
zipher_service_principal_name=zipher_sp_name,
|
|
383
|
+
zipher_workspace_dir_path=f'/zipher_script_existing_{test_uuid}',
|
|
384
|
+
zipher_secret_scope_name=f'zipher_existing_{test_uuid}'
|
|
385
|
+
)
|
|
386
|
+
installer = ZipherInstaller(host=self.host, token=os.environ['DATABRICKS_TOKEN_TEST'], config=installer_config)
|
|
387
|
+
|
|
388
|
+
user_config = InstallerUserConfig(
|
|
389
|
+
jobs_list=[],
|
|
390
|
+
existing_service_principal_id=self.sp.id
|
|
391
|
+
)
|
|
392
|
+
zipher_creds = installer.setup(user_config=user_config, skip_approval=True)
|
|
393
|
+
|
|
394
|
+
assert zipher_creds is not None, "Setup should return credentials"
|
|
395
|
+
zipher_sps = [sp for sp in self.admin_dbx.service_principals.list() if sp.display_name == zipher_sp_name]
|
|
396
|
+
assert len(zipher_sps) == 0, "Should not create new SP when using existing_service_principal_id"
|
|
397
|
+
|
|
398
|
+
# Verify existing SP is in the zipher group
|
|
399
|
+
group = [g for g in self.admin_dbx.groups.list() if g.display_name == installer_config.zipher_group_name][0]
|
|
400
|
+
group_details = self.admin_dbx.groups.get(id=group.id)
|
|
401
|
+
sp_in_group = any(member.value == self.sp.id for member in (group_details.members or []))
|
|
402
|
+
assert sp_in_group, f"Existing SP {self.sp.id} should be in group {installer_config.zipher_group_name}"
|
|
403
|
+
|
|
404
|
+
# Verify existing SP has token permissions
|
|
405
|
+
token_perms = self.admin_dbx.token_management.get_permissions()
|
|
406
|
+
sp_has_token_perms = any(
|
|
407
|
+
hasattr(perm, 'service_principal_name') and perm.service_principal_name == self.sp.application_id
|
|
408
|
+
for perm in token_perms.access_control_list
|
|
409
|
+
)
|
|
410
|
+
assert sp_has_token_perms, f"Existing SP {self.sp.application_id} should have token permissions"
|
|
411
|
+
|
|
412
|
+
installer.clean_resources()
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def tearDownClass(cls):
|
|
416
|
+
cls.admin_dbx.service_principals.delete(id=cls.sp.id)
|
|
@@ -49,9 +49,6 @@ class TestClient(unittest.TestCase):
|
|
|
49
49
|
res = self.client.update_existing_conf(job_id="1234", existing_conf=config_to_update)
|
|
50
50
|
self.assertDictEqual(config_to_update, res)
|
|
51
51
|
|
|
52
|
-
def test_pydantic_version(self):
|
|
53
|
-
import pydantic
|
|
54
|
-
print(pydantic.__version__)
|
|
55
52
|
|
|
56
53
|
if __name__ == '__main__':
|
|
57
54
|
unittest.main()
|
|
@@ -47,3 +47,4 @@ class InstallerUserConfig(BaseModel, extra='forbid', use_enum_values=True):
|
|
|
47
47
|
jobs_list : Optional[List[str]] = None
|
|
48
48
|
jobs_permission: JobPermissions = JobPermissions.CAN_MANAGE
|
|
49
49
|
service_principal_token_type: ServicePrincipalTokenType = ServicePrincipalTokenType.OAUTH
|
|
50
|
+
existing_service_principal_id: Optional[str] = None
|
|
@@ -219,6 +219,29 @@ class ZipherInstaller:
|
|
|
219
219
|
|
|
220
220
|
return service_principal
|
|
221
221
|
|
|
222
|
+
def _use_existing_service_principal(self, sp_id: str, zipher_group: Group) -> ServicePrincipal:
|
|
223
|
+
"""Use an existing service principal and add it to the zipher group with token permissions."""
|
|
224
|
+
sp = self.dbx_client.service_principals.get(id=sp_id)
|
|
225
|
+
print(f"\nUsing existing service principal: {sp.display_name} (id={sp.id})")
|
|
226
|
+
|
|
227
|
+
# Add existing SP to the zipher group (same as in _create_service_principal)
|
|
228
|
+
existing_groups = [g.value for g in sp.groups] if sp.groups else []
|
|
229
|
+
if zipher_group.id not in existing_groups:
|
|
230
|
+
updated_groups = existing_groups + [zipher_group.id]
|
|
231
|
+
self.dbx_client.service_principals.update(
|
|
232
|
+
id=sp.id,
|
|
233
|
+
display_name=sp.display_name,
|
|
234
|
+
groups=[iam.ComplexValue(value=gid) for gid in updated_groups]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Grant token permissions (same logic as _create_service_principal lines 214-218)
|
|
238
|
+
permissions_to_grant = [TokenAccessControlRequest(service_principal_name=sp.application_id,
|
|
239
|
+
permission_level=TokenPermissionLevel.CAN_USE)]
|
|
240
|
+
self.dbx_client.token_management.update_permissions(access_control_list=permissions_to_grant)
|
|
241
|
+
print(f"Granted token permissions to existing service principal\n")
|
|
242
|
+
|
|
243
|
+
return sp
|
|
244
|
+
|
|
222
245
|
def _create_oauth_secret_for_sp(self, sp: ServicePrincipal):
|
|
223
246
|
token = self.dbx_client.config.token or self.dbx_client.config.oauth_token().access_token
|
|
224
247
|
|
|
@@ -317,8 +340,11 @@ class ZipherInstaller:
|
|
|
317
340
|
|
|
318
341
|
self._enable_job_permissions(user_config=user_config)
|
|
319
342
|
|
|
320
|
-
|
|
321
|
-
|
|
343
|
+
if user_config.existing_service_principal_id:
|
|
344
|
+
sp = self._use_existing_service_principal(user_config.existing_service_principal_id, group)
|
|
345
|
+
else:
|
|
346
|
+
sp = self._create_service_principal(name=self.config.zipher_service_principal_name,
|
|
347
|
+
zipher_group=group)
|
|
322
348
|
|
|
323
349
|
self._add_unity_catalog_permissions(sp)
|
|
324
350
|
|
|
@@ -106,6 +106,13 @@ def main():
|
|
|
106
106
|
help="Skip user input approval."
|
|
107
107
|
)
|
|
108
108
|
|
|
109
|
+
setup_parser.add_argument(
|
|
110
|
+
"--existing-sp-id",
|
|
111
|
+
type=str,
|
|
112
|
+
default=None,
|
|
113
|
+
help="Use existing service principal by ID instead of creating a new one."
|
|
114
|
+
)
|
|
115
|
+
|
|
109
116
|
remove_parser = subparsers.add_parser("remove", help="Remove Zipher resources from the workspace.", parents=[parent_parser])
|
|
110
117
|
|
|
111
118
|
args = parser.parse_args()
|
|
@@ -127,7 +134,8 @@ def main():
|
|
|
127
134
|
max_runs=args.max_runs,
|
|
128
135
|
jobs_list=args.jobs_list,
|
|
129
136
|
jobs_permission=JobPermissions.CAN_VIEW if args.readonly else JobPermissions.CAN_MANAGE,
|
|
130
|
-
service_principal_token_type=ServicePrincipalTokenType.PERSONAL_ACCESS_TOKEN if args.pat else ServicePrincipalTokenType.OAUTH
|
|
137
|
+
service_principal_token_type=ServicePrincipalTokenType.PERSONAL_ACCESS_TOKEN if args.pat else ServicePrincipalTokenType.OAUTH,
|
|
138
|
+
existing_service_principal_id=args.existing_sp_id
|
|
131
139
|
)
|
|
132
140
|
|
|
133
141
|
zipher_token = zipher_acl.setup(user_config=config, skip_approval=args.skip_approval)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|