zenml-nightly 0.72.0.dev20250121__py3-none-any.whl → 0.73.0.dev20250124__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/cli/login.py +2 -0
- zenml/cli/server.py +1 -0
- zenml/cli/service_connectors.py +8 -4
- zenml/cli/stack.py +2 -2
- zenml/config/pipeline_configurations.py +2 -2
- zenml/config/server_config.py +20 -0
- zenml/enums.py +1 -0
- zenml/event_hub/base_event_hub.py +2 -2
- zenml/integrations/airflow/orchestrators/airflow_orchestrator.py +4 -2
- zenml/integrations/aws/__init__.py +2 -1
- zenml/integrations/aws/flavors/sagemaker_orchestrator_flavor.py +15 -0
- zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +308 -70
- zenml/integrations/gcp/__init__.py +3 -0
- zenml/integrations/gcp/experiment_trackers/__init__.py +18 -0
- zenml/integrations/gcp/experiment_trackers/vertex_experiment_tracker.py +214 -0
- zenml/integrations/gcp/flavors/__init__.py +6 -0
- zenml/integrations/gcp/flavors/vertex_experiment_tracker_flavor.py +199 -0
- zenml/integrations/huggingface/__init__.py +1 -6
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +2 -2
- zenml/integrations/mlflow/experiment_trackers/mlflow_experiment_tracker.py +0 -1
- zenml/integrations/whylogs/data_validators/whylogs_data_validator.py +3 -1
- zenml/models/v2/core/api_key.py +2 -2
- zenml/models/v2/core/schedule.py +16 -1
- zenml/orchestrators/publish_utils.py +4 -4
- zenml/orchestrators/step_launcher.py +3 -3
- zenml/orchestrators/step_run_utils.py +2 -2
- zenml/pipelines/run_utils.py +2 -2
- zenml/service_connectors/service_connector.py +2 -2
- zenml/stack/stack.py +3 -3
- zenml/stack/stack_component.py +10 -2
- zenml/stack_deployments/stack_deployment.py +5 -0
- zenml/utils/git_utils.py +1 -1
- zenml/utils/string_utils.py +2 -2
- zenml/zen_server/auth.py +13 -6
- zenml/zen_server/dashboard/assets/{404-Dfq64Boz.js → 404-c8OuXDAT.js} +1 -1
- zenml/zen_server/dashboard/assets/{@reactflow-BUNIMFeC.js → @reactflow-6JPoencd.js} +1 -1
- zenml/zen_server/dashboard/assets/{AlertDialogDropdownItem-B73Vs10T.js → AlertDialogDropdownItem-8yPFDxEI.js} +1 -1
- zenml/zen_server/dashboard/assets/{CodeSnippet-DIJRT2NT.js → CodeSnippet-Qh1ae_DJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{CollapsibleCard-BzUHGZOU.js → CollapsibleCard-TiI4lId1.js} +1 -1
- zenml/zen_server/dashboard/assets/{Commands-BEGyld4c.js → Commands-BcR2Arie.js} +1 -1
- zenml/zen_server/dashboard/assets/{ComponentBadge-xyKiek1s.js → ComponentBadge-BqQNUZgb.js} +1 -1
- zenml/zen_server/dashboard/assets/{CopyButton-DhW-mapu.js → CopyButton-DCiXO3JC.js} +1 -1
- zenml/zen_server/dashboard/assets/{CsvVizualization-D8oazBiE.js → CsvVizualization-O9cVIaL8.js} +1 -1
- zenml/zen_server/dashboard/assets/{DeleteAlertDialog-WkSIIgfy.js → DeleteAlertDialog-DrPjHtXX.js} +1 -1
- zenml/zen_server/dashboard/assets/{DialogItem-Bgroeg29.js → DialogItem-BYG7d_M2.js} +1 -1
- zenml/zen_server/dashboard/assets/{Error-CY5tlu17.js → Error-C1zbWr19.js} +1 -1
- zenml/zen_server/dashboard/assets/{ExecutionStatus-G8mjIaeA.js → ExecutionStatus-Ct9srgHC.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-Bb1ed--O.js → Helpbox-Bm_1Zx9f.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-Da6-76M2.js → Infobox-OQdkCLSP.js} +1 -1
- zenml/zen_server/dashboard/assets/{InlineAvatar-DqnZaBNq.js → InlineAvatar-CQNjKoEQ.js} +1 -1
- zenml/zen_server/dashboard/assets/{NestedCollapsible-aK5ojKoF.js → NestedCollapsible-DDgd2SGb.js} +1 -1
- zenml/zen_server/dashboard/assets/Partials-MD3e95Dk.js +1 -0
- zenml/zen_server/dashboard/assets/{ProBadge-B4tRUYve.js → ProBadge-D784iVNC.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProCta-CZuP29Qz.js → ProCta-W2PEvNow.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderIcon-Bd7GUQ1_.js → ProviderIcon-DfDUOeAy.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderRadio-mstdqzsS.js → ProviderRadio-B81Elxrc.js} +1 -1
- zenml/zen_server/dashboard/assets/{RunSelector-CsruSB4i.js → RunSelector-DOXgdry5.js} +1 -1
- zenml/zen_server/dashboard/assets/{RunsBody-DxxtWVYz.js → RunsBody-Bnx2fxub.js} +1 -1
- zenml/zen_server/dashboard/assets/SearchField-Yjv-KRW4.js +1 -0
- zenml/zen_server/dashboard/assets/{SecretTooltip-CLzJIYW_.js → SecretTooltip-EKpMlG2f.js} +1 -1
- zenml/zen_server/dashboard/assets/{SetPassword-Yn50ooBC.js → SetPassword-CDLy57PZ.js} +1 -1
- zenml/zen_server/dashboard/assets/StackList-DKQaLDo4.js +1 -0
- zenml/zen_server/dashboard/assets/{Tabs-CNv-eTYM.js → Tabs-B5E-o_h6.js} +1 -1
- zenml/zen_server/dashboard/assets/{Tick-jEIevzVf.js → Tick-DSYBiuXU.js} +1 -1
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-C16GW-kX.js → UpdatePasswordSchemas-HBNOeyoP.js} +1 -1
- zenml/zen_server/dashboard/assets/{UsageReason-Bf2tzhv1.js → UsageReason-DXtPS5nE.js} +1 -1
- zenml/zen_server/dashboard/assets/{WizardFooter-D6i-AP1K.js → WizardFooter-_1VSMZ_c.js} +1 -1
- zenml/zen_server/dashboard/assets/{all-pipeline-runs-query-DUti43aF.js → all-pipeline-runs-query-D0qDLdKB.js} +1 -1
- zenml/zen_server/dashboard/assets/{create-stack-Ch2WPs9U.js → create-stack-7JzgAYAm.js} +1 -1
- zenml/zen_server/dashboard/assets/{delete-run-Byf9hTjA.js → delete-run-CUdtYFLl.js} +1 -1
- zenml/zen_server/dashboard/assets/{form-schemas-BZqKBPBF.js → form-schemas-B6PCV3Y4.js} +1 -1
- zenml/zen_server/dashboard/assets/index-B6U0OkEN.css +1 -0
- zenml/zen_server/dashboard/assets/{index-CyBKZcpO.js → index-CJ5IfeAl.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-CtdYkjUi.js → index-Ceyzb1yI.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-CE0aQlv8.js → index-CxO6541P.js} +3 -3
- zenml/zen_server/dashboard/assets/{index-v6gQjDEo.js → index-D4yoZ_gH.js} +1 -1
- zenml/zen_server/dashboard/assets/{login-mutation-DNDVp_2H.js → login-mutation-BaeJ7MAg.js} +1 -1
- zenml/zen_server/dashboard/assets/{not-found-Bmup4ctE.js → not-found-MGptrNBk.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DGlm1RVc.js → page-Aeu3v0MQ.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CltCNL0T.js → page-BCgEdmhP.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Hn8q9iJZ.js → page-BKwwfTNy.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BNxYrN0q.js → page-BUjw8Tp1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BYJfqgLN.js → page-BXgXP-Qj.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DN4BVIOL.js → page-BXrtxEbw.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CHRn1fQm.js → page-BaUDR9Ri.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DlIi5ThM.js → page-BbljjC-k.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BrmJp1Wt.js → page-BhOXn-s9.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Cc8ZEuj4.js → page-C37IDa-Q.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Dif8CWyZ.js → page-C4JpDeUM.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BC27C_OI.js → page-CB2_GdBA.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B5Sr8pib.js → page-CBiT2Ox9.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-IhckKFnD.js → page-CXPc-HN1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Dth9X1Ih.js → page-CbwI6emp.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DweqqCkF.js → page-CeNL9JWi.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-LyZ_l8vR.js → page-CkPwPmLZ.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-C70wZtV2.js → page-CmJU3Gqo.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-D9Oh05fl.js → page-CoFVtzhG.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-PamGpk0j.js → page-D-KPzeQb.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DoW7YxTu.js → page-DKQ3wZgr.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DWWhxCoF.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CmlYj7Nl.js → page-DbW8MfQ4.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CWr96ZKN.js → page-Dv5lN2w7.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-ANYGfEUL.js → page-Dvbq1BoF.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-D6Ev5P8V.js → page-DyAuja95.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DyOJ_pq3.js → page-DzrdL2v1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CXAbSyp9.js → page-I2B4Ocv8.js} +1 -1
- zenml/zen_server/dashboard/assets/page-OdjGauvw.js +2 -0
- zenml/zen_server/dashboard/assets/{page-CaeI9ptC.js → page-Ox-eC1ik.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B_0XkV48.js → page-khp8QJ6b.js} +1 -1
- zenml/zen_server/dashboard/assets/{page--XLMzHrn.js → page-yNh6PQKt.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-vP0-Xl4f.js → persist-DBTFy--v.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-DeXRG61d.js → persist-K7AY0ju4.js} +1 -1
- zenml/zen_server/dashboard/assets/{service-DH_oUqQj.js → service-BvOYLH5b.js} +1 -1
- zenml/zen_server/dashboard/assets/{sharedSchema-Bw1_Wa7l.js → sharedSchema-xJDsJNgJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{stack-detail-query-B_0R_fd6.js → stack-detail-query-DMJoxwgv.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-D9qYhfaN.js → update-server-settings-mutation-ATZDNNZk.js} +1 -1
- zenml/zen_server/dashboard/assets/{url-Dh93fvh0.js → url-BWJXzuI4.js} +1 -1
- zenml/zen_server/dashboard/index.html +4 -4
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_server/deploy/helm/templates/server-db-job.yaml +5 -3
- zenml/zen_server/deploy/helm/values.yaml +4 -0
- zenml/zen_server/routers/devices_endpoints.py +4 -2
- zenml/zen_server/routers/workspaces_endpoints.py +2 -0
- zenml/zen_stores/migrations/versions/0.73.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/25155145c545_separate_actions_and_triggers.py +2 -2
- zenml/zen_stores/migrations/versions/46506f72f0ed_add_server_settings.py +2 -2
- zenml/zen_stores/migrations/versions/5994f9ad0489_introduce_role_permissions.py +6 -6
- zenml/zen_stores/migrations/versions/7500f434b71c_remove_shared_columns.py +2 -2
- zenml/zen_stores/migrations/versions/a91762e6be36_artifact_version_table.py +3 -3
- zenml/zen_stores/schemas/action_schemas.py +2 -2
- zenml/zen_stores/schemas/api_key_schemas.py +4 -4
- zenml/zen_stores/schemas/artifact_schemas.py +3 -3
- zenml/zen_stores/schemas/base_schemas.py +7 -3
- zenml/zen_stores/schemas/code_repository_schemas.py +2 -2
- zenml/zen_stores/schemas/component_schemas.py +2 -2
- zenml/zen_stores/schemas/device_schemas.py +4 -4
- zenml/zen_stores/schemas/event_source_schemas.py +2 -2
- zenml/zen_stores/schemas/flavor_schemas.py +2 -2
- zenml/zen_stores/schemas/model_schemas.py +3 -3
- zenml/zen_stores/schemas/pipeline_run_schemas.py +10 -3
- zenml/zen_stores/schemas/pipeline_schemas.py +2 -2
- zenml/zen_stores/schemas/run_template_schemas.py +2 -2
- zenml/zen_stores/schemas/schedule_schema.py +19 -4
- zenml/zen_stores/schemas/secret_schemas.py +2 -2
- zenml/zen_stores/schemas/server_settings_schemas.py +9 -5
- zenml/zen_stores/schemas/service_connector_schemas.py +2 -2
- zenml/zen_stores/schemas/service_schemas.py +2 -2
- zenml/zen_stores/schemas/stack_schemas.py +2 -2
- zenml/zen_stores/schemas/step_run_schemas.py +2 -2
- zenml/zen_stores/schemas/tag_schemas.py +2 -2
- zenml/zen_stores/schemas/trigger_schemas.py +2 -2
- zenml/zen_stores/schemas/user_schemas.py +3 -3
- zenml/zen_stores/schemas/workspace_schemas.py +2 -2
- zenml/zen_stores/sql_zen_store.py +10 -1
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/METADATA +3 -3
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/RECORD +161 -157
- zenml/zen_server/dashboard/assets/Partials-CqZp5NMX.js +0 -1
- zenml/zen_server/dashboard/assets/SearchField-D6tPxyqw.js +0 -1
- zenml/zen_server/dashboard/assets/StackList-U537qoYd.js +0 -1
- zenml/zen_server/dashboard/assets/index-DXvT1_Um.css +0 -1
- zenml/zen_server/dashboard/assets/page-C2nU3Gxn.js +0 -1
- zenml/zen_server/dashboard/assets/page-PxOWfKgF.js +0 -2
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/entry_points.txt +0 -0
@@ -15,6 +15,7 @@
|
|
15
15
|
|
16
16
|
import os
|
17
17
|
import re
|
18
|
+
from datetime import datetime, timezone
|
18
19
|
from typing import (
|
19
20
|
TYPE_CHECKING,
|
20
21
|
Any,
|
@@ -35,14 +36,20 @@ from sagemaker.processing import ProcessingInput, ProcessingOutput
|
|
35
36
|
from sagemaker.workflow.execution_variables import ExecutionVariables
|
36
37
|
from sagemaker.workflow.pipeline import Pipeline
|
37
38
|
from sagemaker.workflow.steps import ProcessingStep, TrainingStep
|
39
|
+
from sagemaker.workflow.triggers import PipelineSchedule
|
38
40
|
|
41
|
+
from zenml.client import Client
|
39
42
|
from zenml.config.base_settings import BaseSettings
|
40
43
|
from zenml.constants import (
|
41
44
|
METADATA_ORCHESTRATOR_LOGS_URL,
|
42
45
|
METADATA_ORCHESTRATOR_RUN_ID,
|
43
46
|
METADATA_ORCHESTRATOR_URL,
|
44
47
|
)
|
45
|
-
from zenml.enums import
|
48
|
+
from zenml.enums import (
|
49
|
+
ExecutionStatus,
|
50
|
+
MetadataResourceTypes,
|
51
|
+
StackComponentType,
|
52
|
+
)
|
46
53
|
from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import (
|
47
54
|
SagemakerOrchestratorConfig,
|
48
55
|
SagemakerOrchestratorSettings,
|
@@ -69,6 +76,36 @@ POLLING_DELAY = 30
|
|
69
76
|
logger = get_logger(__name__)
|
70
77
|
|
71
78
|
|
79
|
+
def dissect_schedule_arn(
|
80
|
+
schedule_arn: str,
|
81
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
82
|
+
"""Extracts the region and the name from an EventBridge schedule ARN.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
schedule_arn: The ARN of the EventBridge schedule.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
Region Name, Schedule Name (including the group name)
|
89
|
+
|
90
|
+
Raises:
|
91
|
+
ValueError: If the input is not a properly formatted ARN.
|
92
|
+
"""
|
93
|
+
# Split the ARN into parts
|
94
|
+
arn_parts = schedule_arn.split(":")
|
95
|
+
|
96
|
+
# Validate ARN structure
|
97
|
+
if len(arn_parts) < 6 or not arn_parts[5].startswith("schedule/"):
|
98
|
+
raise ValueError("Invalid EventBridge schedule ARN format.")
|
99
|
+
|
100
|
+
# Extract the region
|
101
|
+
region = arn_parts[3]
|
102
|
+
|
103
|
+
# Extract the group name and schedule name
|
104
|
+
name = arn_parts[5].split("schedule/")[1]
|
105
|
+
|
106
|
+
return region, name
|
107
|
+
|
108
|
+
|
72
109
|
def dissect_pipeline_execution_arn(
|
73
110
|
pipeline_execution_arn: str,
|
74
111
|
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
@@ -237,21 +274,15 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
237
274
|
environment.
|
238
275
|
|
239
276
|
Raises:
|
240
|
-
RuntimeError: If
|
241
|
-
|
277
|
+
RuntimeError: If there is an error creating or scheduling the
|
278
|
+
pipeline.
|
242
279
|
TypeError: If the network_config passed is not compatible with the
|
243
280
|
AWS SageMaker NetworkConfig class.
|
281
|
+
ValueError: If the schedule is not valid.
|
244
282
|
|
245
283
|
Yields:
|
246
284
|
A dictionary of metadata related to the pipeline run.
|
247
285
|
"""
|
248
|
-
if deployment.schedule:
|
249
|
-
logger.warning(
|
250
|
-
"The Sagemaker Orchestrator currently does not support the "
|
251
|
-
"use of schedules. The `schedule` will be ignored "
|
252
|
-
"and the pipeline will be run immediately."
|
253
|
-
)
|
254
|
-
|
255
286
|
# sagemaker requires pipelineName to use alphanum and hyphens only
|
256
287
|
unsanitized_orchestrator_run_name = get_orchestrator_run_name(
|
257
288
|
pipeline_name=deployment.pipeline_configuration.name
|
@@ -459,7 +490,7 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
459
490
|
|
460
491
|
sagemaker_steps.append(sagemaker_step)
|
461
492
|
|
462
|
-
#
|
493
|
+
# Create the pipeline
|
463
494
|
pipeline = Pipeline(
|
464
495
|
name=orchestrator_run_name,
|
465
496
|
steps=sagemaker_steps,
|
@@ -479,39 +510,207 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
479
510
|
if settings.pipeline_tags
|
480
511
|
else None,
|
481
512
|
)
|
482
|
-
execution = pipeline.start()
|
483
|
-
logger.warning(
|
484
|
-
"Steps can take 5-15 minutes to start running "
|
485
|
-
"when using the Sagemaker Orchestrator."
|
486
|
-
)
|
487
513
|
|
488
|
-
#
|
489
|
-
|
490
|
-
|
491
|
-
|
514
|
+
# Handle scheduling if specified
|
515
|
+
if deployment.schedule:
|
516
|
+
if settings.synchronous:
|
517
|
+
logger.warning(
|
518
|
+
"The 'synchronous' setting is ignored for scheduled "
|
519
|
+
"pipelines since they run independently of the "
|
520
|
+
"deployment process."
|
521
|
+
)
|
492
522
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
523
|
+
schedule_name = orchestrator_run_name
|
524
|
+
next_execution = None
|
525
|
+
|
526
|
+
# Create PipelineSchedule based on schedule type
|
527
|
+
if deployment.schedule.cron_expression:
|
528
|
+
cron_exp = self._validate_cron_expression(
|
529
|
+
deployment.schedule.cron_expression
|
530
|
+
)
|
531
|
+
schedule = PipelineSchedule(
|
532
|
+
name=schedule_name,
|
533
|
+
cron=cron_exp,
|
534
|
+
start_date=deployment.schedule.start_time,
|
535
|
+
enabled=True,
|
536
|
+
)
|
537
|
+
elif deployment.schedule.interval_second:
|
538
|
+
# This is necessary because SageMaker's PipelineSchedule rate
|
539
|
+
# expressions require minutes as the minimum time unit.
|
540
|
+
# Even if a user specifies an interval of less than 60 seconds,
|
541
|
+
# it will be rounded up to 1 minute.
|
542
|
+
minutes = max(
|
543
|
+
1,
|
544
|
+
int(
|
545
|
+
deployment.schedule.interval_second.total_seconds()
|
546
|
+
/ 60
|
547
|
+
),
|
548
|
+
)
|
549
|
+
schedule = PipelineSchedule(
|
550
|
+
name=schedule_name,
|
551
|
+
rate=(minutes, "minutes"),
|
552
|
+
start_date=deployment.schedule.start_time,
|
553
|
+
enabled=True,
|
554
|
+
)
|
555
|
+
next_execution = (
|
556
|
+
deployment.schedule.start_time
|
557
|
+
or datetime.now(timezone.utc)
|
558
|
+
) + deployment.schedule.interval_second
|
559
|
+
else:
|
560
|
+
# One-time schedule
|
561
|
+
execution_time = (
|
562
|
+
deployment.schedule.run_once_start_time
|
563
|
+
or deployment.schedule.start_time
|
564
|
+
)
|
565
|
+
if not execution_time:
|
566
|
+
raise ValueError(
|
567
|
+
"A start time must be specified for one-time "
|
568
|
+
"schedule execution"
|
569
|
+
)
|
570
|
+
schedule = PipelineSchedule(
|
571
|
+
name=schedule_name,
|
572
|
+
at=execution_time.astimezone(timezone.utc),
|
573
|
+
enabled=True,
|
574
|
+
)
|
575
|
+
next_execution = execution_time
|
576
|
+
|
577
|
+
# Get the current role ARN if not explicitly configured
|
578
|
+
if self.config.scheduler_role is None:
|
579
|
+
logger.info(
|
580
|
+
"No scheduler_role configured. Trying to extract it from "
|
581
|
+
"the client side authentication."
|
582
|
+
)
|
583
|
+
sts = session.boto_session.client("sts")
|
584
|
+
try:
|
585
|
+
scheduler_role_arn = sts.get_caller_identity()["Arn"]
|
586
|
+
# If this is a user ARN, try to get the role ARN
|
587
|
+
if ":user/" in scheduler_role_arn:
|
588
|
+
logger.warning(
|
589
|
+
f"Using IAM user credentials "
|
590
|
+
f"({scheduler_role_arn}). For production "
|
591
|
+
"environments, it's recommended to use IAM roles "
|
592
|
+
"instead."
|
593
|
+
)
|
594
|
+
# If this is an assumed role, extract the role ARN
|
595
|
+
elif ":assumed-role/" in scheduler_role_arn:
|
596
|
+
# Convert assumed-role ARN format to role ARN format
|
597
|
+
# From: arn:aws:sts::123456789012:assumed-role/role-name/session-name
|
598
|
+
# To: arn:aws:iam::123456789012:role/role-name
|
599
|
+
scheduler_role_arn = re.sub(
|
600
|
+
r"arn:aws:sts::(\d+):assumed-role/([^/]+)/.*",
|
601
|
+
r"arn:aws:iam::\1:role/\2",
|
602
|
+
scheduler_role_arn,
|
603
|
+
)
|
604
|
+
elif ":role/" not in scheduler_role_arn:
|
605
|
+
raise RuntimeError(
|
606
|
+
f"Unexpected credential type "
|
607
|
+
f"({scheduler_role_arn}). Please use IAM "
|
608
|
+
f"roles for SageMaker pipeline scheduling."
|
609
|
+
)
|
610
|
+
else:
|
611
|
+
raise RuntimeError(
|
612
|
+
"The ARN of the caller identity "
|
613
|
+
f"`{scheduler_role_arn}` does not "
|
614
|
+
"include a user or a proper role."
|
615
|
+
)
|
616
|
+
except Exception:
|
617
|
+
raise RuntimeError(
|
618
|
+
"Failed to get current role ARN. This means the "
|
619
|
+
"your client side credentials that you are "
|
620
|
+
"is not configured correctly to schedule sagemaker "
|
621
|
+
"pipelines. For more information, please check:"
|
622
|
+
"https://docs.zenml.io/stack-components/orchestrators/sagemaker#required-iam-permissions-for-schedules"
|
623
|
+
)
|
624
|
+
else:
|
625
|
+
scheduler_role_arn = self.config.scheduler_role
|
626
|
+
|
627
|
+
# Attach schedule to pipeline
|
628
|
+
triggers = pipeline.put_triggers(
|
629
|
+
triggers=[schedule],
|
630
|
+
role_arn=scheduler_role_arn,
|
499
631
|
)
|
632
|
+
logger.info(f"The schedule ARN is: {triggers[0]}")
|
633
|
+
|
500
634
|
try:
|
501
|
-
|
502
|
-
|
635
|
+
from zenml.models import RunMetadataResource
|
636
|
+
|
637
|
+
schedule_metadata = self.generate_schedule_metadata(
|
638
|
+
schedule_arn=triggers[0]
|
503
639
|
)
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
640
|
+
|
641
|
+
Client().create_run_metadata(
|
642
|
+
metadata=schedule_metadata, # type: ignore[arg-type]
|
643
|
+
resources=[
|
644
|
+
RunMetadataResource(
|
645
|
+
id=deployment.schedule.id,
|
646
|
+
type=MetadataResourceTypes.SCHEDULE,
|
647
|
+
)
|
648
|
+
],
|
649
|
+
)
|
650
|
+
except Exception as e:
|
651
|
+
logger.debug(
|
652
|
+
"There was an error attaching metadata to the "
|
653
|
+
f"schedule: {e}"
|
513
654
|
)
|
514
655
|
|
656
|
+
logger.info(
|
657
|
+
f"Successfully scheduled pipeline with name: {schedule_name}\n"
|
658
|
+
+ (
|
659
|
+
f"First execution will occur at: "
|
660
|
+
f"{next_execution.strftime('%Y-%m-%d %H:%M:%S UTC')}"
|
661
|
+
if next_execution
|
662
|
+
else f"Using cron expression: "
|
663
|
+
f"{deployment.schedule.cron_expression}"
|
664
|
+
)
|
665
|
+
+ (
|
666
|
+
f" (and every {minutes} minutes after)"
|
667
|
+
if deployment.schedule.interval_second
|
668
|
+
else ""
|
669
|
+
)
|
670
|
+
)
|
671
|
+
logger.info(
|
672
|
+
"\n\nIn order to cancel the schedule, you can use execute "
|
673
|
+
"the following command:\n"
|
674
|
+
)
|
675
|
+
logger.info(
|
676
|
+
f"`aws scheduler delete-schedule --name {schedule_name}`"
|
677
|
+
)
|
678
|
+
else:
|
679
|
+
# Execute the pipeline immediately if no schedule is specified
|
680
|
+
execution = pipeline.start()
|
681
|
+
logger.warning(
|
682
|
+
"Steps can take 5-15 minutes to start running "
|
683
|
+
"when using the Sagemaker Orchestrator."
|
684
|
+
)
|
685
|
+
|
686
|
+
# Yield metadata based on the generated execution object
|
687
|
+
yield from self.compute_metadata(
|
688
|
+
execution_arn=execution.arn, settings=settings
|
689
|
+
)
|
690
|
+
|
691
|
+
# mainly for testing purposes, we wait for the pipeline to finish
|
692
|
+
if settings.synchronous:
|
693
|
+
logger.info(
|
694
|
+
"Executing synchronously. Waiting for pipeline to "
|
695
|
+
"finish... \n"
|
696
|
+
"At this point you can `Ctrl-C` out without cancelling the "
|
697
|
+
"execution."
|
698
|
+
)
|
699
|
+
try:
|
700
|
+
execution.wait(
|
701
|
+
delay=POLLING_DELAY, max_attempts=MAX_POLLING_ATTEMPTS
|
702
|
+
)
|
703
|
+
logger.info("Pipeline completed successfully.")
|
704
|
+
except WaiterError:
|
705
|
+
raise RuntimeError(
|
706
|
+
"Timed out while waiting for pipeline execution to "
|
707
|
+
"finish. For long-running pipelines we recommend "
|
708
|
+
"configuring your orchestrator for asynchronous "
|
709
|
+
"execution. The following command does this for you: \n"
|
710
|
+
f"`zenml orchestrator update {self.name} "
|
711
|
+
f"--synchronous=False`"
|
712
|
+
)
|
713
|
+
|
515
714
|
def get_pipeline_run_metadata(
|
516
715
|
self, run_id: UUID
|
517
716
|
) -> Dict[str, "MetadataType"]:
|
@@ -523,10 +722,22 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
523
722
|
Returns:
|
524
723
|
A dictionary of metadata.
|
525
724
|
"""
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
725
|
+
from zenml import get_step_context
|
726
|
+
|
727
|
+
execution_arn = os.environ[ENV_ZENML_SAGEMAKER_RUN_ID]
|
728
|
+
|
729
|
+
run_metadata: Dict[str, "MetadataType"] = {}
|
730
|
+
|
731
|
+
settings = cast(
|
732
|
+
SagemakerOrchestratorSettings,
|
733
|
+
self.get_settings(get_step_context().pipeline_run),
|
734
|
+
)
|
735
|
+
|
736
|
+
for metadata in self.compute_metadata(
|
737
|
+
execution_arn=execution_arn,
|
738
|
+
settings=settings,
|
739
|
+
):
|
740
|
+
run_metadata.update(metadata)
|
530
741
|
|
531
742
|
return run_metadata
|
532
743
|
|
@@ -588,56 +799,57 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
588
799
|
|
589
800
|
def compute_metadata(
|
590
801
|
self,
|
591
|
-
|
802
|
+
execution_arn: str,
|
592
803
|
settings: SagemakerOrchestratorSettings,
|
593
804
|
) -> Iterator[Dict[str, MetadataType]]:
|
594
805
|
"""Generate run metadata based on the generated Sagemaker Execution.
|
595
806
|
|
596
807
|
Args:
|
597
|
-
|
808
|
+
execution_arn: The ARN of the pipeline execution.
|
598
809
|
settings: The Sagemaker orchestrator settings.
|
599
810
|
|
600
811
|
Yields:
|
601
812
|
A dictionary of metadata related to the pipeline run.
|
602
813
|
"""
|
603
|
-
# Metadata
|
604
|
-
metadata: Dict[str, MetadataType] = {}
|
605
|
-
|
606
814
|
# Orchestrator Run ID
|
607
|
-
|
608
|
-
|
815
|
+
metadata: Dict[str, MetadataType] = {
|
816
|
+
"pipeline_execution_arn": execution_arn,
|
817
|
+
METADATA_ORCHESTRATOR_RUN_ID: execution_arn,
|
818
|
+
}
|
609
819
|
|
610
820
|
# URL to the Sagemaker's pipeline view
|
611
|
-
if orchestrator_url := self._compute_orchestrator_url(
|
821
|
+
if orchestrator_url := self._compute_orchestrator_url(
|
822
|
+
execution_arn=execution_arn
|
823
|
+
):
|
612
824
|
metadata[METADATA_ORCHESTRATOR_URL] = Uri(orchestrator_url)
|
613
825
|
|
614
826
|
# URL to the corresponding CloudWatch page
|
615
827
|
if logs_url := self._compute_orchestrator_logs_url(
|
616
|
-
|
828
|
+
execution_arn=execution_arn, settings=settings
|
617
829
|
):
|
618
830
|
metadata[METADATA_ORCHESTRATOR_LOGS_URL] = Uri(logs_url)
|
619
831
|
|
620
832
|
yield metadata
|
621
833
|
|
622
|
-
@staticmethod
|
623
834
|
def _compute_orchestrator_url(
|
624
|
-
|
835
|
+
self,
|
836
|
+
execution_arn: Any,
|
625
837
|
) -> Optional[str]:
|
626
838
|
"""Generate the Orchestrator Dashboard URL upon pipeline execution.
|
627
839
|
|
628
840
|
Args:
|
629
|
-
|
841
|
+
execution_arn: The ARN of the pipeline execution.
|
630
842
|
|
631
843
|
Returns:
|
632
844
|
the URL to the dashboard view in SageMaker.
|
633
845
|
"""
|
634
846
|
try:
|
635
847
|
region_name, pipeline_name, execution_id = (
|
636
|
-
dissect_pipeline_execution_arn(
|
848
|
+
dissect_pipeline_execution_arn(execution_arn)
|
637
849
|
)
|
638
850
|
|
639
851
|
# Get the Sagemaker session
|
640
|
-
session =
|
852
|
+
session = self._get_sagemaker_session()
|
641
853
|
|
642
854
|
# List the Studio domains and get the Studio Domain ID
|
643
855
|
domains_response = session.sagemaker_client.list_domains()
|
@@ -657,13 +869,13 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
657
869
|
|
658
870
|
@staticmethod
|
659
871
|
def _compute_orchestrator_logs_url(
|
660
|
-
|
872
|
+
execution_arn: Any,
|
661
873
|
settings: SagemakerOrchestratorSettings,
|
662
874
|
) -> Optional[str]:
|
663
875
|
"""Generate the CloudWatch URL upon pipeline execution.
|
664
876
|
|
665
877
|
Args:
|
666
|
-
|
878
|
+
execution_arn: The ARN of the pipeline execution.
|
667
879
|
settings: The Sagemaker orchestrator settings.
|
668
880
|
|
669
881
|
Returns:
|
@@ -671,7 +883,7 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
671
883
|
"""
|
672
884
|
try:
|
673
885
|
region_name, _, execution_id = dissect_pipeline_execution_arn(
|
674
|
-
|
886
|
+
execution_arn
|
675
887
|
)
|
676
888
|
|
677
889
|
use_training_jobs = True
|
@@ -693,22 +905,48 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
|
|
693
905
|
return None
|
694
906
|
|
695
907
|
@staticmethod
|
696
|
-
def
|
697
|
-
|
698
|
-
) -> Optional[str]:
|
699
|
-
"""Fetch the Orchestrator Run ID upon pipeline execution.
|
908
|
+
def generate_schedule_metadata(schedule_arn: str) -> Dict[str, str]:
|
909
|
+
"""Attaches metadata to the ZenML Schedules.
|
700
910
|
|
701
911
|
Args:
|
702
|
-
|
912
|
+
schedule_arn: The trigger ARNs that is generated on the AWS side.
|
703
913
|
|
704
914
|
Returns:
|
705
|
-
|
915
|
+
a dictionary containing metadata related to the schedule.
|
706
916
|
"""
|
707
|
-
|
708
|
-
return str(pipeline_execution.arn)
|
917
|
+
region, name = dissect_schedule_arn(schedule_arn=schedule_arn)
|
709
918
|
|
710
|
-
|
711
|
-
|
712
|
-
f"
|
919
|
+
return {
|
920
|
+
"trigger_url": (
|
921
|
+
f"https://{region}.console.aws.amazon.com/scheduler/home"
|
922
|
+
f"?region={region}#schedules/{name}"
|
923
|
+
),
|
924
|
+
}
|
925
|
+
|
926
|
+
@staticmethod
|
927
|
+
def _validate_cron_expression(cron_expression: str) -> str:
|
928
|
+
"""Validates and formats a cron expression for SageMaker schedules.
|
929
|
+
|
930
|
+
Args:
|
931
|
+
cron_expression: The cron expression to validate
|
932
|
+
|
933
|
+
Returns:
|
934
|
+
The formatted cron expression
|
935
|
+
|
936
|
+
Raises:
|
937
|
+
ValueError: If the cron expression is invalid
|
938
|
+
"""
|
939
|
+
# Strip any "cron(" prefix if it exists
|
940
|
+
cron_exp = cron_expression.replace("cron(", "").replace(")", "")
|
941
|
+
|
942
|
+
# Split into components
|
943
|
+
parts = cron_exp.split()
|
944
|
+
if len(parts) not in [6, 7]: # AWS cron requires 6 or 7 fields
|
945
|
+
raise ValueError(
|
946
|
+
f"Invalid cron expression: {cron_expression}. AWS cron "
|
947
|
+
"expressions must have 6 or 7 fields: minute hour day-of-month "
|
948
|
+
"month day-of-week year(optional). Example: '15 10 ? * 6L "
|
949
|
+
"2022-2023'"
|
713
950
|
)
|
714
|
-
|
951
|
+
|
952
|
+
return cron_exp
|
@@ -30,6 +30,7 @@ from zenml.stack import Flavor
|
|
30
30
|
|
31
31
|
GCP_ARTIFACT_STORE_FLAVOR = "gcp"
|
32
32
|
GCP_IMAGE_BUILDER_FLAVOR = "gcp"
|
33
|
+
GCP_VERTEX_EXPERIMENT_TRACKER_FLAVOR = "vertex"
|
33
34
|
GCP_VERTEX_ORCHESTRATOR_FLAVOR = "vertex"
|
34
35
|
GCP_VERTEX_STEP_OPERATOR_FLAVOR = "vertex"
|
35
36
|
|
@@ -70,6 +71,7 @@ class GcpIntegration(Integration):
|
|
70
71
|
from zenml.integrations.gcp.flavors import (
|
71
72
|
GCPArtifactStoreFlavor,
|
72
73
|
GCPImageBuilderFlavor,
|
74
|
+
VertexExperimentTrackerFlavor,
|
73
75
|
VertexOrchestratorFlavor,
|
74
76
|
VertexStepOperatorFlavor,
|
75
77
|
)
|
@@ -77,6 +79,7 @@ class GcpIntegration(Integration):
|
|
77
79
|
return [
|
78
80
|
GCPArtifactStoreFlavor,
|
79
81
|
GCPImageBuilderFlavor,
|
82
|
+
VertexExperimentTrackerFlavor,
|
80
83
|
VertexOrchestratorFlavor,
|
81
84
|
VertexStepOperatorFlavor,
|
82
85
|
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at:
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
10
|
+
# or implied. See the License for the specific language governing
|
11
|
+
# permissions and limitations under the License.
|
12
|
+
"""Initialization for the VertexAI experiment tracker."""
|
13
|
+
|
14
|
+
from zenml.integrations.gcp.experiment_trackers.vertex_experiment_tracker import ( # noqa
|
15
|
+
VertexExperimentTracker,
|
16
|
+
)
|
17
|
+
|
18
|
+
__all__ = ["VertexExperimentTracker"]
|