zenml-nightly 0.75.0.dev20250311__py3-none-any.whl → 0.75.0.dev20250313__py3-none-any.whl
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.
- zenml/VERSION +1 -1
- zenml/__init__.py +2 -0
- zenml/analytics/context.py +7 -0
- zenml/artifacts/utils.py +0 -2
- zenml/cli/login.py +6 -0
- zenml/cli/model.py +7 -15
- zenml/cli/secret.py +47 -44
- zenml/cli/service_connectors.py +0 -1
- zenml/cli/stack.py +0 -1
- zenml/cli/tag.py +3 -5
- zenml/cli/utils.py +25 -23
- zenml/cli/workspace.py +79 -5
- zenml/client.py +618 -348
- zenml/config/global_config.py +16 -3
- zenml/config/pipeline_configurations.py +3 -2
- zenml/config/pipeline_run_configuration.py +2 -1
- zenml/config/secret_reference_mixin.py +1 -1
- zenml/constants.py +1 -3
- zenml/enums.py +0 -7
- zenml/event_hub/event_hub.py +3 -1
- zenml/exceptions.py +0 -24
- zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +5 -3
- zenml/integrations/bitbucket/plugins/event_sources/bitbucket_webhook_event_source.py +1 -4
- zenml/integrations/github/plugins/event_sources/github_webhook_event_source.py +1 -4
- zenml/integrations/mlflow/steps/mlflow_registry.py +1 -1
- zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -1
- zenml/integrations/wandb/flavors/wandb_experiment_tracker_flavor.py +3 -3
- zenml/model/model.py +8 -8
- zenml/models/__init__.py +18 -1
- zenml/models/v2/base/base.py +0 -5
- zenml/models/v2/base/filter.py +1 -1
- zenml/models/v2/base/scoped.py +104 -121
- zenml/models/v2/core/api_key.py +1 -1
- zenml/models/v2/core/artifact.py +31 -18
- zenml/models/v2/core/artifact_version.py +42 -25
- zenml/models/v2/core/component.py +22 -33
- zenml/models/v2/core/device.py +3 -2
- zenml/models/v2/core/event_source.py +2 -2
- zenml/models/v2/core/flavor.py +19 -47
- zenml/models/v2/core/logs.py +1 -2
- zenml/models/v2/core/model.py +7 -4
- zenml/models/v2/core/model_version.py +36 -27
- zenml/models/v2/core/pipeline.py +1 -1
- zenml/models/v2/core/pipeline_build.py +18 -0
- zenml/models/v2/core/pipeline_run.py +5 -13
- zenml/models/v2/core/run_template.py +1 -2
- zenml/models/v2/core/schedule.py +0 -9
- zenml/models/v2/core/secret.py +93 -127
- zenml/models/v2/core/server_settings.py +2 -2
- zenml/models/v2/core/service.py +43 -12
- zenml/models/v2/core/service_connector.py +14 -16
- zenml/models/v2/core/stack.py +24 -26
- zenml/models/v2/core/step_run.py +3 -15
- zenml/models/v2/core/tag.py +41 -15
- zenml/models/v2/core/user.py +19 -2
- zenml/models/v2/misc/statistics.py +45 -0
- zenml/models/v2/misc/tag.py +27 -0
- zenml/orchestrators/cache_utils.py +1 -1
- zenml/orchestrators/input_utils.py +1 -0
- zenml/orchestrators/step_launcher.py +0 -1
- zenml/orchestrators/step_run_utils.py +0 -2
- zenml/orchestrators/step_runner.py +10 -1
- zenml/pipelines/build_utils.py +4 -2
- zenml/pipelines/pipeline_decorator.py +3 -2
- zenml/pipelines/pipeline_definition.py +4 -5
- zenml/pipelines/run_utils.py +3 -3
- zenml/service_connectors/service_connector.py +0 -7
- zenml/service_connectors/service_connector_utils.py +0 -1
- zenml/stack/authentication_mixin.py +1 -1
- zenml/stack/flavor.py +3 -14
- zenml/stack/stack_component.py +1 -5
- zenml/steps/step_context.py +19 -0
- zenml/utils/string_utils.py +1 -1
- zenml/utils/tag_utils.py +642 -0
- zenml/zen_server/cloud_utils.py +21 -0
- zenml/zen_server/exceptions.py +0 -6
- zenml/zen_server/rbac/endpoint_utils.py +134 -46
- zenml/zen_server/rbac/models.py +65 -3
- zenml/zen_server/rbac/rbac_interface.py +9 -0
- zenml/zen_server/rbac/rbac_sql_zen_store.py +15 -7
- zenml/zen_server/rbac/utils.py +156 -29
- zenml/zen_server/rbac/zenml_cloud_rbac.py +43 -11
- zenml/zen_server/routers/actions_endpoints.py +3 -5
- zenml/zen_server/routers/artifact_endpoint.py +0 -5
- zenml/zen_server/routers/artifact_version_endpoints.py +15 -9
- zenml/zen_server/routers/auth_endpoints.py +22 -7
- zenml/zen_server/routers/code_repositories_endpoints.py +56 -3
- zenml/zen_server/routers/devices_endpoints.py +0 -4
- zenml/zen_server/routers/event_source_endpoints.py +0 -5
- zenml/zen_server/routers/flavors_endpoints.py +0 -5
- zenml/zen_server/routers/logs_endpoints.py +0 -1
- zenml/zen_server/routers/model_versions_endpoints.py +102 -23
- zenml/zen_server/routers/models_endpoints.py +51 -68
- zenml/zen_server/routers/pipeline_builds_endpoints.py +58 -4
- zenml/zen_server/routers/pipeline_deployments_endpoints.py +58 -4
- zenml/zen_server/routers/pipelines_endpoints.py +73 -4
- zenml/zen_server/routers/plugin_endpoints.py +0 -1
- zenml/zen_server/routers/run_metadata_endpoints.py +99 -0
- zenml/zen_server/routers/run_templates_endpoints.py +66 -3
- zenml/zen_server/routers/runs_endpoints.py +60 -8
- zenml/zen_server/routers/schedule_endpoints.py +69 -6
- zenml/zen_server/routers/secrets_endpoints.py +40 -4
- zenml/zen_server/routers/server_endpoints.py +53 -1
- zenml/zen_server/routers/service_accounts_endpoints.py +14 -15
- zenml/zen_server/routers/service_connectors_endpoints.py +96 -14
- zenml/zen_server/routers/service_endpoints.py +20 -7
- zenml/zen_server/routers/stack_components_endpoints.py +68 -7
- zenml/zen_server/routers/stacks_endpoints.py +98 -7
- zenml/zen_server/routers/steps_endpoints.py +17 -11
- zenml/zen_server/routers/tag_resource_endpoints.py +115 -0
- zenml/zen_server/routers/tags_endpoints.py +6 -17
- zenml/zen_server/routers/triggers_endpoints.py +5 -8
- zenml/zen_server/routers/users_endpoints.py +47 -12
- zenml/zen_server/routers/workspaces_endpoints.py +56 -1285
- zenml/zen_server/template_execution/utils.py +5 -4
- zenml/zen_server/utils.py +21 -0
- zenml/zen_server/zen_server_api.py +4 -0
- zenml/zen_stores/base_zen_store.py +29 -44
- zenml/zen_stores/migrations/versions/0392807467dc_add_build_duration.py +34 -0
- zenml/zen_stores/migrations/versions/1cb6477f72d6_move_artifact_save_type.py +20 -10
- zenml/zen_stores/migrations/versions/1f9d1cd00b90_add_unique_name_constraints.py +231 -0
- zenml/zen_stores/migrations/versions/288f4fb6e112_make_tags_user_scoped.py +74 -0
- zenml/zen_stores/migrations/versions/2e695a26fe7a_add_user_default_workspace.py +45 -0
- zenml/zen_stores/migrations/versions/3b1776345020_remove_workspace_from_globals.py +81 -0
- zenml/zen_stores/migrations/versions/41b28cae31ce_make_artifacts_workspace_scoped.py +136 -0
- zenml/zen_stores/migrations/versions/9e7bf0970266_adding_exclusive_attribute_to_tags.py +47 -0
- zenml/zen_stores/migrations/versions/b557b2871693_update_step_run_input_types.py +8 -4
- zenml/zen_stores/migrations/versions/cc269488e5a9_separate_run_metadata.py +12 -6
- zenml/zen_stores/migrations/versions/f1d723fd723b_add_secret_private_attr.py +61 -0
- zenml/zen_stores/migrations/versions/f76a368a25a5_add_stack_description.py +35 -0
- zenml/zen_stores/rest_zen_store.py +172 -171
- zenml/zen_stores/schemas/action_schemas.py +8 -1
- zenml/zen_stores/schemas/api_key_schemas.py +8 -1
- zenml/zen_stores/schemas/artifact_schemas.py +28 -1
- zenml/zen_stores/schemas/code_repository_schemas.py +8 -1
- zenml/zen_stores/schemas/component_schemas.py +9 -14
- zenml/zen_stores/schemas/event_source_schemas.py +8 -1
- zenml/zen_stores/schemas/flavor_schemas.py +14 -20
- zenml/zen_stores/schemas/model_schemas.py +3 -0
- zenml/zen_stores/schemas/pipeline_build_schemas.py +4 -0
- zenml/zen_stores/schemas/pipeline_deployment_schemas.py +3 -1
- zenml/zen_stores/schemas/pipeline_run_schemas.py +0 -3
- zenml/zen_stores/schemas/run_template_schemas.py +8 -4
- zenml/zen_stores/schemas/schedule_schema.py +9 -14
- zenml/zen_stores/schemas/secret_schemas.py +15 -25
- zenml/zen_stores/schemas/service_connector_schemas.py +8 -17
- zenml/zen_stores/schemas/service_schemas.py +0 -1
- zenml/zen_stores/schemas/stack_schemas.py +12 -15
- zenml/zen_stores/schemas/step_run_schemas.py +7 -8
- zenml/zen_stores/schemas/tag_schemas.py +30 -2
- zenml/zen_stores/schemas/trigger_schemas.py +8 -1
- zenml/zen_stores/schemas/user_schemas.py +24 -2
- zenml/zen_stores/schemas/utils.py +16 -0
- zenml/zen_stores/schemas/workspace_schemas.py +7 -25
- zenml/zen_stores/secrets_stores/service_connector_secrets_store.py +0 -3
- zenml/zen_stores/sql_zen_store.py +2905 -2280
- zenml/zen_stores/template_utils.py +1 -1
- zenml/zen_stores/zen_store_interface.py +82 -58
- {zenml_nightly-0.75.0.dev20250311.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/METADATA +1 -1
- {zenml_nightly-0.75.0.dev20250311.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/RECORD +163 -149
- {zenml_nightly-0.75.0.dev20250311.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.75.0.dev20250311.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.75.0.dev20250311.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/entry_points.txt +0 -0
zenml/zen_server/rbac/utils.py
CHANGED
@@ -34,6 +34,8 @@ from zenml.models import (
|
|
34
34
|
Page,
|
35
35
|
UserResponse,
|
36
36
|
UserScopedResponse,
|
37
|
+
WorkspaceScopedRequest,
|
38
|
+
WorkspaceScopedResponse,
|
37
39
|
)
|
38
40
|
from zenml.zen_server.auth import get_auth_context
|
39
41
|
from zenml.zen_server.rbac.models import Action, Resource, ResourceType
|
@@ -55,24 +57,39 @@ def dehydrate_page(page: Page[AnyResponse]) -> Page[AnyResponse]:
|
|
55
57
|
Returns:
|
56
58
|
The page with (potentially) dehydrated items.
|
57
59
|
"""
|
60
|
+
new_items = dehydrate_response_model_batch(page.items)
|
61
|
+
return page.model_copy(update={"items": new_items})
|
62
|
+
|
63
|
+
|
64
|
+
def dehydrate_response_model_batch(
|
65
|
+
batch: List[AnyResponse],
|
66
|
+
) -> List[AnyResponse]:
|
67
|
+
"""Dehydrate all items of a batch.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
batch: The batch to dehydrate.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
The batch with (potentially) dehydrated items.
|
74
|
+
"""
|
58
75
|
if not server_config().rbac_enabled:
|
59
|
-
return
|
76
|
+
return batch
|
60
77
|
|
61
78
|
auth_context = get_auth_context()
|
62
79
|
assert auth_context
|
63
80
|
|
64
|
-
resource_list = [get_subresources_for_model(item) for item in
|
81
|
+
resource_list = [get_subresources_for_model(item) for item in batch]
|
65
82
|
resources = set.union(*resource_list) if resource_list else set()
|
66
83
|
permissions = rbac().check_permissions(
|
67
84
|
user=auth_context.user, resources=resources, action=Action.READ
|
68
85
|
)
|
69
86
|
|
70
|
-
|
87
|
+
new_batch = [
|
71
88
|
dehydrate_response_model(item, permissions=permissions)
|
72
|
-
for item in
|
89
|
+
for item in batch
|
73
90
|
]
|
74
91
|
|
75
|
-
return
|
92
|
+
return new_batch
|
76
93
|
|
77
94
|
|
78
95
|
def dehydrate_response_model(
|
@@ -161,7 +178,7 @@ def _dehydrate_value(
|
|
161
178
|
return value
|
162
179
|
|
163
180
|
|
164
|
-
def has_permissions_for_model(model:
|
181
|
+
def has_permissions_for_model(model: AnyModel, action: Action) -> bool:
|
165
182
|
"""If the active user has permissions to perform the action on the model.
|
166
183
|
|
167
184
|
Args:
|
@@ -201,7 +218,7 @@ def get_permission_denied_model(model: AnyResponse) -> AnyResponse:
|
|
201
218
|
|
202
219
|
|
203
220
|
def batch_verify_permissions_for_models(
|
204
|
-
models: Sequence[
|
221
|
+
models: Sequence[AnyModel],
|
205
222
|
action: Action,
|
206
223
|
) -> None:
|
207
224
|
"""Batch permission verification for models.
|
@@ -229,7 +246,7 @@ def batch_verify_permissions_for_models(
|
|
229
246
|
batch_verify_permissions(resources=resources, action=action)
|
230
247
|
|
231
248
|
|
232
|
-
def verify_permission_for_model(model:
|
249
|
+
def verify_permission_for_model(model: AnyModel, action: Action) -> None:
|
233
250
|
"""Verifies if a user has permission to perform an action on a model.
|
234
251
|
|
235
252
|
Args:
|
@@ -283,6 +300,7 @@ def verify_permission(
|
|
283
300
|
resource_type: str,
|
284
301
|
action: Action,
|
285
302
|
resource_id: Optional[UUID] = None,
|
303
|
+
workspace_id: Optional[UUID] = None,
|
286
304
|
) -> None:
|
287
305
|
"""Verifies if a user has permission to perform an action on a resource.
|
288
306
|
|
@@ -291,20 +309,27 @@ def verify_permission(
|
|
291
309
|
action on.
|
292
310
|
action: The action the user wants to perform.
|
293
311
|
resource_id: ID of the resource the user wants to perform the action on.
|
312
|
+
workspace_id: ID of the workspace the user wants to perform the action
|
313
|
+
on. Only used for workspace scoped resources.
|
294
314
|
"""
|
295
|
-
resource = Resource(
|
315
|
+
resource = Resource(
|
316
|
+
type=resource_type, id=resource_id, workspace_id=workspace_id
|
317
|
+
)
|
296
318
|
batch_verify_permissions(resources={resource}, action=action)
|
297
319
|
|
298
320
|
|
299
321
|
def get_allowed_resource_ids(
|
300
322
|
resource_type: str,
|
301
323
|
action: Action = Action.READ,
|
324
|
+
workspace_id: Optional[UUID] = None,
|
302
325
|
) -> Optional[Set[UUID]]:
|
303
326
|
"""Get all resource IDs of a resource type that a user can access.
|
304
327
|
|
305
328
|
Args:
|
306
329
|
resource_type: The resource type.
|
307
330
|
action: The action the user wants to perform on the resource.
|
331
|
+
workspace_id: Optional workspace ID to filter the resources by.
|
332
|
+
Required for workspace scoped resources.
|
308
333
|
|
309
334
|
Returns:
|
310
335
|
A list of resource IDs or `None` if the user has full access to the
|
@@ -321,7 +346,7 @@ def get_allowed_resource_ids(
|
|
321
346
|
allowed_ids,
|
322
347
|
) = rbac().list_allowed_resource_ids(
|
323
348
|
user=auth_context.user,
|
324
|
-
resource=Resource(type=resource_type),
|
349
|
+
resource=Resource(type=resource_type, workspace_id=workspace_id),
|
325
350
|
action=action,
|
326
351
|
)
|
327
352
|
|
@@ -331,7 +356,7 @@ def get_allowed_resource_ids(
|
|
331
356
|
return {UUID(id) for id in allowed_ids}
|
332
357
|
|
333
358
|
|
334
|
-
def get_resource_for_model(model:
|
359
|
+
def get_resource_for_model(model: AnyModel) -> Optional[Resource]:
|
335
360
|
"""Get the resource associated with a model object.
|
336
361
|
|
337
362
|
Args:
|
@@ -346,12 +371,26 @@ def get_resource_for_model(model: AnyResponse) -> Optional[Resource]:
|
|
346
371
|
# This model is not tied to any RBAC resource type
|
347
372
|
return None
|
348
373
|
|
349
|
-
|
374
|
+
workspace_id: Optional[UUID] = None
|
375
|
+
if isinstance(model, WorkspaceScopedResponse):
|
376
|
+
# A workspace scoped response is always scoped to a specific workspace
|
377
|
+
workspace_id = model.workspace.id
|
378
|
+
elif isinstance(model, WorkspaceScopedRequest):
|
379
|
+
# A workspace scoped request is always scoped to a specific workspace
|
380
|
+
workspace_id = model.workspace
|
381
|
+
|
382
|
+
resource_id: Optional[UUID] = None
|
383
|
+
if isinstance(model, BaseIdentifiedResponse):
|
384
|
+
resource_id = model.id
|
385
|
+
|
386
|
+
return Resource(
|
387
|
+
type=resource_type, id=resource_id, workspace_id=workspace_id
|
388
|
+
)
|
350
389
|
|
351
390
|
|
352
391
|
def get_surrogate_permission_model_for_model(
|
353
|
-
model:
|
354
|
-
) ->
|
392
|
+
model: BaseModel, action: str
|
393
|
+
) -> BaseModel:
|
355
394
|
"""Get a surrogate permission model for a model.
|
356
395
|
|
357
396
|
In some cases a different model instead of the original model is used to
|
@@ -379,7 +418,7 @@ def get_surrogate_permission_model_for_model(
|
|
379
418
|
|
380
419
|
|
381
420
|
def get_resource_type_for_model(
|
382
|
-
model:
|
421
|
+
model: AnyModel,
|
383
422
|
) -> Optional[ResourceType]:
|
384
423
|
"""Get the resource type associated with a model object.
|
385
424
|
|
@@ -391,64 +430,113 @@ def get_resource_type_for_model(
|
|
391
430
|
is not associated with any resource type.
|
392
431
|
"""
|
393
432
|
from zenml.models import (
|
433
|
+
ActionRequest,
|
394
434
|
ActionResponse,
|
435
|
+
ArtifactRequest,
|
395
436
|
ArtifactResponse,
|
437
|
+
ArtifactVersionRequest,
|
396
438
|
ArtifactVersionResponse,
|
439
|
+
CodeRepositoryRequest,
|
397
440
|
CodeRepositoryResponse,
|
441
|
+
ComponentRequest,
|
398
442
|
ComponentResponse,
|
443
|
+
EventSourceRequest,
|
399
444
|
EventSourceResponse,
|
445
|
+
FlavorRequest,
|
400
446
|
FlavorResponse,
|
447
|
+
ModelRequest,
|
401
448
|
ModelResponse,
|
449
|
+
ModelVersionRequest,
|
402
450
|
ModelVersionResponse,
|
451
|
+
PipelineBuildRequest,
|
403
452
|
PipelineBuildResponse,
|
453
|
+
PipelineDeploymentRequest,
|
404
454
|
PipelineDeploymentResponse,
|
455
|
+
PipelineRequest,
|
405
456
|
PipelineResponse,
|
457
|
+
PipelineRunRequest,
|
406
458
|
PipelineRunResponse,
|
459
|
+
RunMetadataRequest,
|
460
|
+
RunTemplateRequest,
|
407
461
|
RunTemplateResponse,
|
462
|
+
SecretRequest,
|
408
463
|
SecretResponse,
|
464
|
+
ServiceAccountRequest,
|
409
465
|
ServiceAccountResponse,
|
466
|
+
ServiceConnectorRequest,
|
410
467
|
ServiceConnectorResponse,
|
468
|
+
ServiceRequest,
|
411
469
|
ServiceResponse,
|
470
|
+
StackRequest,
|
412
471
|
StackResponse,
|
472
|
+
TagRequest,
|
413
473
|
TagResponse,
|
474
|
+
TriggerExecutionRequest,
|
414
475
|
TriggerExecutionResponse,
|
476
|
+
TriggerRequest,
|
415
477
|
TriggerResponse,
|
478
|
+
WorkspaceRequest,
|
479
|
+
WorkspaceResponse,
|
416
480
|
)
|
417
481
|
|
418
482
|
mapping: Dict[
|
419
483
|
Any,
|
420
484
|
ResourceType,
|
421
485
|
] = {
|
486
|
+
ActionRequest: ResourceType.ACTION,
|
422
487
|
ActionResponse: ResourceType.ACTION,
|
488
|
+
ArtifactRequest: ResourceType.ARTIFACT,
|
489
|
+
ArtifactResponse: ResourceType.ARTIFACT,
|
490
|
+
ArtifactVersionRequest: ResourceType.ARTIFACT_VERSION,
|
491
|
+
ArtifactVersionResponse: ResourceType.ARTIFACT_VERSION,
|
492
|
+
CodeRepositoryRequest: ResourceType.CODE_REPOSITORY,
|
493
|
+
CodeRepositoryResponse: ResourceType.CODE_REPOSITORY,
|
494
|
+
ComponentRequest: ResourceType.STACK_COMPONENT,
|
495
|
+
ComponentResponse: ResourceType.STACK_COMPONENT,
|
496
|
+
EventSourceRequest: ResourceType.EVENT_SOURCE,
|
423
497
|
EventSourceResponse: ResourceType.EVENT_SOURCE,
|
498
|
+
FlavorRequest: ResourceType.FLAVOR,
|
424
499
|
FlavorResponse: ResourceType.FLAVOR,
|
425
|
-
|
426
|
-
ComponentResponse: ResourceType.STACK_COMPONENT,
|
427
|
-
StackResponse: ResourceType.STACK,
|
428
|
-
PipelineResponse: ResourceType.PIPELINE,
|
429
|
-
CodeRepositoryResponse: ResourceType.CODE_REPOSITORY,
|
430
|
-
SecretResponse: ResourceType.SECRET,
|
500
|
+
ModelRequest: ResourceType.MODEL,
|
431
501
|
ModelResponse: ResourceType.MODEL,
|
502
|
+
ModelVersionRequest: ResourceType.MODEL_VERSION,
|
432
503
|
ModelVersionResponse: ResourceType.MODEL_VERSION,
|
433
|
-
|
434
|
-
ArtifactVersionResponse: ResourceType.ARTIFACT_VERSION,
|
435
|
-
# WorkspaceResponse: ResourceType.WORKSPACE,
|
436
|
-
# UserResponse: ResourceType.USER,
|
437
|
-
PipelineDeploymentResponse: ResourceType.PIPELINE_DEPLOYMENT,
|
504
|
+
PipelineBuildRequest: ResourceType.PIPELINE_BUILD,
|
438
505
|
PipelineBuildResponse: ResourceType.PIPELINE_BUILD,
|
506
|
+
PipelineDeploymentRequest: ResourceType.PIPELINE_DEPLOYMENT,
|
507
|
+
PipelineDeploymentResponse: ResourceType.PIPELINE_DEPLOYMENT,
|
508
|
+
PipelineRequest: ResourceType.PIPELINE,
|
509
|
+
PipelineResponse: ResourceType.PIPELINE,
|
510
|
+
PipelineRunRequest: ResourceType.PIPELINE_RUN,
|
439
511
|
PipelineRunResponse: ResourceType.PIPELINE_RUN,
|
512
|
+
RunMetadataRequest: ResourceType.RUN_METADATA,
|
513
|
+
RunTemplateRequest: ResourceType.RUN_TEMPLATE,
|
440
514
|
RunTemplateResponse: ResourceType.RUN_TEMPLATE,
|
515
|
+
SecretRequest: ResourceType.SECRET,
|
516
|
+
SecretResponse: ResourceType.SECRET,
|
517
|
+
ServiceAccountRequest: ResourceType.SERVICE_ACCOUNT,
|
518
|
+
ServiceAccountResponse: ResourceType.SERVICE_ACCOUNT,
|
519
|
+
ServiceConnectorRequest: ResourceType.SERVICE_CONNECTOR,
|
520
|
+
ServiceConnectorResponse: ResourceType.SERVICE_CONNECTOR,
|
521
|
+
ServiceRequest: ResourceType.SERVICE,
|
522
|
+
ServiceResponse: ResourceType.SERVICE,
|
523
|
+
StackRequest: ResourceType.STACK,
|
524
|
+
StackResponse: ResourceType.STACK,
|
525
|
+
TagRequest: ResourceType.TAG,
|
441
526
|
TagResponse: ResourceType.TAG,
|
527
|
+
TriggerRequest: ResourceType.TRIGGER,
|
442
528
|
TriggerResponse: ResourceType.TRIGGER,
|
529
|
+
TriggerExecutionRequest: ResourceType.TRIGGER_EXECUTION,
|
443
530
|
TriggerExecutionResponse: ResourceType.TRIGGER_EXECUTION,
|
444
|
-
|
445
|
-
|
531
|
+
WorkspaceResponse: ResourceType.WORKSPACE,
|
532
|
+
WorkspaceRequest: ResourceType.WORKSPACE,
|
533
|
+
# UserResponse: ResourceType.USER,
|
446
534
|
}
|
447
535
|
|
448
536
|
return mapping.get(type(model))
|
449
537
|
|
450
538
|
|
451
|
-
def is_owned_by_authenticated_user(model:
|
539
|
+
def is_owned_by_authenticated_user(model: AnyModel) -> bool:
|
452
540
|
"""Returns whether the currently authenticated user owns the model.
|
453
541
|
|
454
542
|
Args:
|
@@ -618,3 +706,42 @@ def update_resource_membership(
|
|
618
706
|
rbac().update_resource_membership(
|
619
707
|
user=user, resource=resource, actions=actions
|
620
708
|
)
|
709
|
+
|
710
|
+
|
711
|
+
def delete_model_resource(model: AnyModel) -> None:
|
712
|
+
"""Delete resource membership information for a model.
|
713
|
+
|
714
|
+
Args:
|
715
|
+
model: The model for which to delete the resource membership information.
|
716
|
+
"""
|
717
|
+
delete_model_resources(models=[model])
|
718
|
+
|
719
|
+
|
720
|
+
def delete_model_resources(models: List[AnyModel]) -> None:
|
721
|
+
"""Delete resource membership information for a list of models.
|
722
|
+
|
723
|
+
Args:
|
724
|
+
models: The models for which to delete the resource membership information.
|
725
|
+
"""
|
726
|
+
if not server_config().rbac_enabled:
|
727
|
+
return
|
728
|
+
|
729
|
+
resources = set()
|
730
|
+
for model in models:
|
731
|
+
if resource := get_resource_for_model(model):
|
732
|
+
resources.add(resource)
|
733
|
+
|
734
|
+
delete_resources(resources=list(resources))
|
735
|
+
|
736
|
+
|
737
|
+
def delete_resources(resources: List[Resource]) -> None:
|
738
|
+
"""Delete resource membership information for a list of resources.
|
739
|
+
|
740
|
+
Args:
|
741
|
+
resources: The resources for which to delete the resource membership
|
742
|
+
information.
|
743
|
+
"""
|
744
|
+
if not server_config().rbac_enabled:
|
745
|
+
return
|
746
|
+
|
747
|
+
rbac().delete_resources(resources=resources)
|
@@ -13,10 +13,10 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Cloud RBAC implementation."""
|
15
15
|
|
16
|
-
from typing import TYPE_CHECKING, Dict, List, Set, Tuple
|
16
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
17
17
|
|
18
18
|
from zenml.zen_server.cloud_utils import cloud_connection
|
19
|
-
from zenml.zen_server.rbac.models import Action, Resource
|
19
|
+
from zenml.zen_server.rbac.models import Action, Resource, ResourceType
|
20
20
|
from zenml.zen_server.rbac.rbac_interface import RBACInterface
|
21
21
|
from zenml.zen_server.utils import server_config
|
22
22
|
|
@@ -27,6 +27,7 @@ if TYPE_CHECKING:
|
|
27
27
|
PERMISSIONS_ENDPOINT = "/rbac/check_permissions"
|
28
28
|
ALLOWED_RESOURCE_IDS_ENDPOINT = "/rbac/allowed_resource_ids"
|
29
29
|
RESOURCE_MEMBERSHIP_ENDPOINT = "/rbac/resource_members"
|
30
|
+
RESOURCES_ENDPOINT = "/rbac/resources"
|
30
31
|
|
31
32
|
SERVER_SCOPE_IDENTIFIER = "server"
|
32
33
|
|
@@ -42,12 +43,7 @@ def _convert_to_cloud_resource(resource: Resource) -> str:
|
|
42
43
|
Returns:
|
43
44
|
The converted resource.
|
44
45
|
"""
|
45
|
-
|
46
|
-
|
47
|
-
if resource.id:
|
48
|
-
resource_string += f"/{resource.id}"
|
49
|
-
|
50
|
-
return resource_string
|
46
|
+
return f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}:{resource}"
|
51
47
|
|
52
48
|
|
53
49
|
def _convert_from_cloud_resource(cloud_resource: str) -> Resource:
|
@@ -62,16 +58,38 @@ def _convert_from_cloud_resource(cloud_resource: str) -> Resource:
|
|
62
58
|
Returns:
|
63
59
|
The converted resource.
|
64
60
|
"""
|
65
|
-
scope,
|
61
|
+
scope, workspace_resource_type_and_id = cloud_resource.split(
|
62
|
+
":", maxsplit=1
|
63
|
+
)
|
66
64
|
|
67
65
|
if scope != f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}":
|
68
66
|
raise ValueError("Invalid scope for server resource.")
|
69
67
|
|
68
|
+
workspace_id: Optional[str] = None
|
69
|
+
if ":" in workspace_resource_type_and_id:
|
70
|
+
(
|
71
|
+
workspace_id,
|
72
|
+
resource_type_and_id,
|
73
|
+
) = workspace_resource_type_and_id.split(":", maxsplit=1)
|
74
|
+
else:
|
75
|
+
workspace_id = None
|
76
|
+
resource_type_and_id = workspace_resource_type_and_id
|
77
|
+
|
78
|
+
resource_id: Optional[str] = None
|
70
79
|
if "/" in resource_type_and_id:
|
71
80
|
resource_type, resource_id = resource_type_and_id.split("/")
|
72
|
-
return Resource(type=resource_type, id=resource_id)
|
73
81
|
else:
|
74
|
-
|
82
|
+
resource_type = resource_type_and_id
|
83
|
+
|
84
|
+
if resource_type == ResourceType.WORKSPACE and workspace_id is not None:
|
85
|
+
# TODO: For now, we duplicate the workspace ID in the string
|
86
|
+
# representation when describing a workspace instance, because
|
87
|
+
# this is what is expected by the RBAC implementation.
|
88
|
+
workspace_id = None
|
89
|
+
|
90
|
+
return Resource(
|
91
|
+
type=resource_type, id=resource_id, workspace_id=workspace_id
|
92
|
+
)
|
75
93
|
|
76
94
|
|
77
95
|
class ZenMLCloudRBAC(RBACInterface):
|
@@ -184,3 +202,17 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
184
202
|
"actions": [str(action) for action in actions],
|
185
203
|
}
|
186
204
|
self._connection.post(endpoint=RESOURCE_MEMBERSHIP_ENDPOINT, data=data)
|
205
|
+
|
206
|
+
def delete_resources(self, resources: List[Resource]) -> None:
|
207
|
+
"""Delete resource membership information for a list of resources.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
resources: The resources for which to delete the resource membership
|
211
|
+
information.
|
212
|
+
"""
|
213
|
+
params = {
|
214
|
+
"resources": [
|
215
|
+
_convert_to_cloud_resource(resource) for resource in resources
|
216
|
+
],
|
217
|
+
}
|
218
|
+
self._connection.delete(endpoint=RESOURCES_ENDPOINT, params=params)
|
@@ -40,6 +40,7 @@ from zenml.zen_server.rbac.endpoint_utils import (
|
|
40
40
|
from zenml.zen_server.rbac.models import Action, ResourceType
|
41
41
|
from zenml.zen_server.rbac.utils import (
|
42
42
|
dehydrate_response_model,
|
43
|
+
delete_model_resource,
|
43
44
|
verify_permission_for_model,
|
44
45
|
)
|
45
46
|
from zenml.zen_server.utils import (
|
@@ -58,7 +59,6 @@ router = APIRouter(
|
|
58
59
|
|
59
60
|
@router.get(
|
60
61
|
"",
|
61
|
-
response_model=Page[ActionResponse],
|
62
62
|
responses={401: error_response, 404: error_response, 422: error_response},
|
63
63
|
)
|
64
64
|
@handle_exceptions
|
@@ -130,7 +130,6 @@ def list_actions(
|
|
130
130
|
|
131
131
|
@router.get(
|
132
132
|
"/{action_id}",
|
133
|
-
response_model=ActionResponse,
|
134
133
|
responses={401: error_response, 404: error_response, 422: error_response},
|
135
134
|
)
|
136
135
|
@handle_exceptions
|
@@ -178,7 +177,6 @@ def get_action(
|
|
178
177
|
|
179
178
|
@router.post(
|
180
179
|
"",
|
181
|
-
response_model=ActionResponse,
|
182
180
|
responses={401: error_response, 409: error_response, 422: error_response},
|
183
181
|
)
|
184
182
|
@handle_exceptions
|
@@ -219,14 +217,12 @@ def create_action(
|
|
219
217
|
|
220
218
|
return verify_permissions_and_create_entity(
|
221
219
|
request_model=action,
|
222
|
-
resource_type=ResourceType.ACTION,
|
223
220
|
create_method=action_handler.create_action,
|
224
221
|
)
|
225
222
|
|
226
223
|
|
227
224
|
@router.put(
|
228
225
|
"/{action_id}",
|
229
|
-
response_model=ActionResponse,
|
230
226
|
responses={401: error_response, 404: error_response, 422: error_response},
|
231
227
|
)
|
232
228
|
@handle_exceptions
|
@@ -322,3 +318,5 @@ def delete_action(
|
|
322
318
|
action=action,
|
323
319
|
force=force,
|
324
320
|
)
|
321
|
+
|
322
|
+
delete_model_resource(action)
|
@@ -50,7 +50,6 @@ artifact_router = APIRouter(
|
|
50
50
|
|
51
51
|
@artifact_router.get(
|
52
52
|
"",
|
53
|
-
response_model=Page[ArtifactResponse],
|
54
53
|
responses={401: error_response, 404: error_response, 422: error_response},
|
55
54
|
)
|
56
55
|
@handle_exceptions
|
@@ -82,7 +81,6 @@ def list_artifacts(
|
|
82
81
|
|
83
82
|
@artifact_router.post(
|
84
83
|
"",
|
85
|
-
response_model=ArtifactResponse,
|
86
84
|
responses={401: error_response, 409: error_response, 422: error_response},
|
87
85
|
)
|
88
86
|
@handle_exceptions
|
@@ -100,14 +98,12 @@ def create_artifact(
|
|
100
98
|
"""
|
101
99
|
return verify_permissions_and_create_entity(
|
102
100
|
request_model=artifact,
|
103
|
-
resource_type=ResourceType.ARTIFACT,
|
104
101
|
create_method=zen_store().create_artifact,
|
105
102
|
)
|
106
103
|
|
107
104
|
|
108
105
|
@artifact_router.get(
|
109
106
|
"/{artifact_id}",
|
110
|
-
response_model=ArtifactResponse,
|
111
107
|
responses={401: error_response, 404: error_response, 422: error_response},
|
112
108
|
)
|
113
109
|
@handle_exceptions
|
@@ -135,7 +131,6 @@ def get_artifact(
|
|
135
131
|
|
136
132
|
@artifact_router.put(
|
137
133
|
"/{artifact_id}",
|
138
|
-
response_model=ArtifactResponse,
|
139
134
|
responses={401: error_response, 404: error_response, 422: error_response},
|
140
135
|
)
|
141
136
|
@handle_exceptions
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Endpoint definitions for artifact versions."""
|
15
15
|
|
16
|
-
from typing import List
|
16
|
+
from typing import List, Union
|
17
17
|
from uuid import UUID
|
18
18
|
|
19
19
|
from fastapi import APIRouter, Depends, Security
|
@@ -46,6 +46,7 @@ from zenml.zen_server.rbac.utils import (
|
|
46
46
|
from zenml.zen_server.utils import (
|
47
47
|
handle_exceptions,
|
48
48
|
make_dependable,
|
49
|
+
set_filter_workspace_scope,
|
49
50
|
zen_store,
|
50
51
|
)
|
51
52
|
|
@@ -58,7 +59,6 @@ artifact_version_router = APIRouter(
|
|
58
59
|
|
59
60
|
@artifact_version_router.get(
|
60
61
|
"",
|
61
|
-
response_model=Page[ArtifactVersionResponse],
|
62
62
|
responses={401: error_response, 404: error_response, 422: error_response},
|
63
63
|
)
|
64
64
|
@handle_exceptions
|
@@ -81,8 +81,14 @@ def list_artifact_versions(
|
|
81
81
|
Returns:
|
82
82
|
The artifact versions according to query filters.
|
83
83
|
"""
|
84
|
+
# A workspace scoped request must always be scoped to a specific
|
85
|
+
# workspace. This is required for the RBAC check to work.
|
86
|
+
set_filter_workspace_scope(artifact_version_filter_model)
|
87
|
+
assert isinstance(artifact_version_filter_model.workspace, UUID)
|
88
|
+
|
84
89
|
allowed_artifact_ids = get_allowed_resource_ids(
|
85
|
-
resource_type=ResourceType.ARTIFACT
|
90
|
+
resource_type=ResourceType.ARTIFACT,
|
91
|
+
workspace_id=artifact_version_filter_model.workspace,
|
86
92
|
)
|
87
93
|
artifact_version_filter_model.configure_rbac(
|
88
94
|
authenticated_user_id=auth_context.user.id,
|
@@ -97,7 +103,6 @@ def list_artifact_versions(
|
|
97
103
|
|
98
104
|
@artifact_version_router.post(
|
99
105
|
"",
|
100
|
-
response_model=ArtifactVersionResponse,
|
101
106
|
responses={401: error_response, 409: error_response, 422: error_response},
|
102
107
|
)
|
103
108
|
@handle_exceptions
|
@@ -115,7 +120,6 @@ def create_artifact_version(
|
|
115
120
|
"""
|
116
121
|
return verify_permissions_and_create_entity(
|
117
122
|
request_model=artifact_version,
|
118
|
-
resource_type=ResourceType.ARTIFACT_VERSION,
|
119
123
|
create_method=zen_store().create_artifact_version,
|
120
124
|
)
|
121
125
|
|
@@ -139,14 +143,12 @@ def batch_create_artifact_version(
|
|
139
143
|
"""
|
140
144
|
return verify_permissions_and_batch_create_entity(
|
141
145
|
batch=artifact_versions,
|
142
|
-
resource_type=ResourceType.ARTIFACT_VERSION,
|
143
146
|
create_method=zen_store().batch_create_artifact_versions,
|
144
147
|
)
|
145
148
|
|
146
149
|
|
147
150
|
@artifact_version_router.get(
|
148
151
|
"/{artifact_version_id}",
|
149
|
-
response_model=ArtifactVersionResponse,
|
150
152
|
responses={401: error_response, 404: error_response, 422: error_response},
|
151
153
|
)
|
152
154
|
@handle_exceptions
|
@@ -174,7 +176,6 @@ def get_artifact_version(
|
|
174
176
|
|
175
177
|
@artifact_version_router.put(
|
176
178
|
"/{artifact_version_id}",
|
177
|
-
response_model=ArtifactVersionResponse,
|
178
179
|
responses={401: error_response, 404: error_response, 422: error_response},
|
179
180
|
)
|
180
181
|
@handle_exceptions
|
@@ -227,24 +228,29 @@ def delete_artifact_version(
|
|
227
228
|
)
|
228
229
|
@handle_exceptions
|
229
230
|
def prune_artifact_versions(
|
231
|
+
workspace_name_or_id: Union[str, UUID],
|
230
232
|
only_versions: bool = True,
|
231
233
|
_: AuthContext = Security(authorize),
|
232
234
|
) -> None:
|
233
235
|
"""Prunes unused artifact versions and their artifacts.
|
234
236
|
|
235
237
|
Args:
|
238
|
+
workspace_name_or_id: The workspace name or ID to prune artifact
|
239
|
+
versions for.
|
236
240
|
only_versions: Only delete artifact versions, keeping artifacts
|
237
241
|
"""
|
242
|
+
workspace_id = zen_store().get_workspace(workspace_name_or_id).id
|
243
|
+
|
238
244
|
verify_permissions_and_prune_entities(
|
239
245
|
resource_type=ResourceType.ARTIFACT_VERSION,
|
240
246
|
prune_method=zen_store().prune_artifact_versions,
|
241
247
|
only_versions=only_versions,
|
248
|
+
workspace_id=workspace_id,
|
242
249
|
)
|
243
250
|
|
244
251
|
|
245
252
|
@artifact_version_router.get(
|
246
253
|
"/{artifact_version_id}" + VISUALIZE,
|
247
|
-
response_model=LoadedVisualization,
|
248
254
|
responses={401: error_response, 404: error_response, 422: error_response},
|
249
255
|
)
|
250
256
|
@handle_exceptions
|