zenml-nightly 0.75.0.dev20250313__py3-none-any.whl → 0.75.0.dev20250314__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/analytics/context.py +4 -4
- zenml/analytics/enums.py +2 -2
- zenml/artifacts/utils.py +2 -2
- zenml/cli/__init__.py +8 -9
- zenml/cli/base.py +2 -2
- zenml/cli/code_repository.py +1 -1
- zenml/cli/pipeline.py +3 -3
- zenml/cli/project.py +172 -0
- zenml/cli/service_accounts.py +0 -1
- zenml/cli/service_connectors.py +15 -16
- zenml/cli/stack.py +0 -2
- zenml/cli/stack_components.py +2 -2
- zenml/cli/utils.py +3 -3
- zenml/client.py +347 -340
- zenml/config/global_config.py +41 -43
- zenml/constants.py +5 -3
- zenml/event_hub/event_hub.py +1 -1
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py +7 -6
- zenml/integrations/mlflow/steps/mlflow_registry.py +3 -3
- zenml/integrations/wandb/__init__.py +1 -1
- zenml/integrations/wandb/experiment_trackers/wandb_experiment_tracker.py +29 -9
- zenml/integrations/wandb/flavors/wandb_experiment_tracker_flavor.py +2 -0
- zenml/model/model.py +2 -2
- zenml/model_registries/base_model_registry.py +1 -1
- zenml/models/__init__.py +29 -29
- zenml/models/v2/base/filter.py +1 -1
- zenml/models/v2/base/scoped.py +49 -53
- zenml/models/v2/core/action.py +12 -12
- zenml/models/v2/core/artifact.py +15 -15
- zenml/models/v2/core/artifact_version.py +15 -15
- zenml/models/v2/core/code_repository.py +12 -12
- zenml/models/v2/core/event_source.py +12 -12
- zenml/models/v2/core/model.py +17 -17
- zenml/models/v2/core/model_version.py +15 -15
- zenml/models/v2/core/pipeline.py +15 -15
- zenml/models/v2/core/pipeline_build.py +14 -14
- zenml/models/v2/core/pipeline_deployment.py +12 -14
- zenml/models/v2/core/pipeline_run.py +16 -16
- zenml/models/v2/core/project.py +203 -0
- zenml/models/v2/core/run_metadata.py +2 -2
- zenml/models/v2/core/run_template.py +15 -15
- zenml/models/v2/core/schedule.py +12 -12
- zenml/models/v2/core/secret.py +1 -1
- zenml/models/v2/core/service.py +14 -14
- zenml/models/v2/core/step_run.py +13 -13
- zenml/models/v2/core/trigger.py +13 -13
- zenml/models/v2/core/trigger_execution.py +2 -2
- zenml/models/v2/core/user.py +0 -17
- zenml/models/v2/misc/statistics.py +4 -4
- zenml/orchestrators/cache_utils.py +7 -7
- zenml/orchestrators/input_utils.py +1 -1
- zenml/orchestrators/step_launcher.py +1 -1
- zenml/orchestrators/step_run_utils.py +2 -2
- zenml/orchestrators/utils.py +4 -4
- zenml/pipelines/build_utils.py +2 -2
- zenml/pipelines/pipeline_definition.py +5 -5
- zenml/pipelines/run_utils.py +1 -1
- zenml/service_connectors/service_connector.py +0 -3
- zenml/service_connectors/service_connector_utils.py +0 -1
- zenml/stack/stack.py +0 -1
- zenml/steps/base_step.py +10 -2
- zenml/zen_server/rbac/endpoint_utils.py +17 -17
- zenml/zen_server/rbac/models.py +20 -20
- zenml/zen_server/rbac/rbac_sql_zen_store.py +3 -3
- zenml/zen_server/rbac/utils.py +23 -25
- zenml/zen_server/rbac/zenml_cloud_rbac.py +12 -16
- zenml/zen_server/routers/artifact_version_endpoints.py +10 -10
- zenml/zen_server/routers/auth_endpoints.py +6 -6
- zenml/zen_server/routers/code_repositories_endpoints.py +12 -14
- zenml/zen_server/routers/model_versions_endpoints.py +13 -15
- zenml/zen_server/routers/models_endpoints.py +7 -9
- zenml/zen_server/routers/pipeline_builds_endpoints.py +14 -16
- zenml/zen_server/routers/pipeline_deployments_endpoints.py +13 -15
- zenml/zen_server/routers/pipelines_endpoints.py +16 -18
- zenml/zen_server/routers/{workspaces_endpoints.py → projects_endpoints.py} +111 -68
- zenml/zen_server/routers/run_metadata_endpoints.py +7 -9
- zenml/zen_server/routers/run_templates_endpoints.py +15 -17
- zenml/zen_server/routers/runs_endpoints.py +12 -14
- zenml/zen_server/routers/schedule_endpoints.py +12 -14
- zenml/zen_server/routers/secrets_endpoints.py +1 -3
- zenml/zen_server/routers/server_endpoints.py +5 -5
- zenml/zen_server/routers/service_connectors_endpoints.py +11 -13
- zenml/zen_server/routers/service_endpoints.py +7 -9
- zenml/zen_server/routers/stack_components_endpoints.py +9 -11
- zenml/zen_server/routers/stacks_endpoints.py +9 -11
- zenml/zen_server/routers/steps_endpoints.py +6 -6
- zenml/zen_server/routers/users_endpoints.py +5 -43
- zenml/zen_server/template_execution/utils.py +4 -4
- zenml/zen_server/utils.py +10 -10
- zenml/zen_server/zen_server_api.py +3 -2
- zenml/zen_stores/base_zen_store.py +35 -39
- zenml/zen_stores/migrations/versions/12eff0206201_rename_workspace_to_project.py +768 -0
- zenml/zen_stores/migrations/versions/41b28cae31ce_make_artifacts_workspace_scoped.py +3 -3
- zenml/zen_stores/migrations/versions/cbc6acd71f92_add_workspace_display_name.py +58 -0
- zenml/zen_stores/rest_zen_store.py +54 -62
- zenml/zen_stores/schemas/__init__.py +2 -2
- zenml/zen_stores/schemas/action_schemas.py +9 -9
- zenml/zen_stores/schemas/artifact_schemas.py +15 -17
- zenml/zen_stores/schemas/code_repository_schemas.py +16 -18
- zenml/zen_stores/schemas/event_source_schemas.py +9 -9
- zenml/zen_stores/schemas/model_schemas.py +15 -17
- zenml/zen_stores/schemas/pipeline_build_schemas.py +7 -7
- zenml/zen_stores/schemas/pipeline_deployment_schemas.py +7 -7
- zenml/zen_stores/schemas/pipeline_run_schemas.py +9 -9
- zenml/zen_stores/schemas/pipeline_schemas.py +9 -9
- zenml/zen_stores/schemas/{workspace_schemas.py → project_schemas.py} +47 -41
- zenml/zen_stores/schemas/run_metadata_schemas.py +5 -5
- zenml/zen_stores/schemas/run_template_schemas.py +9 -9
- zenml/zen_stores/schemas/schedule_schema.py +9 -9
- zenml/zen_stores/schemas/service_schemas.py +7 -7
- zenml/zen_stores/schemas/step_run_schemas.py +7 -7
- zenml/zen_stores/schemas/trigger_schemas.py +9 -9
- zenml/zen_stores/schemas/user_schemas.py +0 -12
- zenml/zen_stores/sql_zen_store.py +258 -268
- zenml/zen_stores/zen_store_interface.py +56 -70
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250314.dist-info}/METADATA +1 -1
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250314.dist-info}/RECORD +121 -119
- zenml/cli/workspace.py +0 -160
- zenml/models/v2/core/workspace.py +0 -131
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250314.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250314.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250314.dist-info}/entry_points.txt +0 -0
@@ -20,8 +20,8 @@ from zenml.models.v2.base.base import (
|
|
20
20
|
)
|
21
21
|
|
22
22
|
|
23
|
-
class
|
24
|
-
"""
|
23
|
+
class ProjectStatistics(BaseZenModel):
|
24
|
+
"""Project statistics."""
|
25
25
|
|
26
26
|
pipelines: int = Field(
|
27
27
|
title="The number of pipelines.",
|
@@ -40,6 +40,6 @@ class ServerStatistics(BaseZenModel):
|
|
40
40
|
components: int = Field(
|
41
41
|
title="The number of components.",
|
42
42
|
)
|
43
|
-
|
44
|
-
title="The number of
|
43
|
+
projects: int = Field(
|
44
|
+
title="The number of projects.",
|
45
45
|
)
|
@@ -34,7 +34,7 @@ def generate_cache_key(
|
|
34
34
|
step: "Step",
|
35
35
|
input_artifact_ids: Dict[str, "UUID"],
|
36
36
|
artifact_store: "BaseArtifactStore",
|
37
|
-
|
37
|
+
project_id: "UUID",
|
38
38
|
) -> str:
|
39
39
|
"""Generates a cache key for a step run.
|
40
40
|
|
@@ -42,7 +42,7 @@ def generate_cache_key(
|
|
42
42
|
runs are identical and can be cached.
|
43
43
|
|
44
44
|
The cache key is a MD5 hash of:
|
45
|
-
- the
|
45
|
+
- the project ID,
|
46
46
|
- the artifact store ID and path,
|
47
47
|
- the source code that defines the step,
|
48
48
|
- the parameters of the step,
|
@@ -55,15 +55,15 @@ def generate_cache_key(
|
|
55
55
|
step: The step to generate the cache key for.
|
56
56
|
input_artifact_ids: The input artifact IDs for the step.
|
57
57
|
artifact_store: The artifact store of the active stack.
|
58
|
-
|
58
|
+
project_id: The ID of the active project.
|
59
59
|
|
60
60
|
Returns:
|
61
61
|
A cache key.
|
62
62
|
"""
|
63
63
|
hash_ = hashlib.md5() # nosec
|
64
64
|
|
65
|
-
#
|
66
|
-
hash_.update(
|
65
|
+
# Project ID
|
66
|
+
hash_.update(project_id.bytes)
|
67
67
|
|
68
68
|
# Artifact store ID and path
|
69
69
|
hash_.update(artifact_store.id.bytes)
|
@@ -106,7 +106,7 @@ def get_cached_step_run(cache_key: str) -> Optional["StepRunResponse"]:
|
|
106
106
|
"""If a given step can be cached, get the corresponding existing step run.
|
107
107
|
|
108
108
|
A step run can be cached if there is an existing step run in the same
|
109
|
-
|
109
|
+
project which has the same cache key and was successfully executed.
|
110
110
|
|
111
111
|
Args:
|
112
112
|
cache_key: The cache key of the step.
|
@@ -117,7 +117,7 @@ def get_cached_step_run(cache_key: str) -> Optional["StepRunResponse"]:
|
|
117
117
|
client = Client()
|
118
118
|
|
119
119
|
cache_candidates = client.list_run_steps(
|
120
|
-
|
120
|
+
project=client.active_project.id,
|
121
121
|
cache_key=cache_key,
|
122
122
|
status=ExecutionStatus.COMPLETED,
|
123
123
|
sort_by=f"{SorterOps.DESCENDING}:created",
|
@@ -319,7 +319,7 @@ class StepLauncher:
|
|
319
319
|
pipeline_run = PipelineRunRequest(
|
320
320
|
name=run_name,
|
321
321
|
orchestrator_run_id=self._orchestrator_run_id,
|
322
|
-
|
322
|
+
project=client.active_project.id,
|
323
323
|
deployment=self._deployment.id,
|
324
324
|
pipeline=(
|
325
325
|
self._deployment.pipeline.id
|
@@ -75,7 +75,7 @@ class StepRunRequestFactory:
|
|
75
75
|
pipeline_run_id=self.pipeline_run.id,
|
76
76
|
status=ExecutionStatus.RUNNING,
|
77
77
|
start_time=utc_now(),
|
78
|
-
|
78
|
+
project=Client().active_project.id,
|
79
79
|
)
|
80
80
|
|
81
81
|
def populate_request(self, request: StepRunRequest) -> None:
|
@@ -102,7 +102,7 @@ class StepRunRequestFactory:
|
|
102
102
|
step=step,
|
103
103
|
input_artifact_ids=input_artifact_ids,
|
104
104
|
artifact_store=self.stack.artifact_store,
|
105
|
-
|
105
|
+
project_id=Client().active_project.id,
|
106
106
|
)
|
107
107
|
request.cache_key = cache_key
|
108
108
|
|
zenml/orchestrators/utils.py
CHANGED
@@ -23,8 +23,8 @@ from zenml.config.global_config import (
|
|
23
23
|
GlobalConfiguration,
|
24
24
|
)
|
25
25
|
from zenml.constants import (
|
26
|
+
ENV_ZENML_ACTIVE_PROJECT_ID,
|
26
27
|
ENV_ZENML_ACTIVE_STACK_ID,
|
27
|
-
ENV_ZENML_ACTIVE_WORKSPACE_ID,
|
28
28
|
ENV_ZENML_DISABLE_CREDENTIALS_DISK_CACHING,
|
29
29
|
ENV_ZENML_PIPELINE_RUN_API_TOKEN_EXPIRATION,
|
30
30
|
ENV_ZENML_SERVER,
|
@@ -211,13 +211,13 @@ def get_config_environment_vars(
|
|
211
211
|
# in the pipeline run environment
|
212
212
|
environment_vars[ENV_ZENML_DISABLE_CREDENTIALS_DISK_CACHING] = "true"
|
213
213
|
|
214
|
-
# Make sure to use the correct active stack/
|
214
|
+
# Make sure to use the correct active stack/project which might come
|
215
215
|
# from a .zen repository and not the global config
|
216
216
|
environment_vars[ENV_ZENML_ACTIVE_STACK_ID] = str(
|
217
217
|
Client().active_stack_model.id
|
218
218
|
)
|
219
|
-
environment_vars[
|
220
|
-
Client().
|
219
|
+
environment_vars[ENV_ZENML_ACTIVE_PROJECT_ID] = str(
|
220
|
+
Client().active_project.id
|
221
221
|
)
|
222
222
|
|
223
223
|
return environment_vars
|
zenml/pipelines/build_utils.py
CHANGED
@@ -215,7 +215,7 @@ def reuse_or_create_pipeline_build(
|
|
215
215
|
build_model = Client().zen_store.get_build(build_id=build)
|
216
216
|
else:
|
217
217
|
build_request = PipelineBuildRequest(
|
218
|
-
|
218
|
+
project=Client().active_project.id,
|
219
219
|
stack=Client().active_stack_model.id,
|
220
220
|
pipeline=pipeline_id,
|
221
221
|
**build.model_dump(),
|
@@ -418,7 +418,7 @@ def create_pipeline_build(
|
|
418
418
|
)
|
419
419
|
stack_checksum = compute_stack_checksum(stack=stack_model)
|
420
420
|
build_request = PipelineBuildRequest(
|
421
|
-
|
421
|
+
project=client.active_project.id,
|
422
422
|
stack=stack_model.id,
|
423
423
|
pipeline=pipeline_id,
|
424
424
|
is_local=is_local,
|
@@ -684,7 +684,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
684
684
|
components = Client().active_stack_model.components
|
685
685
|
orchestrator = components[StackComponentType.ORCHESTRATOR][0]
|
686
686
|
schedule_model = ScheduleRequest(
|
687
|
-
|
687
|
+
project=Client().active_project.id,
|
688
688
|
pipeline_id=pipeline_id,
|
689
689
|
orchestrator_id=orchestrator.id,
|
690
690
|
name=schedule_name,
|
@@ -770,7 +770,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
770
770
|
code_path = code_utils.upload_code_if_necessary(code_archive)
|
771
771
|
|
772
772
|
request = PipelineDeploymentRequest(
|
773
|
-
|
773
|
+
project=Client().active_project.id,
|
774
774
|
stack=stack.id,
|
775
775
|
pipeline=pipeline_id,
|
776
776
|
build=build_id,
|
@@ -1013,7 +1013,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
1013
1013
|
for component_type, component in stack.components.items()
|
1014
1014
|
}
|
1015
1015
|
return {
|
1016
|
-
"
|
1016
|
+
"project_id": deployment.project.id,
|
1017
1017
|
"store_type": Client().zen_store.type.value,
|
1018
1018
|
**stack_metadata,
|
1019
1019
|
"total_steps": len(self.invocations),
|
@@ -1094,7 +1094,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
1094
1094
|
return _get()
|
1095
1095
|
except RuntimeError:
|
1096
1096
|
request = PipelineRequest(
|
1097
|
-
|
1097
|
+
project=client.active_project.id,
|
1098
1098
|
name=self.name,
|
1099
1099
|
)
|
1100
1100
|
|
@@ -1233,7 +1233,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
1233
1233
|
return id_
|
1234
1234
|
|
1235
1235
|
if not allow_suffix:
|
1236
|
-
raise RuntimeError("Duplicate step ID")
|
1236
|
+
raise RuntimeError(f"Duplicate step ID `{id_}`")
|
1237
1237
|
|
1238
1238
|
for index in range(2, 10000):
|
1239
1239
|
id_ = f"{base_id}_{index}"
|
zenml/pipelines/run_utils.py
CHANGED
@@ -81,7 +81,7 @@ def create_placeholder_run(
|
|
81
81
|
# running.
|
82
82
|
start_time=start_time,
|
83
83
|
orchestrator_run_id=None,
|
84
|
-
|
84
|
+
project=deployment.project.id,
|
85
85
|
deployment=deployment.id,
|
86
86
|
pipeline=deployment.pipeline.id if deployment.pipeline else None,
|
87
87
|
status=ExecutionStatus.INITIALIZING,
|
@@ -705,7 +705,6 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
705
705
|
|
706
706
|
def to_model(
|
707
707
|
self,
|
708
|
-
workspace: UUID,
|
709
708
|
name: Optional[str] = None,
|
710
709
|
description: str = "",
|
711
710
|
labels: Optional[Dict[str, str]] = None,
|
@@ -714,7 +713,6 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
714
713
|
|
715
714
|
Args:
|
716
715
|
name: The name of the connector.
|
717
|
-
workspace: The ID of the workspace that the connector belongs to.
|
718
716
|
description: The description of the connector.
|
719
717
|
labels: The labels of the connector.
|
720
718
|
|
@@ -737,7 +735,6 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
737
735
|
connector_type=spec.connector_type,
|
738
736
|
name=name,
|
739
737
|
description=description,
|
740
|
-
workspace=workspace,
|
741
738
|
auth_method=self.auth_method,
|
742
739
|
expires_at=self.expires_at,
|
743
740
|
expires_skew_tolerance=self.expires_skew_tolerance,
|
@@ -184,7 +184,6 @@ def get_resources_options_from_resource_model_for_full_stack(
|
|
184
184
|
else:
|
185
185
|
resource_model = zen_store.verify_service_connector_config(
|
186
186
|
service_connector=ServiceConnectorRequest(
|
187
|
-
workspace=client.active_workspace.id,
|
188
187
|
name="fake",
|
189
188
|
connector_type=connector_details.type,
|
190
189
|
auth_method=connector_details.auth_method,
|
zenml/stack/stack.py
CHANGED
zenml/steps/base_step.py
CHANGED
@@ -432,7 +432,9 @@ class BaseStep:
|
|
432
432
|
self,
|
433
433
|
*args: Any,
|
434
434
|
id: Optional[str] = None,
|
435
|
-
after: Union[
|
435
|
+
after: Union[
|
436
|
+
str, StepArtifact, Sequence[Union[str, StepArtifact]], None
|
437
|
+
] = None,
|
436
438
|
**kwargs: Any,
|
437
439
|
) -> Any:
|
438
440
|
"""Handle a call of the step.
|
@@ -497,8 +499,14 @@ class BaseStep:
|
|
497
499
|
}
|
498
500
|
if isinstance(after, str):
|
499
501
|
upstream_steps.add(after)
|
502
|
+
elif isinstance(after, StepArtifact):
|
503
|
+
upstream_steps.add(after.invocation_id)
|
500
504
|
elif isinstance(after, Sequence):
|
501
|
-
|
505
|
+
for item in after:
|
506
|
+
if isinstance(item, str):
|
507
|
+
upstream_steps.add(item)
|
508
|
+
elif isinstance(item, StepArtifact):
|
509
|
+
upstream_steps.add(item.invocation_id)
|
502
510
|
|
503
511
|
invocation_id = Pipeline.ACTIVE_PIPELINE.add_step_invocation(
|
504
512
|
step=self,
|
@@ -22,8 +22,8 @@ from zenml.models import (
|
|
22
22
|
BaseRequest,
|
23
23
|
BaseUpdate,
|
24
24
|
Page,
|
25
|
+
ProjectScopedFilter,
|
25
26
|
UserScopedRequest,
|
26
|
-
WorkspaceScopedFilter,
|
27
27
|
)
|
28
28
|
from zenml.zen_server.auth import get_auth_context
|
29
29
|
from zenml.zen_server.feature_gate.endpoint_utils import (
|
@@ -42,7 +42,7 @@ from zenml.zen_server.rbac.utils import (
|
|
42
42
|
verify_permission,
|
43
43
|
verify_permission_for_model,
|
44
44
|
)
|
45
|
-
from zenml.zen_server.utils import server_config,
|
45
|
+
from zenml.zen_server.utils import server_config, set_filter_project_scope
|
46
46
|
|
47
47
|
AnyRequest = TypeVar("AnyRequest", bound=BaseRequest)
|
48
48
|
AnyResponse = TypeVar("AnyResponse", bound=BaseIdentifiedResponse) # type: ignore[type-arg]
|
@@ -232,27 +232,27 @@ def verify_permissions_and_list_entities(
|
|
232
232
|
A page of entity models.
|
233
233
|
|
234
234
|
Raises:
|
235
|
-
ValueError: If the filter's
|
235
|
+
ValueError: If the filter's project scope is not set or is not a UUID.
|
236
236
|
"""
|
237
237
|
auth_context = get_auth_context()
|
238
238
|
assert auth_context
|
239
239
|
|
240
|
-
|
241
|
-
if isinstance(filter_model,
|
242
|
-
# A
|
243
|
-
#
|
244
|
-
|
245
|
-
if not filter_model.
|
246
|
-
filter_model.
|
240
|
+
project_id: Optional[UUID] = None
|
241
|
+
if isinstance(filter_model, ProjectScopedFilter):
|
242
|
+
# A project scoped filter must always be scoped to a specific
|
243
|
+
# project. This is required for the RBAC check to work.
|
244
|
+
set_filter_project_scope(filter_model)
|
245
|
+
if not filter_model.project or not isinstance(
|
246
|
+
filter_model.project, UUID
|
247
247
|
):
|
248
248
|
raise ValueError(
|
249
|
-
"
|
250
|
-
f"{type(filter_model.
|
249
|
+
"Project scope must be a UUID, got "
|
250
|
+
f"{type(filter_model.project)}."
|
251
251
|
)
|
252
|
-
|
252
|
+
project_id = filter_model.project
|
253
253
|
|
254
254
|
allowed_ids = get_allowed_resource_ids(
|
255
|
-
resource_type=resource_type,
|
255
|
+
resource_type=resource_type, project_id=project_id
|
256
256
|
)
|
257
257
|
filter_model.configure_rbac(
|
258
258
|
authenticated_user_id=auth_context.user.id, id=allowed_ids
|
@@ -318,7 +318,7 @@ def verify_permissions_and_delete_entity(
|
|
318
318
|
def verify_permissions_and_prune_entities(
|
319
319
|
resource_type: ResourceType,
|
320
320
|
prune_method: Callable[..., None],
|
321
|
-
|
321
|
+
project_id: Optional[UUID] = None,
|
322
322
|
**kwargs: Any,
|
323
323
|
) -> None:
|
324
324
|
"""Verify permissions and prune entities of certain type.
|
@@ -326,12 +326,12 @@ def verify_permissions_and_prune_entities(
|
|
326
326
|
Args:
|
327
327
|
resource_type: The resource type of the entities to prune.
|
328
328
|
prune_method: The method to prune the entities.
|
329
|
-
|
329
|
+
project_id: The project ID to prune the entities for.
|
330
330
|
kwargs: Keyword arguments to pass to the prune method.
|
331
331
|
"""
|
332
332
|
verify_permission(
|
333
333
|
resource_type=resource_type,
|
334
334
|
action=Action.PRUNE,
|
335
|
-
|
335
|
+
project_id=project_id,
|
336
336
|
)
|
337
337
|
prune_method(**kwargs)
|
zenml/zen_server/rbac/models.py
CHANGED
@@ -73,15 +73,15 @@ class ResourceType(StrEnum):
|
|
73
73
|
TAG = "tag"
|
74
74
|
TRIGGER = "trigger"
|
75
75
|
TRIGGER_EXECUTION = "trigger_execution"
|
76
|
-
|
76
|
+
PROJECT = "project"
|
77
77
|
# Deactivated for now
|
78
78
|
# USER = "user"
|
79
79
|
|
80
|
-
def
|
81
|
-
"""Check if a resource type is
|
80
|
+
def is_project_scoped(self) -> bool:
|
81
|
+
"""Check if a resource type is project scoped.
|
82
82
|
|
83
83
|
Returns:
|
84
|
-
Whether the resource type is
|
84
|
+
Whether the resource type is project scoped.
|
85
85
|
"""
|
86
86
|
return self not in [
|
87
87
|
self.FLAVOR,
|
@@ -91,7 +91,7 @@ class ResourceType(StrEnum):
|
|
91
91
|
self.STACK_COMPONENT,
|
92
92
|
self.TAG,
|
93
93
|
self.SERVICE_ACCOUNT,
|
94
|
-
self.
|
94
|
+
self.PROJECT,
|
95
95
|
# Deactivated for now
|
96
96
|
# self.USER,
|
97
97
|
]
|
@@ -102,7 +102,7 @@ class Resource(BaseModel):
|
|
102
102
|
|
103
103
|
type: str
|
104
104
|
id: Optional[UUID] = None
|
105
|
-
|
105
|
+
project_id: Optional[UUID] = None
|
106
106
|
|
107
107
|
def __str__(self) -> str:
|
108
108
|
"""Convert to a string.
|
@@ -110,15 +110,15 @@ class Resource(BaseModel):
|
|
110
110
|
Returns:
|
111
111
|
Resource string representation.
|
112
112
|
"""
|
113
|
-
|
114
|
-
if self.type == ResourceType.
|
115
|
-
# TODO: For now, we duplicate the
|
116
|
-
# representation when describing a
|
113
|
+
project_id = self.project_id
|
114
|
+
if self.type == ResourceType.PROJECT and self.id:
|
115
|
+
# TODO: For now, we duplicate the project ID in the string
|
116
|
+
# representation when describing a project instance, because
|
117
117
|
# this is what is expected by the RBAC implementation.
|
118
|
-
|
118
|
+
project_id = self.id
|
119
119
|
|
120
|
-
if
|
121
|
-
representation = f"{
|
120
|
+
if project_id:
|
121
|
+
representation = f"{project_id}:"
|
122
122
|
else:
|
123
123
|
representation = ""
|
124
124
|
representation += self.type
|
@@ -128,11 +128,11 @@ class Resource(BaseModel):
|
|
128
128
|
return representation
|
129
129
|
|
130
130
|
@model_validator(mode="after")
|
131
|
-
def
|
132
|
-
"""Validate that
|
131
|
+
def validate_project_id(self) -> "Resource":
|
132
|
+
"""Validate that project_id is set in combination with project-scoped resource types.
|
133
133
|
|
134
134
|
Raises:
|
135
|
-
ValueError: If
|
135
|
+
ValueError: If project_id is not set for a project-scoped
|
136
136
|
resource or set for an unscoped resource.
|
137
137
|
|
138
138
|
Returns:
|
@@ -140,15 +140,15 @@ class Resource(BaseModel):
|
|
140
140
|
"""
|
141
141
|
resource_type = ResourceType(self.type)
|
142
142
|
|
143
|
-
if resource_type.
|
143
|
+
if resource_type.is_project_scoped() and not self.project_id:
|
144
144
|
raise ValueError(
|
145
|
-
"
|
145
|
+
"project_id must be set for project-scoped resource type "
|
146
146
|
f"'{self.type}'"
|
147
147
|
)
|
148
148
|
|
149
|
-
if not resource_type.
|
149
|
+
if not resource_type.is_project_scoped() and self.project_id:
|
150
150
|
raise ValueError(
|
151
|
-
"
|
151
|
+
"project_id must not be set for global resource type "
|
152
152
|
f"'{self.type}'"
|
153
153
|
)
|
154
154
|
|
@@ -65,7 +65,7 @@ class RBACSqlZenStore(SqlZenStore):
|
|
65
65
|
verify_permission(
|
66
66
|
resource_type=ResourceType.MODEL,
|
67
67
|
action=Action.CREATE,
|
68
|
-
|
68
|
+
project_id=model_request.project,
|
69
69
|
)
|
70
70
|
check_entitlement(resource_type=ResourceType.MODEL)
|
71
71
|
except Exception as e:
|
@@ -80,7 +80,7 @@ class RBACSqlZenStore(SqlZenStore):
|
|
80
80
|
try:
|
81
81
|
model_response = self.get_model_by_name_or_id(
|
82
82
|
model_name_or_id=model_request.name,
|
83
|
-
|
83
|
+
project=model_request.project,
|
84
84
|
)
|
85
85
|
created = False
|
86
86
|
except KeyError:
|
@@ -152,7 +152,7 @@ class RBACSqlZenStore(SqlZenStore):
|
|
152
152
|
verify_permission(
|
153
153
|
resource_type=ResourceType.MODEL_VERSION,
|
154
154
|
action=Action.CREATE,
|
155
|
-
|
155
|
+
project_id=model_version_request.project,
|
156
156
|
)
|
157
157
|
except Exception as e:
|
158
158
|
allow_creation = False
|
zenml/zen_server/rbac/utils.py
CHANGED
@@ -32,10 +32,10 @@ from zenml.exceptions import IllegalOperationError
|
|
32
32
|
from zenml.models import (
|
33
33
|
BaseIdentifiedResponse,
|
34
34
|
Page,
|
35
|
+
ProjectScopedRequest,
|
36
|
+
ProjectScopedResponse,
|
35
37
|
UserResponse,
|
36
38
|
UserScopedResponse,
|
37
|
-
WorkspaceScopedRequest,
|
38
|
-
WorkspaceScopedResponse,
|
39
39
|
)
|
40
40
|
from zenml.zen_server.auth import get_auth_context
|
41
41
|
from zenml.zen_server.rbac.models import Action, Resource, ResourceType
|
@@ -300,7 +300,7 @@ def verify_permission(
|
|
300
300
|
resource_type: str,
|
301
301
|
action: Action,
|
302
302
|
resource_id: Optional[UUID] = None,
|
303
|
-
|
303
|
+
project_id: Optional[UUID] = None,
|
304
304
|
) -> None:
|
305
305
|
"""Verifies if a user has permission to perform an action on a resource.
|
306
306
|
|
@@ -309,11 +309,11 @@ def verify_permission(
|
|
309
309
|
action on.
|
310
310
|
action: The action the user wants to perform.
|
311
311
|
resource_id: ID of the resource the user wants to perform the action on.
|
312
|
-
|
313
|
-
on. Only used for
|
312
|
+
project_id: ID of the project the user wants to perform the action
|
313
|
+
on. Only used for project scoped resources.
|
314
314
|
"""
|
315
315
|
resource = Resource(
|
316
|
-
type=resource_type, id=resource_id,
|
316
|
+
type=resource_type, id=resource_id, project_id=project_id
|
317
317
|
)
|
318
318
|
batch_verify_permissions(resources={resource}, action=action)
|
319
319
|
|
@@ -321,15 +321,15 @@ def verify_permission(
|
|
321
321
|
def get_allowed_resource_ids(
|
322
322
|
resource_type: str,
|
323
323
|
action: Action = Action.READ,
|
324
|
-
|
324
|
+
project_id: Optional[UUID] = None,
|
325
325
|
) -> Optional[Set[UUID]]:
|
326
326
|
"""Get all resource IDs of a resource type that a user can access.
|
327
327
|
|
328
328
|
Args:
|
329
329
|
resource_type: The resource type.
|
330
330
|
action: The action the user wants to perform on the resource.
|
331
|
-
|
332
|
-
Required for
|
331
|
+
project_id: Optional project ID to filter the resources by.
|
332
|
+
Required for project scoped resources.
|
333
333
|
|
334
334
|
Returns:
|
335
335
|
A list of resource IDs or `None` if the user has full access to the
|
@@ -346,7 +346,7 @@ def get_allowed_resource_ids(
|
|
346
346
|
allowed_ids,
|
347
347
|
) = rbac().list_allowed_resource_ids(
|
348
348
|
user=auth_context.user,
|
349
|
-
resource=Resource(type=resource_type,
|
349
|
+
resource=Resource(type=resource_type, project_id=project_id),
|
350
350
|
action=action,
|
351
351
|
)
|
352
352
|
|
@@ -371,21 +371,19 @@ def get_resource_for_model(model: AnyModel) -> Optional[Resource]:
|
|
371
371
|
# This model is not tied to any RBAC resource type
|
372
372
|
return None
|
373
373
|
|
374
|
-
|
375
|
-
if isinstance(model,
|
376
|
-
# A
|
377
|
-
|
378
|
-
elif isinstance(model,
|
379
|
-
# A
|
380
|
-
|
374
|
+
project_id: Optional[UUID] = None
|
375
|
+
if isinstance(model, ProjectScopedResponse):
|
376
|
+
# A project scoped response is always scoped to a specific project
|
377
|
+
project_id = model.project.id
|
378
|
+
elif isinstance(model, ProjectScopedRequest):
|
379
|
+
# A project scoped request is always scoped to a specific project
|
380
|
+
project_id = model.project
|
381
381
|
|
382
382
|
resource_id: Optional[UUID] = None
|
383
383
|
if isinstance(model, BaseIdentifiedResponse):
|
384
384
|
resource_id = model.id
|
385
385
|
|
386
|
-
return Resource(
|
387
|
-
type=resource_type, id=resource_id, workspace_id=workspace_id
|
388
|
-
)
|
386
|
+
return Resource(type=resource_type, id=resource_id, project_id=project_id)
|
389
387
|
|
390
388
|
|
391
389
|
def get_surrogate_permission_model_for_model(
|
@@ -456,6 +454,8 @@ def get_resource_type_for_model(
|
|
456
454
|
PipelineResponse,
|
457
455
|
PipelineRunRequest,
|
458
456
|
PipelineRunResponse,
|
457
|
+
ProjectRequest,
|
458
|
+
ProjectResponse,
|
459
459
|
RunMetadataRequest,
|
460
460
|
RunTemplateRequest,
|
461
461
|
RunTemplateResponse,
|
@@ -475,8 +475,6 @@ def get_resource_type_for_model(
|
|
475
475
|
TriggerExecutionResponse,
|
476
476
|
TriggerRequest,
|
477
477
|
TriggerResponse,
|
478
|
-
WorkspaceRequest,
|
479
|
-
WorkspaceResponse,
|
480
478
|
)
|
481
479
|
|
482
480
|
mapping: Dict[
|
@@ -528,8 +526,8 @@ def get_resource_type_for_model(
|
|
528
526
|
TriggerResponse: ResourceType.TRIGGER,
|
529
527
|
TriggerExecutionRequest: ResourceType.TRIGGER_EXECUTION,
|
530
528
|
TriggerExecutionResponse: ResourceType.TRIGGER_EXECUTION,
|
531
|
-
|
532
|
-
|
529
|
+
ProjectResponse: ResourceType.PROJECT,
|
530
|
+
ProjectRequest: ResourceType.PROJECT,
|
533
531
|
# UserResponse: ResourceType.USER,
|
534
532
|
}
|
535
533
|
|
@@ -673,7 +671,7 @@ def get_schema_for_resource_type(
|
|
673
671
|
ResourceType.SERVICE: ServiceSchema,
|
674
672
|
ResourceType.TAG: TagSchema,
|
675
673
|
ResourceType.SERVICE_ACCOUNT: UserSchema,
|
676
|
-
# ResourceType.
|
674
|
+
# ResourceType.PROJECT: ProjectSchema,
|
677
675
|
ResourceType.PIPELINE_RUN: PipelineRunSchema,
|
678
676
|
ResourceType.PIPELINE_DEPLOYMENT: PipelineDeploymentSchema,
|
679
677
|
ResourceType.PIPELINE_BUILD: PipelineBuildSchema,
|