zenml-nightly 0.75.0.dev20250312__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 +615 -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_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 +0 -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/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_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.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/METADATA +1 -1
- {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/RECORD +160 -147
- {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/entry_points.txt +0 -0
@@ -13,21 +13,17 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""High-level helper functions to write endpoints with RBAC."""
|
15
15
|
|
16
|
-
from typing import Any, Callable, List, TypeVar, Union
|
16
|
+
from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union
|
17
17
|
from uuid import UUID
|
18
18
|
|
19
|
-
from pydantic import BaseModel
|
20
|
-
|
21
|
-
from zenml.constants import (
|
22
|
-
REQUIRES_CUSTOM_RESOURCE_REPORTING,
|
23
|
-
)
|
24
|
-
from zenml.exceptions import IllegalOperationError
|
25
19
|
from zenml.models import (
|
26
20
|
BaseFilter,
|
27
21
|
BaseIdentifiedResponse,
|
28
22
|
BaseRequest,
|
23
|
+
BaseUpdate,
|
29
24
|
Page,
|
30
25
|
UserScopedRequest,
|
26
|
+
WorkspaceScopedFilter,
|
31
27
|
)
|
32
28
|
from zenml.zen_server.auth import get_auth_context
|
33
29
|
from zenml.zen_server.feature_gate.endpoint_utils import (
|
@@ -36,36 +32,42 @@ from zenml.zen_server.feature_gate.endpoint_utils import (
|
|
36
32
|
)
|
37
33
|
from zenml.zen_server.rbac.models import Action, ResourceType
|
38
34
|
from zenml.zen_server.rbac.utils import (
|
35
|
+
batch_verify_permissions_for_models,
|
39
36
|
dehydrate_page,
|
40
37
|
dehydrate_response_model,
|
38
|
+
dehydrate_response_model_batch,
|
39
|
+
delete_model_resource,
|
41
40
|
get_allowed_resource_ids,
|
41
|
+
get_resource_type_for_model,
|
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_workspace_scope
|
46
46
|
|
47
47
|
AnyRequest = TypeVar("AnyRequest", bound=BaseRequest)
|
48
48
|
AnyResponse = TypeVar("AnyResponse", bound=BaseIdentifiedResponse) # type: ignore[type-arg]
|
49
|
+
AnyOtherResponse = TypeVar("AnyOtherResponse", bound=BaseIdentifiedResponse) # type: ignore[type-arg]
|
49
50
|
AnyFilter = TypeVar("AnyFilter", bound=BaseFilter)
|
50
|
-
AnyUpdate = TypeVar("AnyUpdate", bound=
|
51
|
+
AnyUpdate = TypeVar("AnyUpdate", bound=BaseUpdate)
|
51
52
|
UUIDOrStr = TypeVar("UUIDOrStr", UUID, Union[UUID, str])
|
52
53
|
|
53
54
|
|
54
55
|
def verify_permissions_and_create_entity(
|
55
56
|
request_model: AnyRequest,
|
56
|
-
resource_type: ResourceType,
|
57
57
|
create_method: Callable[[AnyRequest], AnyResponse],
|
58
|
+
surrogate_models: Optional[List[AnyOtherResponse]] = None,
|
59
|
+
skip_entitlements: bool = False,
|
58
60
|
) -> AnyResponse:
|
59
61
|
"""Verify permissions and create the entity if authorized.
|
60
62
|
|
61
63
|
Args:
|
62
64
|
request_model: The entity request model.
|
63
|
-
resource_type: The resource type of the entity to create.
|
64
65
|
create_method: The method to create the entity.
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
surrogate_models: Optional list of surrogate models to verify
|
67
|
+
UPDATE permissions for instead of verifying CREATE permissions for
|
68
|
+
the request model.
|
69
|
+
skip_entitlements: Whether to skip the entitlement check and usage
|
70
|
+
increment.
|
69
71
|
|
70
72
|
Returns:
|
71
73
|
A model of the created entity.
|
@@ -74,43 +76,47 @@ def verify_permissions_and_create_entity(
|
|
74
76
|
auth_context = get_auth_context()
|
75
77
|
assert auth_context
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
)
|
82
|
-
verify_permission(resource_type=resource_type, action=Action.CREATE)
|
79
|
+
# Ignore the user field set in the request model, if any, and set it to
|
80
|
+
# the current user's ID instead. This is just a precaution, given that
|
81
|
+
# the SQLZenStore also does this same validation on all request models.
|
82
|
+
request_model.user = auth_context.user.id
|
83
83
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
if surrogate_models:
|
85
|
+
batch_verify_permissions_for_models(
|
86
|
+
models=surrogate_models, action=Action.UPDATE
|
87
|
+
)
|
88
|
+
else:
|
89
|
+
verify_permission_for_model(model=request_model, action=Action.CREATE)
|
90
|
+
|
91
|
+
resource_type = get_resource_type_for_model(request_model)
|
92
|
+
|
93
|
+
if resource_type:
|
94
|
+
needs_usage_increment = (
|
95
|
+
not skip_entitlements
|
96
|
+
and resource_type in server_config().reportable_resources
|
97
|
+
)
|
98
|
+
if needs_usage_increment:
|
99
|
+
check_entitlement(resource_type)
|
90
100
|
|
91
101
|
created = create_method(request_model)
|
92
102
|
|
93
|
-
if needs_usage_increment:
|
103
|
+
if resource_type and needs_usage_increment:
|
94
104
|
report_usage(resource_type, resource_id=created.id)
|
95
105
|
|
96
|
-
return created
|
106
|
+
return dehydrate_response_model(created)
|
97
107
|
|
98
108
|
|
99
109
|
def verify_permissions_and_batch_create_entity(
|
100
110
|
batch: List[AnyRequest],
|
101
|
-
resource_type: ResourceType,
|
102
111
|
create_method: Callable[[List[AnyRequest]], List[AnyResponse]],
|
103
112
|
) -> List[AnyResponse]:
|
104
113
|
"""Verify permissions and create a batch of entities if authorized.
|
105
114
|
|
106
115
|
Args:
|
107
116
|
batch: The batch to create.
|
108
|
-
resource_type: The resource type of the entities to create.
|
109
117
|
create_method: The method to create the entities.
|
110
118
|
|
111
119
|
Raises:
|
112
|
-
IllegalOperationError: If the request model has a different owner then
|
113
|
-
the currently authenticated user.
|
114
120
|
RuntimeError: If the resource type is usage-tracked.
|
115
121
|
|
116
122
|
Returns:
|
@@ -119,23 +125,73 @@ def verify_permissions_and_batch_create_entity(
|
|
119
125
|
auth_context = get_auth_context()
|
120
126
|
assert auth_context
|
121
127
|
|
128
|
+
resource_types = set()
|
122
129
|
for request_model in batch:
|
130
|
+
resource_type = get_resource_type_for_model(request_model)
|
131
|
+
if resource_type:
|
132
|
+
resource_types.add(resource_type)
|
133
|
+
|
123
134
|
if isinstance(request_model, UserScopedRequest):
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
135
|
+
# Ignore the user field set in the request model, if any, and set it
|
136
|
+
# to the current user's ID instead. This is just a precaution, given
|
137
|
+
# that the SQLZenStore also does this same validation on all request
|
138
|
+
# models.
|
139
|
+
request_model.user = auth_context.user.id
|
129
140
|
|
130
|
-
|
141
|
+
batch_verify_permissions_for_models(models=batch, action=Action.CREATE)
|
131
142
|
|
132
|
-
if
|
143
|
+
if resource_types & set(server_config().reportable_resources):
|
133
144
|
raise RuntimeError(
|
134
|
-
"Batch requests are currently not possible with usage-tracked
|
145
|
+
"Batch requests are currently not possible with usage-tracked "
|
146
|
+
"features."
|
135
147
|
)
|
136
148
|
|
137
149
|
created = create_method(batch)
|
138
|
-
return created
|
150
|
+
return dehydrate_response_model_batch(created)
|
151
|
+
|
152
|
+
|
153
|
+
def verify_permissions_and_get_or_create_entity(
|
154
|
+
request_model: AnyRequest,
|
155
|
+
get_or_create_method: Callable[
|
156
|
+
[AnyRequest, Optional[Callable[[], None]]], Tuple[AnyResponse, bool]
|
157
|
+
],
|
158
|
+
) -> Tuple[AnyResponse, bool]:
|
159
|
+
"""Verify permissions and create the entity if authorized.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
request_model: The entity request model.
|
163
|
+
get_or_create_method: The method to get or create the entity.
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
The entity and a boolean indicating whether the entity was created.
|
167
|
+
"""
|
168
|
+
if isinstance(request_model, UserScopedRequest):
|
169
|
+
auth_context = get_auth_context()
|
170
|
+
assert auth_context
|
171
|
+
|
172
|
+
# Ignore the user field set in the request model, if any, and set it to
|
173
|
+
# the current user's ID instead. This is just a precaution, given that
|
174
|
+
# the SQLZenStore also does this same validation on all request models.
|
175
|
+
request_model.user = auth_context.user.id
|
176
|
+
|
177
|
+
resource_type = get_resource_type_for_model(request_model)
|
178
|
+
needs_usage_increment = (
|
179
|
+
resource_type and resource_type in server_config().reportable_resources
|
180
|
+
)
|
181
|
+
|
182
|
+
def _pre_creation_hook() -> None:
|
183
|
+
verify_permission_for_model(model=request_model, action=Action.CREATE)
|
184
|
+
if resource_type and needs_usage_increment:
|
185
|
+
check_entitlement(resource_type=resource_type)
|
186
|
+
|
187
|
+
model, created = get_or_create_method(request_model, _pre_creation_hook)
|
188
|
+
|
189
|
+
if not created:
|
190
|
+
verify_permission_for_model(model=model, action=Action.READ)
|
191
|
+
elif resource_type and needs_usage_increment:
|
192
|
+
report_usage(resource_type, resource_id=model.id)
|
193
|
+
|
194
|
+
return dehydrate_response_model(model), created
|
139
195
|
|
140
196
|
|
141
197
|
def verify_permissions_and_get_entity(
|
@@ -174,11 +230,30 @@ def verify_permissions_and_list_entities(
|
|
174
230
|
|
175
231
|
Returns:
|
176
232
|
A page of entity models.
|
233
|
+
|
234
|
+
Raises:
|
235
|
+
ValueError: If the filter's workspace scope is not set or is not a UUID.
|
177
236
|
"""
|
178
237
|
auth_context = get_auth_context()
|
179
238
|
assert auth_context
|
180
239
|
|
181
|
-
|
240
|
+
workspace_id: Optional[UUID] = None
|
241
|
+
if isinstance(filter_model, WorkspaceScopedFilter):
|
242
|
+
# A workspace scoped filter must always be scoped to a specific
|
243
|
+
# workspace. This is required for the RBAC check to work.
|
244
|
+
set_filter_workspace_scope(filter_model)
|
245
|
+
if not filter_model.workspace or not isinstance(
|
246
|
+
filter_model.workspace, UUID
|
247
|
+
):
|
248
|
+
raise ValueError(
|
249
|
+
"Workspace scope must be a UUID, got "
|
250
|
+
f"{type(filter_model.workspace)}."
|
251
|
+
)
|
252
|
+
workspace_id = filter_model.workspace
|
253
|
+
|
254
|
+
allowed_ids = get_allowed_resource_ids(
|
255
|
+
resource_type=resource_type, workspace_id=workspace_id
|
256
|
+
)
|
182
257
|
filter_model.configure_rbac(
|
183
258
|
authenticated_user_id=auth_context.user.id, id=allowed_ids
|
184
259
|
)
|
@@ -191,6 +266,7 @@ def verify_permissions_and_update_entity(
|
|
191
266
|
update_model: AnyUpdate,
|
192
267
|
get_method: Callable[[UUIDOrStr, bool], AnyResponse],
|
193
268
|
update_method: Callable[[UUIDOrStr, AnyUpdate], AnyResponse],
|
269
|
+
**update_method_kwargs: Any,
|
194
270
|
) -> AnyResponse:
|
195
271
|
"""Verify permissions and update an entity.
|
196
272
|
|
@@ -199,6 +275,7 @@ def verify_permissions_and_update_entity(
|
|
199
275
|
update_model: The entity update model.
|
200
276
|
get_method: The method to fetch the entity.
|
201
277
|
update_method: The method to update the entity.
|
278
|
+
update_method_kwargs: Keyword arguments to pass to the update method.
|
202
279
|
|
203
280
|
Returns:
|
204
281
|
A model of the updated entity.
|
@@ -206,7 +283,9 @@ def verify_permissions_and_update_entity(
|
|
206
283
|
# We don't need the hydrated version here
|
207
284
|
model = get_method(id, False)
|
208
285
|
verify_permission_for_model(model, action=Action.UPDATE)
|
209
|
-
updated_model = update_method(
|
286
|
+
updated_model = update_method(
|
287
|
+
model.id, update_model, **update_method_kwargs
|
288
|
+
)
|
210
289
|
return dehydrate_response_model(updated_model)
|
211
290
|
|
212
291
|
|
@@ -214,6 +293,7 @@ def verify_permissions_and_delete_entity(
|
|
214
293
|
id: UUIDOrStr,
|
215
294
|
get_method: Callable[[UUIDOrStr, bool], AnyResponse],
|
216
295
|
delete_method: Callable[[UUIDOrStr], None],
|
296
|
+
**delete_method_kwargs: Any,
|
217
297
|
) -> AnyResponse:
|
218
298
|
"""Verify permissions and delete an entity.
|
219
299
|
|
@@ -221,6 +301,7 @@ def verify_permissions_and_delete_entity(
|
|
221
301
|
id: The ID of the entity to delete.
|
222
302
|
get_method: The method to fetch the entity.
|
223
303
|
delete_method: The method to delete the entity.
|
304
|
+
delete_method_kwargs: Keyword arguments to pass to the delete method.
|
224
305
|
|
225
306
|
Returns:
|
226
307
|
The deleted entity.
|
@@ -228,7 +309,8 @@ def verify_permissions_and_delete_entity(
|
|
228
309
|
# We don't need the hydrated version here
|
229
310
|
model = get_method(id, False)
|
230
311
|
verify_permission_for_model(model, action=Action.DELETE)
|
231
|
-
delete_method(model.id)
|
312
|
+
delete_method(model.id, **delete_method_kwargs)
|
313
|
+
delete_model_resource(model)
|
232
314
|
|
233
315
|
return model
|
234
316
|
|
@@ -236,6 +318,7 @@ def verify_permissions_and_delete_entity(
|
|
236
318
|
def verify_permissions_and_prune_entities(
|
237
319
|
resource_type: ResourceType,
|
238
320
|
prune_method: Callable[..., None],
|
321
|
+
workspace_id: Optional[UUID] = None,
|
239
322
|
**kwargs: Any,
|
240
323
|
) -> None:
|
241
324
|
"""Verify permissions and prune entities of certain type.
|
@@ -243,7 +326,12 @@ def verify_permissions_and_prune_entities(
|
|
243
326
|
Args:
|
244
327
|
resource_type: The resource type of the entities to prune.
|
245
328
|
prune_method: The method to prune the entities.
|
329
|
+
workspace_id: The workspace ID to prune the entities for.
|
246
330
|
kwargs: Keyword arguments to pass to the prune method.
|
247
331
|
"""
|
248
|
-
verify_permission(
|
332
|
+
verify_permission(
|
333
|
+
resource_type=resource_type,
|
334
|
+
action=Action.PRUNE,
|
335
|
+
workspace_id=workspace_id,
|
336
|
+
)
|
249
337
|
prune_method(**kwargs)
|
zenml/zen_server/rbac/models.py
CHANGED
@@ -16,7 +16,11 @@
|
|
16
16
|
from typing import Optional
|
17
17
|
from uuid import UUID
|
18
18
|
|
19
|
-
from pydantic import
|
19
|
+
from pydantic import (
|
20
|
+
BaseModel,
|
21
|
+
ConfigDict,
|
22
|
+
model_validator,
|
23
|
+
)
|
20
24
|
|
21
25
|
from zenml.utils.enum_utils import StrEnum
|
22
26
|
|
@@ -69,9 +73,28 @@ class ResourceType(StrEnum):
|
|
69
73
|
TAG = "tag"
|
70
74
|
TRIGGER = "trigger"
|
71
75
|
TRIGGER_EXECUTION = "trigger_execution"
|
76
|
+
WORKSPACE = "workspace"
|
72
77
|
# Deactivated for now
|
73
78
|
# USER = "user"
|
74
|
-
|
79
|
+
|
80
|
+
def is_workspace_scoped(self) -> bool:
|
81
|
+
"""Check if a resource type is workspace scoped.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Whether the resource type is workspace scoped.
|
85
|
+
"""
|
86
|
+
return self not in [
|
87
|
+
self.FLAVOR,
|
88
|
+
self.SECRET,
|
89
|
+
self.SERVICE_CONNECTOR,
|
90
|
+
self.STACK,
|
91
|
+
self.STACK_COMPONENT,
|
92
|
+
self.TAG,
|
93
|
+
self.SERVICE_ACCOUNT,
|
94
|
+
self.WORKSPACE,
|
95
|
+
# Deactivated for now
|
96
|
+
# self.USER,
|
97
|
+
]
|
75
98
|
|
76
99
|
|
77
100
|
class Resource(BaseModel):
|
@@ -79,6 +102,7 @@ class Resource(BaseModel):
|
|
79
102
|
|
80
103
|
type: str
|
81
104
|
id: Optional[UUID] = None
|
105
|
+
workspace_id: Optional[UUID] = None
|
82
106
|
|
83
107
|
def __str__(self) -> str:
|
84
108
|
"""Convert to a string.
|
@@ -86,10 +110,48 @@ class Resource(BaseModel):
|
|
86
110
|
Returns:
|
87
111
|
Resource string representation.
|
88
112
|
"""
|
89
|
-
|
113
|
+
workspace_id = self.workspace_id
|
114
|
+
if self.type == ResourceType.WORKSPACE and self.id:
|
115
|
+
# TODO: For now, we duplicate the workspace ID in the string
|
116
|
+
# representation when describing a workspace instance, because
|
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}:"
|
122
|
+
else:
|
123
|
+
representation = ""
|
124
|
+
representation += self.type
|
90
125
|
if self.id:
|
91
126
|
representation += f"/{self.id}"
|
92
127
|
|
93
128
|
return representation
|
94
129
|
|
130
|
+
@model_validator(mode="after")
|
131
|
+
def validate_workspace_id(self) -> "Resource":
|
132
|
+
"""Validate that workspace_id is set in combination with workspace-scoped resource types.
|
133
|
+
|
134
|
+
Raises:
|
135
|
+
ValueError: If workspace_id is not set for a workspace-scoped
|
136
|
+
resource or set for an unscoped resource.
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
The validated resource.
|
140
|
+
"""
|
141
|
+
resource_type = ResourceType(self.type)
|
142
|
+
|
143
|
+
if resource_type.is_workspace_scoped() and not self.workspace_id:
|
144
|
+
raise ValueError(
|
145
|
+
"workspace_id must be set for workspace-scoped resource type "
|
146
|
+
f"'{self.type}'"
|
147
|
+
)
|
148
|
+
|
149
|
+
if not resource_type.is_workspace_scoped() and self.workspace_id:
|
150
|
+
raise ValueError(
|
151
|
+
"workspace_id must not be set for global resource type "
|
152
|
+
f"'{self.type}'"
|
153
|
+
)
|
154
|
+
|
155
|
+
return self
|
156
|
+
|
95
157
|
model_config = ConfigDict(frozen=True)
|
@@ -73,3 +73,12 @@ class RBACInterface(ABC):
|
|
73
73
|
actions: The actions that the user should be able to perform on the
|
74
74
|
resource.
|
75
75
|
"""
|
76
|
+
|
77
|
+
@abstractmethod
|
78
|
+
def delete_resources(self, resources: List[Resource]) -> None:
|
79
|
+
"""Delete resource membership information for a list of resources.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
resources: The resources for which to delete the resource membership
|
83
|
+
information.
|
84
|
+
"""
|
@@ -63,7 +63,9 @@ class RBACSqlZenStore(SqlZenStore):
|
|
63
63
|
|
64
64
|
try:
|
65
65
|
verify_permission(
|
66
|
-
resource_type=ResourceType.MODEL,
|
66
|
+
resource_type=ResourceType.MODEL,
|
67
|
+
action=Action.CREATE,
|
68
|
+
workspace_id=model_request.workspace,
|
67
69
|
)
|
68
70
|
check_entitlement(resource_type=ResourceType.MODEL)
|
69
71
|
except Exception as e:
|
@@ -76,7 +78,10 @@ class RBACSqlZenStore(SqlZenStore):
|
|
76
78
|
)
|
77
79
|
else:
|
78
80
|
try:
|
79
|
-
model_response = self.
|
81
|
+
model_response = self.get_model_by_name_or_id(
|
82
|
+
model_name_or_id=model_request.name,
|
83
|
+
workspace=model_request.workspace,
|
84
|
+
)
|
80
85
|
created = False
|
81
86
|
except KeyError:
|
82
87
|
# The model does not exist. We now raise the error that
|
@@ -145,17 +150,20 @@ class RBACSqlZenStore(SqlZenStore):
|
|
145
150
|
|
146
151
|
try:
|
147
152
|
verify_permission(
|
148
|
-
resource_type=ResourceType.MODEL_VERSION,
|
153
|
+
resource_type=ResourceType.MODEL_VERSION,
|
154
|
+
action=Action.CREATE,
|
155
|
+
workspace_id=model_version_request.workspace,
|
149
156
|
)
|
150
157
|
except Exception as e:
|
151
158
|
allow_creation = False
|
152
159
|
error = e
|
153
160
|
|
154
161
|
if allow_creation:
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
162
|
+
(
|
163
|
+
created,
|
164
|
+
model_version_response,
|
165
|
+
) = super()._get_or_create_model_version(
|
166
|
+
model_version_request, producer_run_id=producer_run_id
|
159
167
|
)
|
160
168
|
else:
|
161
169
|
try:
|