zenml-nightly 0.75.0.dev20250313__py3-none-any.whl → 0.75.0.dev20250315__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/login.py +21 -18
- zenml/cli/pipeline.py +3 -3
- zenml/cli/project.py +172 -0
- zenml/cli/server.py +5 -5
- 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 +352 -341
- zenml/config/global_config.py +41 -43
- zenml/config/server_config.py +9 -9
- zenml/constants.py +5 -3
- zenml/event_hub/event_hub.py +1 -1
- zenml/integrations/gcp/__init__.py +1 -0
- zenml/integrations/gcp/flavors/vertex_orchestrator_flavor.py +5 -0
- zenml/integrations/gcp/flavors/vertex_step_operator_flavor.py +5 -28
- zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +125 -78
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py +7 -6
- zenml/integrations/gcp/vertex_custom_job_parameters.py +50 -0
- 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/login/credentials.py +26 -27
- zenml/login/credentials_store.py +5 -5
- zenml/login/pro/client.py +9 -9
- zenml/login/pro/utils.py +8 -8
- zenml/login/pro/{tenant → workspace}/__init__.py +1 -1
- zenml/login/pro/{tenant → workspace}/client.py +25 -25
- zenml/login/pro/{tenant → workspace}/models.py +27 -28
- 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 +26 -18
- 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/tag.py +96 -3
- 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/server_models.py +6 -6
- 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 +3 -3
- 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/utils/dashboard_utils.py +1 -1
- zenml/utils/tag_utils.py +0 -12
- zenml/zen_server/cloud_utils.py +3 -3
- zenml/zen_server/feature_gate/endpoint_utils.py +1 -1
- zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +1 -1
- zenml/zen_server/rbac/endpoint_utils.py +17 -17
- zenml/zen_server/rbac/models.py +47 -22
- 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 +7 -74
- 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 +7 -7
- 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 +6 -5
- zenml/zen_stores/base_zen_store.py +38 -42
- 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 +55 -63
- 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 +318 -275
- zenml/zen_stores/zen_store_interface.py +56 -70
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250315.dist-info}/METADATA +1 -1
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250315.dist-info}/RECORD +143 -140
- 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.dev20250315.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250315.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.75.0.dev20250313.dist-info → zenml_nightly-0.75.0.dev20250315.dist-info}/entry_points.txt +0 -0
@@ -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,10 @@ class Resource(BaseModel):
|
|
110
110
|
Returns:
|
111
111
|
Resource string representation.
|
112
112
|
"""
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
# this is what is expected by the RBAC implementation.
|
118
|
-
workspace_id = self.id
|
119
|
-
|
120
|
-
if workspace_id:
|
121
|
-
representation = f"{workspace_id}:"
|
113
|
+
project_id = self.project_id
|
114
|
+
|
115
|
+
if project_id:
|
116
|
+
representation = f"{project_id}:"
|
122
117
|
else:
|
123
118
|
representation = ""
|
124
119
|
representation += self.type
|
@@ -127,12 +122,42 @@ class Resource(BaseModel):
|
|
127
122
|
|
128
123
|
return representation
|
129
124
|
|
125
|
+
@classmethod
|
126
|
+
def parse(cls, resource: str) -> "Resource":
|
127
|
+
"""Parse an RBAC resource string into a Resource object.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
resource: The resource to convert.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
The converted resource.
|
134
|
+
"""
|
135
|
+
project_id: Optional[str] = None
|
136
|
+
if ":" in resource:
|
137
|
+
(
|
138
|
+
project_id,
|
139
|
+
resource_type_and_id,
|
140
|
+
) = resource.split(":", maxsplit=1)
|
141
|
+
else:
|
142
|
+
project_id = None
|
143
|
+
resource_type_and_id = resource
|
144
|
+
|
145
|
+
resource_id: Optional[str] = None
|
146
|
+
if "/" in resource_type_and_id:
|
147
|
+
resource_type, resource_id = resource_type_and_id.split("/")
|
148
|
+
else:
|
149
|
+
resource_type = resource_type_and_id
|
150
|
+
|
151
|
+
return Resource(
|
152
|
+
type=resource_type, id=resource_id, project_id=project_id
|
153
|
+
)
|
154
|
+
|
130
155
|
@model_validator(mode="after")
|
131
|
-
def
|
132
|
-
"""Validate that
|
156
|
+
def validate_project_id(self) -> "Resource":
|
157
|
+
"""Validate that project_id is set in combination with project-scoped resource types.
|
133
158
|
|
134
159
|
Raises:
|
135
|
-
ValueError: If
|
160
|
+
ValueError: If project_id is not set for a project-scoped
|
136
161
|
resource or set for an unscoped resource.
|
137
162
|
|
138
163
|
Returns:
|
@@ -140,15 +165,15 @@ class Resource(BaseModel):
|
|
140
165
|
"""
|
141
166
|
resource_type = ResourceType(self.type)
|
142
167
|
|
143
|
-
if resource_type.
|
168
|
+
if resource_type.is_project_scoped() and not self.project_id:
|
144
169
|
raise ValueError(
|
145
|
-
"
|
170
|
+
"project_id must be set for project-scoped resource type "
|
146
171
|
f"'{self.type}'"
|
147
172
|
)
|
148
173
|
|
149
|
-
if not resource_type.
|
174
|
+
if not resource_type.is_project_scoped() and self.project_id:
|
150
175
|
raise ValueError(
|
151
|
-
"
|
176
|
+
"project_id must not be set for global resource type "
|
152
177
|
f"'{self.type}'"
|
153
178
|
)
|
154
179
|
|
@@ -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,
|
@@ -13,12 +13,11 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Cloud RBAC implementation."""
|
15
15
|
|
16
|
-
from typing import TYPE_CHECKING, Dict, List,
|
16
|
+
from typing import TYPE_CHECKING, Dict, List, 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
|
20
20
|
from zenml.zen_server.rbac.rbac_interface import RBACInterface
|
21
|
-
from zenml.zen_server.utils import server_config
|
22
21
|
|
23
22
|
if TYPE_CHECKING:
|
24
23
|
from zenml.models import UserResponse
|
@@ -29,68 +28,6 @@ ALLOWED_RESOURCE_IDS_ENDPOINT = "/rbac/allowed_resource_ids"
|
|
29
28
|
RESOURCE_MEMBERSHIP_ENDPOINT = "/rbac/resource_members"
|
30
29
|
RESOURCES_ENDPOINT = "/rbac/resources"
|
31
30
|
|
32
|
-
SERVER_SCOPE_IDENTIFIER = "server"
|
33
|
-
|
34
|
-
SERVER_ID = server_config().external_server_id
|
35
|
-
|
36
|
-
|
37
|
-
def _convert_to_cloud_resource(resource: Resource) -> str:
|
38
|
-
"""Convert a resource to a ZenML Pro Management Plane resource.
|
39
|
-
|
40
|
-
Args:
|
41
|
-
resource: The resource to convert.
|
42
|
-
|
43
|
-
Returns:
|
44
|
-
The converted resource.
|
45
|
-
"""
|
46
|
-
return f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}:{resource}"
|
47
|
-
|
48
|
-
|
49
|
-
def _convert_from_cloud_resource(cloud_resource: str) -> Resource:
|
50
|
-
"""Convert a cloud resource to a ZenML server resource.
|
51
|
-
|
52
|
-
Args:
|
53
|
-
cloud_resource: The cloud resource to convert.
|
54
|
-
|
55
|
-
Raises:
|
56
|
-
ValueError: If the cloud resource is invalid for this server.
|
57
|
-
|
58
|
-
Returns:
|
59
|
-
The converted resource.
|
60
|
-
"""
|
61
|
-
scope, workspace_resource_type_and_id = cloud_resource.split(
|
62
|
-
":", maxsplit=1
|
63
|
-
)
|
64
|
-
|
65
|
-
if scope != f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}":
|
66
|
-
raise ValueError("Invalid scope for server resource.")
|
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
|
79
|
-
if "/" in resource_type_and_id:
|
80
|
-
resource_type, resource_id = resource_type_and_id.split("/")
|
81
|
-
else:
|
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
|
-
)
|
93
|
-
|
94
31
|
|
95
32
|
class ZenMLCloudRBAC(RBACInterface):
|
96
33
|
"""RBAC implementation that uses the ZenML Pro Management Plane as a backend."""
|
@@ -127,9 +64,7 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
127
64
|
|
128
65
|
params = {
|
129
66
|
"user_id": str(user.external_user_id),
|
130
|
-
"resources":
|
131
|
-
_convert_to_cloud_resource(resource) for resource in resources
|
132
|
-
],
|
67
|
+
"resources": resources,
|
133
68
|
"action": str(action),
|
134
69
|
}
|
135
70
|
response = self._connection.get(
|
@@ -138,7 +73,7 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
138
73
|
value = response.json()
|
139
74
|
|
140
75
|
assert isinstance(value, dict)
|
141
|
-
return {
|
76
|
+
return {Resource.parse(k): v for k, v in value.items()}
|
142
77
|
|
143
78
|
def list_allowed_resource_ids(
|
144
79
|
self, user: "UserResponse", resource: Resource, action: Action
|
@@ -168,7 +103,7 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
168
103
|
assert user.external_user_id
|
169
104
|
params = {
|
170
105
|
"user_id": str(user.external_user_id),
|
171
|
-
"resource":
|
106
|
+
"resource": str(resource),
|
172
107
|
"action": str(action),
|
173
108
|
}
|
174
109
|
response = self._connection.get(
|
@@ -198,7 +133,7 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
198
133
|
|
199
134
|
data = {
|
200
135
|
"user_id": str(user.external_user_id),
|
201
|
-
"resource":
|
136
|
+
"resource": str(resource),
|
202
137
|
"actions": [str(action) for action in actions],
|
203
138
|
}
|
204
139
|
self._connection.post(endpoint=RESOURCE_MEMBERSHIP_ENDPOINT, data=data)
|
@@ -211,8 +146,6 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
211
146
|
information.
|
212
147
|
"""
|
213
148
|
params = {
|
214
|
-
"resources": [
|
215
|
-
_convert_to_cloud_resource(resource) for resource in resources
|
216
|
-
],
|
149
|
+
"resources": [str(resource) for resource in resources],
|
217
150
|
}
|
218
151
|
self._connection.delete(endpoint=RESOURCES_ENDPOINT, params=params)
|
@@ -46,7 +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
|
-
|
49
|
+
set_filter_project_scope,
|
50
50
|
zen_store,
|
51
51
|
)
|
52
52
|
|
@@ -81,14 +81,14 @@ def list_artifact_versions(
|
|
81
81
|
Returns:
|
82
82
|
The artifact versions according to query filters.
|
83
83
|
"""
|
84
|
-
# A
|
85
|
-
#
|
86
|
-
|
87
|
-
assert isinstance(artifact_version_filter_model.
|
84
|
+
# A project scoped request must always be scoped to a specific
|
85
|
+
# project. This is required for the RBAC check to work.
|
86
|
+
set_filter_project_scope(artifact_version_filter_model)
|
87
|
+
assert isinstance(artifact_version_filter_model.project, UUID)
|
88
88
|
|
89
89
|
allowed_artifact_ids = get_allowed_resource_ids(
|
90
90
|
resource_type=ResourceType.ARTIFACT,
|
91
|
-
|
91
|
+
project_id=artifact_version_filter_model.project,
|
92
92
|
)
|
93
93
|
artifact_version_filter_model.configure_rbac(
|
94
94
|
authenticated_user_id=auth_context.user.id,
|
@@ -228,24 +228,24 @@ def delete_artifact_version(
|
|
228
228
|
)
|
229
229
|
@handle_exceptions
|
230
230
|
def prune_artifact_versions(
|
231
|
-
|
231
|
+
project_name_or_id: Union[str, UUID],
|
232
232
|
only_versions: bool = True,
|
233
233
|
_: AuthContext = Security(authorize),
|
234
234
|
) -> None:
|
235
235
|
"""Prunes unused artifact versions and their artifacts.
|
236
236
|
|
237
237
|
Args:
|
238
|
-
|
238
|
+
project_name_or_id: The project name or ID to prune artifact
|
239
239
|
versions for.
|
240
240
|
only_versions: Only delete artifact versions, keeping artifacts
|
241
241
|
"""
|
242
|
-
|
242
|
+
project_id = zen_store().get_project(project_name_or_id).id
|
243
243
|
|
244
244
|
verify_permissions_and_prune_entities(
|
245
245
|
resource_type=ResourceType.ARTIFACT_VERSION,
|
246
246
|
prune_method=zen_store().prune_artifact_versions,
|
247
247
|
only_versions=only_versions,
|
248
|
-
|
248
|
+
project_id=project_id,
|
249
249
|
)
|
250
250
|
|
251
251
|
|
@@ -582,7 +582,7 @@ def api_token(
|
|
582
582
|
f"step run {token.step_run_id}."
|
583
583
|
)
|
584
584
|
|
585
|
-
|
585
|
+
project_id: Optional[UUID] = None
|
586
586
|
|
587
587
|
if schedule_id:
|
588
588
|
# The schedule must exist
|
@@ -593,7 +593,7 @@ def api_token(
|
|
593
593
|
f"Schedule {schedule_id} does not exist and API tokens cannot "
|
594
594
|
"be generated for non-existent schedules for security reasons."
|
595
595
|
)
|
596
|
-
|
596
|
+
project_id = schedule.project.id
|
597
597
|
|
598
598
|
if not schedule.active:
|
599
599
|
raise ValueError(
|
@@ -614,7 +614,7 @@ def api_token(
|
|
614
614
|
|
615
615
|
verify_permission_for_model(model=pipeline_run, action=Action.READ)
|
616
616
|
|
617
|
-
|
617
|
+
project_id = pipeline_run.project.id
|
618
618
|
|
619
619
|
if pipeline_run.status.is_finished:
|
620
620
|
raise ValueError(
|
@@ -633,7 +633,7 @@ def api_token(
|
|
633
633
|
"be generated for non-existent step runs for security reasons."
|
634
634
|
)
|
635
635
|
|
636
|
-
|
636
|
+
project_id = step_run.project.id
|
637
637
|
|
638
638
|
if step_run.status.is_finished:
|
639
639
|
raise ValueError(
|
@@ -642,11 +642,11 @@ def api_token(
|
|
642
642
|
"for security reasons."
|
643
643
|
)
|
644
644
|
|
645
|
-
assert
|
645
|
+
assert project_id is not None
|
646
646
|
verify_permission(
|
647
647
|
resource_type=ResourceType.PIPELINE_RUN,
|
648
648
|
action=Action.CREATE,
|
649
|
-
|
649
|
+
project_id=project_id,
|
650
650
|
)
|
651
651
|
|
652
652
|
return generate_access_token(
|