zenml-nightly 0.58.2.dev20240626__py3-none-any.whl → 0.62.0.dev20240726__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.
- README.md +31 -10
- RELEASE_NOTES.md +280 -0
- zenml/VERSION +1 -1
- zenml/__init__.py +2 -0
- zenml/analytics/enums.py +3 -0
- zenml/cli/__init__.py +28 -0
- zenml/cli/artifact.py +1 -2
- zenml/cli/integration.py +9 -8
- zenml/cli/server.py +6 -0
- zenml/cli/stack.py +812 -39
- zenml/cli/stack_components.py +9 -0
- zenml/cli/text_utils.py +35 -1
- zenml/cli/utils.py +127 -10
- zenml/client.py +23 -14
- zenml/config/docker_settings.py +8 -5
- zenml/constants.py +13 -1
- zenml/container_registries/base_container_registry.py +1 -0
- zenml/enums.py +23 -0
- zenml/event_hub/event_hub.py +5 -8
- zenml/integrations/__init__.py +1 -0
- zenml/integrations/aws/__init__.py +1 -0
- zenml/integrations/azure/__init__.py +3 -2
- zenml/integrations/constants.py +1 -0
- zenml/integrations/databricks/__init__.py +52 -0
- zenml/integrations/databricks/flavors/__init__.py +30 -0
- zenml/integrations/databricks/flavors/databricks_model_deployer_flavor.py +118 -0
- zenml/integrations/databricks/flavors/databricks_orchestrator_flavor.py +147 -0
- zenml/integrations/databricks/model_deployers/__init__.py +20 -0
- zenml/integrations/databricks/model_deployers/databricks_model_deployer.py +249 -0
- zenml/integrations/databricks/orchestrators/__init__.py +20 -0
- zenml/integrations/databricks/orchestrators/databricks_orchestrator.py +497 -0
- zenml/integrations/databricks/orchestrators/databricks_orchestrator_entrypoint_config.py +97 -0
- zenml/integrations/databricks/services/__init__.py +19 -0
- zenml/integrations/databricks/services/databricks_deployment.py +407 -0
- zenml/integrations/databricks/utils/__init__.py +14 -0
- zenml/integrations/databricks/utils/databricks_utils.py +87 -0
- zenml/integrations/deepchecks/__init__.py +1 -0
- zenml/integrations/discord/__init__.py +1 -0
- zenml/integrations/evidently/__init__.py +1 -0
- zenml/integrations/facets/__init__.py +1 -0
- zenml/integrations/feast/__init__.py +1 -0
- zenml/integrations/gcp/__init__.py +3 -1
- zenml/integrations/gcp/google_credentials_mixin.py +1 -1
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py +320 -64
- zenml/integrations/great_expectations/data_validators/ge_data_validator.py +12 -8
- zenml/integrations/huggingface/__init__.py +1 -0
- zenml/integrations/huggingface/materializers/huggingface_datasets_materializer.py +88 -3
- zenml/integrations/huggingface/steps/accelerate_runner.py +1 -7
- zenml/integrations/integration.py +24 -0
- zenml/integrations/kubeflow/__init__.py +3 -0
- zenml/integrations/kubeflow/flavors/kubeflow_orchestrator_flavor.py +1 -1
- zenml/integrations/kubeflow/orchestrators/kubeflow_orchestrator.py +0 -1
- zenml/integrations/kubernetes/__init__.py +3 -1
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +4 -1
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +1 -13
- zenml/integrations/kubernetes/orchestrators/manifest_utils.py +22 -4
- zenml/integrations/kubernetes/pod_settings.py +4 -0
- zenml/integrations/label_studio/annotators/label_studio_annotator.py +1 -0
- zenml/integrations/langchain/__init__.py +1 -0
- zenml/integrations/lightgbm/__init__.py +1 -0
- zenml/integrations/mlflow/__init__.py +4 -2
- zenml/integrations/mlflow/model_registries/mlflow_model_registry.py +6 -2
- zenml/integrations/mlflow/services/mlflow_deployment.py +1 -1
- zenml/integrations/neural_prophet/__init__.py +1 -0
- zenml/integrations/polars/__init__.py +1 -0
- zenml/integrations/prodigy/__init__.py +1 -0
- zenml/integrations/pycaret/__init__.py +6 -0
- zenml/integrations/registry.py +37 -0
- zenml/integrations/s3/artifact_stores/s3_artifact_store.py +17 -6
- zenml/integrations/seldon/__init__.py +1 -0
- zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -0
- zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +2 -2
- zenml/integrations/skypilot/orchestrators/skypilot_base_vm_orchestrator.py +1 -1
- zenml/integrations/skypilot/orchestrators/skypilot_orchestrator_entrypoint.py +2 -2
- zenml/integrations/skypilot_aws/__init__.py +2 -1
- zenml/integrations/skypilot_azure/__init__.py +1 -3
- zenml/integrations/skypilot_gcp/__init__.py +1 -1
- zenml/integrations/skypilot_lambda/__init__.py +1 -1
- zenml/integrations/skypilot_lambda/flavors/skypilot_orchestrator_lambda_vm_flavor.py +1 -1
- zenml/integrations/slack/__init__.py +1 -0
- zenml/integrations/tekton/__init__.py +1 -0
- zenml/integrations/tensorboard/__init__.py +0 -1
- zenml/integrations/tensorflow/__init__.py +18 -6
- zenml/integrations/wandb/__init__.py +1 -0
- zenml/logging/step_logging.py +34 -35
- zenml/materializers/built_in_materializer.py +1 -1
- zenml/materializers/cloudpickle_materializer.py +1 -1
- zenml/model/model.py +1 -1
- zenml/models/__init__.py +11 -0
- zenml/models/v2/core/component.py +47 -0
- zenml/models/v2/core/model.py +1 -2
- zenml/models/v2/core/server_settings.py +0 -20
- zenml/models/v2/core/service_connector.py +17 -0
- zenml/models/v2/core/stack.py +31 -0
- zenml/models/v2/misc/full_stack.py +129 -0
- zenml/models/v2/misc/stack_deployment.py +91 -0
- zenml/new/pipelines/pipeline.py +1 -1
- zenml/new/pipelines/run_utils.py +1 -1
- zenml/orchestrators/__init__.py +4 -0
- zenml/orchestrators/input_utils.py +3 -6
- zenml/orchestrators/step_launcher.py +1 -0
- zenml/orchestrators/wheeled_orchestrator.py +147 -0
- zenml/service_connectors/service_connector_utils.py +408 -0
- zenml/stack/stack.py +3 -6
- zenml/stack_deployments/__init__.py +14 -0
- zenml/stack_deployments/aws_stack_deployment.py +254 -0
- zenml/stack_deployments/azure_stack_deployment.py +179 -0
- zenml/stack_deployments/gcp_stack_deployment.py +269 -0
- zenml/stack_deployments/stack_deployment.py +218 -0
- zenml/stack_deployments/utils.py +48 -0
- zenml/steps/base_step.py +7 -5
- zenml/utils/function_utils.py +2 -2
- zenml/utils/pagination_utils.py +7 -5
- zenml/utils/pipeline_docker_image_builder.py +105 -68
- zenml/utils/pydantic_utils.py +6 -5
- zenml/utils/source_utils.py +4 -1
- zenml/zen_server/cloud_utils.py +18 -3
- zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-B_YdvmwS.js} +1 -1
- zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +85 -0
- zenml/zen_server/dashboard/assets/{@react-router-DYovave8.js → @react-router-CO-OsFwI.js} +2 -2
- zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-l_1hUr1S.js} +2 -2
- zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
- zenml/zen_server/dashboard/assets/AwarenessChannel-CFg5iX4Z.js +1 -0
- zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-Dvkx_82E.js} +2 -2
- zenml/zen_server/dashboard/assets/CollapsibleCard-opiuBHHc.js +1 -0
- zenml/zen_server/dashboard/assets/Commands-DoN1xrEq.js +1 -0
- zenml/zen_server/dashboard/assets/CopyButton-Cr7xYEPb.js +2 -0
- zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-Ck-nZ43m.js} +7 -7
- zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +1 -0
- zenml/zen_server/dashboard/assets/EmptyState-BMLnFVlB.js +1 -0
- zenml/zen_server/dashboard/assets/Error-kLtljEOM.js +1 -0
- zenml/zen_server/dashboard/assets/ExecutionStatus-DguLLgTK.js +1 -0
- zenml/zen_server/dashboard/assets/Helpbox-BXUMP21n.js +1 -0
- zenml/zen_server/dashboard/assets/Infobox-DSt0O-dm.js +1 -0
- zenml/zen_server/dashboard/assets/InlineAvatar-xsrsIGE-.js +1 -0
- zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
- zenml/zen_server/dashboard/assets/Pagination-C6X-mifw.js +1 -0
- zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
- zenml/zen_server/dashboard/assets/SetPassword-BXGTWiwj.js +1 -0
- zenml/zen_server/dashboard/assets/SuccessStep-DZC60t0x.js +1 -0
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DGvwFWO1.js} +1 -1
- zenml/zen_server/dashboard/assets/{aws-t0gKCj_R.js → aws-BgKTfTfx.js} +1 -1
- zenml/zen_server/dashboard/assets/{check-circle-BVvhm5dy.js → check-circle-i56092KI.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-CZBOf6JM.js} +1 -1
- zenml/zen_server/dashboard/assets/cloud-only-C_yFCAkP.js +1 -0
- zenml/zen_server/dashboard/assets/{copy-BRhQz3j-.js → copy-BXNk6BjL.js} +1 -1
- zenml/zen_server/dashboard/assets/{database-CRRnyFWh.js → database-1xWSgZfO.js} +1 -1
- zenml/zen_server/dashboard/assets/{docker-BAonhm6G.js → docker-CQMVm_4d.js} +1 -1
- zenml/zen_server/dashboard/assets/{file-text-CbVERUON.js → file-text-CqD_iu6l.js} +1 -1
- zenml/zen_server/dashboard/assets/{help-B8rqCvqn.js → help-bu_DgLKI.js} +1 -1
- zenml/zen_server/dashboard/assets/index-BczVOqUf.js +55 -0
- zenml/zen_server/dashboard/assets/index-EpMIKgrI.css +1 -0
- zenml/zen_server/dashboard/assets/index-rK_Wuy2W.js +1 -0
- zenml/zen_server/dashboard/assets/index.esm-Corw4lXQ.js +1 -0
- zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-CrHrndTI.js} +1 -1
- zenml/zen_server/dashboard/assets/logs-D8k8BVFf.js +1 -0
- zenml/zen_server/dashboard/assets/not-found-DYa4pC-C.js +1 -0
- zenml/zen_server/dashboard/assets/package-B3fWP-Dh.js +1 -0
- zenml/zen_server/dashboard/assets/page-1h_sD1jz.js +1 -0
- zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-1iL8aMqs.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-2grKx_MY.js} +1 -1
- zenml/zen_server/dashboard/assets/page-5NCOHOsy.js +1 -0
- zenml/zen_server/dashboard/assets/page-8a4UMKXZ.js +1 -0
- zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-B6h3iaHJ.js} +1 -1
- zenml/zen_server/dashboard/assets/page-BDns21Iz.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-BhgCDInH.js} +2 -2
- zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-Bi-wtWiO.js} +2 -2
- zenml/zen_server/dashboard/assets/page-BkeAAYwp.js +1 -0
- zenml/zen_server/dashboard/assets/page-BkuQDIf-.js +1 -0
- zenml/zen_server/dashboard/assets/page-BnaevhnB.js +1 -0
- zenml/zen_server/dashboard/assets/page-Bq0YxkLV.js +1 -0
- zenml/zen_server/dashboard/assets/page-Bs2F4eoD.js +2 -0
- zenml/zen_server/dashboard/assets/page-C6-UGEbH.js +1 -0
- zenml/zen_server/dashboard/assets/page-CCNRIt_f.js +1 -0
- zenml/zen_server/dashboard/assets/page-CHNxpz3n.js +1 -0
- zenml/zen_server/dashboard/assets/page-DgorQFqi.js +1 -0
- zenml/zen_server/dashboard/assets/page-K8ebxVIs.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-MFQyIJd3.js} +1 -1
- zenml/zen_server/dashboard/assets/page-TgCF0P_U.js +1 -0
- zenml/zen_server/dashboard/assets/page-ZnCEe-eK.js +9 -0
- zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-uA5prJGY.js} +1 -1
- zenml/zen_server/dashboard/assets/persist-D7HJNBWx.js +1 -0
- zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
- zenml/zen_server/dashboard/assets/plus-C8WOyCzt.js +1 -0
- zenml/zen_server/dashboard/assets/stack-detail-query-Cficsl6d.js +1 -0
- zenml/zen_server/dashboard/assets/{terminal-B2ovgWuz.js → terminal-By9cErXc.js} +1 -1
- zenml/zen_server/dashboard/assets/update-server-settings-mutation-7d8xi1tS.js +1 -0
- zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-D7mAQGUM.js} +1 -1
- zenml/zen_server/dashboard/assets/{zod-DrZvVLjd.js → zod-BhoGpZ63.js} +1 -1
- zenml/zen_server/dashboard/index.html +7 -7
- zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
- zenml/zen_server/dashboard_legacy/index.html +1 -1
- zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.12246c7548e71e2c4438e496360de80c.js} +4 -4
- zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
- zenml/zen_server/dashboard_legacy/static/js/main.3b27024b.chunk.js +2 -0
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.3b27024b.chunk.js.map} +1 -1
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +11 -5
- zenml/zen_server/pipeline_deployment/utils.py +57 -44
- zenml/zen_server/rbac/utils.py +10 -2
- zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
- zenml/zen_server/routers/devices_endpoints.py +4 -1
- zenml/zen_server/routers/server_endpoints.py +29 -2
- zenml/zen_server/routers/service_connectors_endpoints.py +57 -0
- zenml/zen_server/routers/stack_deployment_endpoints.py +158 -0
- zenml/zen_server/routers/steps_endpoints.py +2 -1
- zenml/zen_server/routers/workspaces_endpoints.py +64 -0
- zenml/zen_server/zen_server_api.py +2 -0
- zenml/zen_stores/migrations/utils.py +1 -1
- zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/0.61.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/0.62.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/0d707865f404_adding_labels_to_stacks.py +30 -0
- zenml/zen_stores/migrations/versions/b4fca5241eea_migrate_onboarding_state.py +167 -0
- zenml/zen_stores/rest_zen_store.py +149 -4
- zenml/zen_stores/schemas/component_schemas.py +14 -0
- zenml/zen_stores/schemas/server_settings_schemas.py +23 -11
- zenml/zen_stores/schemas/stack_schemas.py +10 -0
- zenml/zen_stores/schemas/step_run_schemas.py +27 -11
- zenml/zen_stores/sql_zen_store.py +450 -6
- zenml/zen_stores/zen_store_interface.py +80 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/METADATA +35 -13
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/RECORD +227 -191
- zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js +0 -77
- zenml/zen_server/dashboard/assets/@tanstack-CEbkxrhX.js +0 -30
- zenml/zen_server/dashboard/assets/AwarenessChannel-nXGpmj_f.js +0 -1
- zenml/zen_server/dashboard/assets/Cards-nwsvQLVS.js +0 -1
- zenml/zen_server/dashboard/assets/Commands-DuIWKg_Q.js +0 -1
- zenml/zen_server/dashboard/assets/CopyButton-B_YSm-Ds.js +0 -2
- zenml/zen_server/dashboard/assets/DisplayDate-BdguISQF.js +0 -1
- zenml/zen_server/dashboard/assets/EmptyState-BkooiGtL.js +0 -1
- zenml/zen_server/dashboard/assets/Error-B6M0dPph.js +0 -1
- zenml/zen_server/dashboard/assets/Helpbox-BQoqCm04.js +0 -1
- zenml/zen_server/dashboard/assets/Infobox-Ce9mefqU.js +0 -1
- zenml/zen_server/dashboard/assets/InlineAvatar-DGf3dVhV.js +0 -1
- zenml/zen_server/dashboard/assets/PageHeader-DGaemzjc.js +0 -1
- zenml/zen_server/dashboard/assets/Pagination-DVYfBCCc.js +0 -1
- zenml/zen_server/dashboard/assets/PasswordChecker-DSLBp7Vl.js +0 -1
- zenml/zen_server/dashboard/assets/SetPassword-B5s7DJug.js +0 -1
- zenml/zen_server/dashboard/assets/SuccessStep-ZzczaM7g.js +0 -1
- zenml/zen_server/dashboard/assets/chevron-down-zcvCWmyP.js +0 -1
- zenml/zen_server/dashboard/assets/cloud-only-Ba_ShBR5.js +0 -1
- zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
- zenml/zen_server/dashboard/assets/index-QORVVTMN.js +0 -55
- zenml/zen_server/dashboard/assets/index.esm-F7nqy9zY.js +0 -1
- zenml/zen_server/dashboard/assets/not-found-Dh2la7kh.js +0 -1
- zenml/zen_server/dashboard/assets/page-B-5jAKoO.js +0 -1
- zenml/zen_server/dashboard/assets/page-B-vWk8a6.js +0 -1
- zenml/zen_server/dashboard/assets/page-B0BrqfS8.js +0 -1
- zenml/zen_server/dashboard/assets/page-BQxVFlUl.js +0 -1
- zenml/zen_server/dashboard/assets/page-ByrHy6Ss.js +0 -1
- zenml/zen_server/dashboard/assets/page-CPtY4Kv_.js +0 -1
- zenml/zen_server/dashboard/assets/page-CmmukLsl.js +0 -1
- zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
- zenml/zen_server/dashboard/assets/page-DAQQyLxT.js +0 -1
- zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
- zenml/zen_server/dashboard/assets/page-DZCbwOEs.js +0 -2
- zenml/zen_server/dashboard/assets/page-DdaIt20-.js +0 -1
- zenml/zen_server/dashboard/assets/page-LqLs24Ot.js +0 -1
- zenml/zen_server/dashboard/assets/page-lebv0c7C.js +0 -1
- zenml/zen_server/dashboard/assets/update-server-settings-mutation-0Wgz8pUE.js +0 -1
- zenml/zen_server/dashboard_legacy/static/js/main.ac2f17d0.chunk.js +0 -2
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/entry_points.txt +0 -0
zenml/cli/stack.py
CHANGED
@@ -15,17 +15,35 @@
|
|
15
15
|
|
16
16
|
import getpass
|
17
17
|
import os
|
18
|
+
import re
|
19
|
+
import time
|
20
|
+
import webbrowser
|
21
|
+
from datetime import datetime
|
18
22
|
from pathlib import Path
|
19
|
-
from typing import
|
23
|
+
from typing import (
|
24
|
+
TYPE_CHECKING,
|
25
|
+
Any,
|
26
|
+
Dict,
|
27
|
+
List,
|
28
|
+
Optional,
|
29
|
+
Set,
|
30
|
+
Union,
|
31
|
+
)
|
20
32
|
from uuid import UUID
|
21
33
|
|
22
34
|
import click
|
35
|
+
from rich.console import Console
|
36
|
+
from rich.markdown import Markdown
|
37
|
+
from rich.prompt import Confirm
|
38
|
+
from rich.style import Style
|
39
|
+
from rich.syntax import Syntax
|
23
40
|
|
24
41
|
import zenml
|
25
42
|
from zenml.analytics.enums import AnalyticsEvent
|
26
43
|
from zenml.analytics.utils import track_handler
|
27
44
|
from zenml.cli import utils as cli_utils
|
28
45
|
from zenml.cli.cli import TagGroup, cli
|
46
|
+
from zenml.cli.text_utils import OldSchoolMarkdownHeading
|
29
47
|
from zenml.cli.utils import (
|
30
48
|
_component_display_name,
|
31
49
|
confirmation,
|
@@ -45,7 +63,11 @@ from zenml.constants import (
|
|
45
63
|
MLSTACKS_SUPPORTED_STACK_COMPONENTS,
|
46
64
|
STACK_RECIPE_MODULAR_RECIPES,
|
47
65
|
)
|
48
|
-
from zenml.enums import
|
66
|
+
from zenml.enums import (
|
67
|
+
CliCategories,
|
68
|
+
StackComponentType,
|
69
|
+
StackDeploymentProvider,
|
70
|
+
)
|
49
71
|
from zenml.exceptions import (
|
50
72
|
IllegalOperationError,
|
51
73
|
ProvisioningError,
|
@@ -53,7 +75,20 @@ from zenml.exceptions import (
|
|
53
75
|
from zenml.io.fileio import rmtree
|
54
76
|
from zenml.logger import get_logger
|
55
77
|
from zenml.models import StackFilter
|
56
|
-
from zenml.
|
78
|
+
from zenml.models.v2.core.service_connector import (
|
79
|
+
ServiceConnectorRequest,
|
80
|
+
ServiceConnectorResponse,
|
81
|
+
)
|
82
|
+
from zenml.models.v2.misc.full_stack import (
|
83
|
+
ComponentInfo,
|
84
|
+
FullStackRequest,
|
85
|
+
ServiceConnectorInfo,
|
86
|
+
ServiceConnectorResourcesInfo,
|
87
|
+
)
|
88
|
+
from zenml.service_connectors.service_connector_utils import (
|
89
|
+
get_resources_options_from_resource_model_for_full_stack,
|
90
|
+
)
|
91
|
+
from zenml.utils.dashboard_utils import get_component_url, get_stack_url
|
57
92
|
from zenml.utils.io_utils import create_dir_recursive_if_not_exists
|
58
93
|
from zenml.utils.mlstacks_utils import (
|
59
94
|
convert_click_params_to_mlstacks_primitives,
|
@@ -93,7 +128,7 @@ def stack() -> None:
|
|
93
128
|
"artifact_store",
|
94
129
|
help="Name of the artifact store for this stack.",
|
95
130
|
type=str,
|
96
|
-
required=
|
131
|
+
required=False,
|
97
132
|
)
|
98
133
|
@click.option(
|
99
134
|
"-o",
|
@@ -101,7 +136,7 @@ def stack() -> None:
|
|
101
136
|
"orchestrator",
|
102
137
|
help="Name of the orchestrator for this stack.",
|
103
138
|
type=str,
|
104
|
-
required=
|
139
|
+
required=False,
|
105
140
|
)
|
106
141
|
@click.option(
|
107
142
|
"-c",
|
@@ -190,10 +225,24 @@ def stack() -> None:
|
|
190
225
|
help="Immediately set this stack as active.",
|
191
226
|
type=click.BOOL,
|
192
227
|
)
|
228
|
+
@click.option(
|
229
|
+
"-p",
|
230
|
+
"--provider",
|
231
|
+
help="Name of the cloud provider for this stack.",
|
232
|
+
type=click.Choice(["aws", "azure", "gcp"]),
|
233
|
+
required=False,
|
234
|
+
)
|
235
|
+
@click.option(
|
236
|
+
"-sc",
|
237
|
+
"--connector",
|
238
|
+
help="Name of the service connector for this stack.",
|
239
|
+
type=str,
|
240
|
+
required=False,
|
241
|
+
)
|
193
242
|
def register_stack(
|
194
243
|
stack_name: str,
|
195
|
-
artifact_store: str,
|
196
|
-
orchestrator: str,
|
244
|
+
artifact_store: Optional[str] = None,
|
245
|
+
orchestrator: Optional[str] = None,
|
197
246
|
container_registry: Optional[str] = None,
|
198
247
|
model_registry: Optional[str] = None,
|
199
248
|
step_operator: Optional[str] = None,
|
@@ -205,6 +254,8 @@ def register_stack(
|
|
205
254
|
data_validator: Optional[str] = None,
|
206
255
|
image_builder: Optional[str] = None,
|
207
256
|
set_stack: bool = False,
|
257
|
+
provider: Optional[str] = None,
|
258
|
+
connector: Optional[str] = None,
|
208
259
|
) -> None:
|
209
260
|
"""Register a stack.
|
210
261
|
|
@@ -223,44 +274,259 @@ def register_stack(
|
|
223
274
|
data_validator: Name of the data validator for this stack.
|
224
275
|
image_builder: Name of the new image builder for this stack.
|
225
276
|
set_stack: Immediately set this stack as active.
|
277
|
+
provider: Name of the cloud provider for this stack.
|
278
|
+
connector: Name of the service connector for this stack.
|
226
279
|
"""
|
227
|
-
|
228
|
-
|
280
|
+
if (provider is None and connector is None) and (
|
281
|
+
artifact_store is None or orchestrator is None
|
282
|
+
):
|
283
|
+
cli_utils.error(
|
284
|
+
"Only stack using service connector can be registered "
|
285
|
+
"without specifying an artifact store and an orchestrator. "
|
286
|
+
"Please specify the artifact store and the orchestrator or "
|
287
|
+
"the service connector or cloud type settings."
|
288
|
+
)
|
229
289
|
|
230
|
-
|
290
|
+
client = Client()
|
231
291
|
|
232
|
-
|
233
|
-
|
292
|
+
if provider is not None or connector is not None:
|
293
|
+
if client.zen_store.is_local_store():
|
294
|
+
cli_utils.error(
|
295
|
+
"You are registering a stack using a service connector, but "
|
296
|
+
"this feature cannot be used with a local ZenML deployment. "
|
297
|
+
"ZenML needs to be accessible from the cloud provider to allow the "
|
298
|
+
"stack and its components to be registered automatically. "
|
299
|
+
"Please deploy ZenML in a remote environment as described in the "
|
300
|
+
"documentation: https://docs.zenml.io/getting-started/deploying-zenml "
|
301
|
+
"or use a managed ZenML Pro server instance for quick access to "
|
302
|
+
"this feature and more: https://www.zenml.io/pro"
|
303
|
+
)
|
234
304
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
305
|
+
try:
|
306
|
+
client.get_stack(
|
307
|
+
name_id_or_prefix=stack_name,
|
308
|
+
allow_name_prefix_match=False,
|
309
|
+
)
|
310
|
+
cli_utils.error(
|
311
|
+
f"A stack with name `{stack_name}` already exists, "
|
312
|
+
"please use a different name."
|
313
|
+
)
|
314
|
+
except KeyError:
|
315
|
+
pass
|
316
|
+
|
317
|
+
labels: Dict[str, str] = {}
|
318
|
+
components: Dict[StackComponentType, Union[UUID, ComponentInfo]] = {}
|
319
|
+
# cloud flow
|
320
|
+
created_objects: Set[str] = set()
|
321
|
+
service_connector: Optional[Union[UUID, ServiceConnectorInfo]] = None
|
322
|
+
if provider is not None and connector is None:
|
323
|
+
service_connector_response = None
|
324
|
+
use_auto_configure = False
|
325
|
+
try:
|
326
|
+
service_connector_response, _ = client.create_service_connector(
|
327
|
+
name=stack_name,
|
328
|
+
connector_type=provider,
|
329
|
+
register=False,
|
330
|
+
auto_configure=True,
|
331
|
+
verify=False,
|
254
332
|
)
|
255
|
-
|
256
|
-
|
257
|
-
|
333
|
+
except NotImplementedError:
|
334
|
+
cli_utils.warning(
|
335
|
+
f"The {provider.upper()} service connector libraries are not "
|
336
|
+
"installed properly. Please run `zenml integration install "
|
337
|
+
f"{provider}` and try again to enable auto-discovery of the "
|
338
|
+
"connection configuration."
|
339
|
+
)
|
340
|
+
except Exception:
|
341
|
+
pass
|
342
|
+
if service_connector_response:
|
343
|
+
use_auto_configure = Confirm.ask(
|
344
|
+
f"[bold]{provider.upper()} cloud service connector[/bold] "
|
345
|
+
"has detected connection credentials in your environment.\n"
|
346
|
+
"Would you like to use these credentials or create a new "
|
347
|
+
"configuration by providing connection details?",
|
348
|
+
default=True,
|
349
|
+
show_choices=True,
|
350
|
+
show_default=True,
|
258
351
|
)
|
259
352
|
|
353
|
+
connector_selected: Optional[int] = None
|
354
|
+
if not use_auto_configure:
|
355
|
+
service_connector_response = None
|
356
|
+
existing_connectors = client.list_service_connectors(
|
357
|
+
connector_type=provider, size=100
|
358
|
+
)
|
359
|
+
if existing_connectors.total:
|
360
|
+
connector_selected = cli_utils.multi_choice_prompt(
|
361
|
+
object_type=f"{provider.upper()} service connectors",
|
362
|
+
choices=[
|
363
|
+
[connector.name]
|
364
|
+
for connector in existing_connectors.items
|
365
|
+
],
|
366
|
+
headers=["Name"],
|
367
|
+
prompt_text=f"We found these {provider.upper()} service connectors. "
|
368
|
+
"Do you want to create a new one or use one of the existing ones?",
|
369
|
+
default_choice="0",
|
370
|
+
allow_zero_be_a_new_object=True,
|
371
|
+
)
|
372
|
+
if use_auto_configure or connector_selected is None:
|
373
|
+
service_connector = _get_service_connector_info(
|
374
|
+
cloud_provider=provider,
|
375
|
+
connector_details=service_connector_response,
|
376
|
+
)
|
377
|
+
created_objects.add("service_connector")
|
378
|
+
else:
|
379
|
+
selected_connector = existing_connectors.items[connector_selected]
|
380
|
+
service_connector = selected_connector.id
|
381
|
+
connector = selected_connector.name
|
382
|
+
if isinstance(selected_connector.connector_type, str):
|
383
|
+
provider = selected_connector.connector_type
|
384
|
+
else:
|
385
|
+
provider = selected_connector.connector_type.connector_type
|
386
|
+
elif connector is not None:
|
387
|
+
service_connector_response = client.get_service_connector(connector)
|
388
|
+
service_connector = service_connector_response.id
|
389
|
+
if provider:
|
390
|
+
if service_connector_response.type != provider:
|
391
|
+
cli_utils.warning(
|
392
|
+
f"The service connector `{connector}` is not of type `{provider}`."
|
393
|
+
)
|
394
|
+
else:
|
395
|
+
provider = service_connector_response.type
|
396
|
+
|
397
|
+
if service_connector:
|
398
|
+
labels["zenml:wizard"] = "true"
|
399
|
+
if provider:
|
400
|
+
labels["zenml:provider"] = provider
|
401
|
+
resources_info = None
|
402
|
+
# explore the service connector
|
403
|
+
with console.status(
|
404
|
+
"Exploring resources available to the service connector...\n"
|
405
|
+
):
|
406
|
+
resources_info = (
|
407
|
+
get_resources_options_from_resource_model_for_full_stack(
|
408
|
+
connector_details=service_connector
|
409
|
+
)
|
410
|
+
)
|
411
|
+
if resources_info is None:
|
412
|
+
cli_utils.error(
|
413
|
+
f"Failed to fetch service connector resources information for {service_connector}..."
|
414
|
+
)
|
415
|
+
|
416
|
+
# create components
|
417
|
+
needed_components = (
|
418
|
+
(StackComponentType.ARTIFACT_STORE, artifact_store),
|
419
|
+
(StackComponentType.ORCHESTRATOR, orchestrator),
|
420
|
+
(StackComponentType.CONTAINER_REGISTRY, container_registry),
|
421
|
+
)
|
422
|
+
for component_type, preset_name in needed_components:
|
423
|
+
component_info: Optional[Union[UUID, ComponentInfo]] = None
|
424
|
+
if preset_name is not None:
|
425
|
+
component_response = client.get_stack_component(
|
426
|
+
component_type, preset_name
|
427
|
+
)
|
428
|
+
component_info = component_response.id
|
429
|
+
else:
|
430
|
+
if isinstance(service_connector, UUID):
|
431
|
+
# find existing components under same connector
|
432
|
+
if (
|
433
|
+
component_type
|
434
|
+
in resources_info.components_resources_info
|
435
|
+
):
|
436
|
+
existing_components = [
|
437
|
+
existing_response
|
438
|
+
for res_info in resources_info.components_resources_info[
|
439
|
+
component_type
|
440
|
+
]
|
441
|
+
for existing_response in res_info.connected_through_service_connector
|
442
|
+
]
|
443
|
+
|
444
|
+
# if some existing components are found - prompt user what to do
|
445
|
+
component_selected: Optional[int] = None
|
446
|
+
component_selected = cli_utils.multi_choice_prompt(
|
447
|
+
object_type=component_type.value.replace("_", " "),
|
448
|
+
choices=[
|
449
|
+
[
|
450
|
+
component.flavor,
|
451
|
+
component.name,
|
452
|
+
component.configuration or "",
|
453
|
+
component.connector_resource_id,
|
454
|
+
]
|
455
|
+
for component in existing_components
|
456
|
+
],
|
457
|
+
headers=[
|
458
|
+
"Type",
|
459
|
+
"Name",
|
460
|
+
"Configuration",
|
461
|
+
"Connected as",
|
462
|
+
],
|
463
|
+
prompt_text=f"We found these {component_type.value.replace('_', ' ')} "
|
464
|
+
"connected using the current service connector. Do you "
|
465
|
+
"want to create a new one or use existing one?",
|
466
|
+
default_choice="0",
|
467
|
+
allow_zero_be_a_new_object=True,
|
468
|
+
)
|
469
|
+
else:
|
470
|
+
component_selected = None
|
471
|
+
|
472
|
+
if component_selected is None:
|
473
|
+
component_info = _get_stack_component_info(
|
474
|
+
component_type=component_type.value,
|
475
|
+
cloud_provider=provider
|
476
|
+
or resources_info.connector_type,
|
477
|
+
resources_info=resources_info,
|
478
|
+
service_connector_index=0,
|
479
|
+
)
|
480
|
+
component_name = stack_name
|
481
|
+
created_objects.add(component_type.value)
|
482
|
+
else:
|
483
|
+
selected_component = existing_components[
|
484
|
+
component_selected
|
485
|
+
]
|
486
|
+
component_info = selected_component.id
|
487
|
+
component_name = selected_component.name
|
488
|
+
|
489
|
+
components[component_type] = component_info
|
490
|
+
if component_type == StackComponentType.ARTIFACT_STORE:
|
491
|
+
artifact_store = component_name
|
492
|
+
if component_type == StackComponentType.ORCHESTRATOR:
|
493
|
+
orchestrator = component_name
|
494
|
+
if component_type == StackComponentType.CONTAINER_REGISTRY:
|
495
|
+
container_registry = component_name
|
496
|
+
|
497
|
+
# normal flow once all components are defined
|
498
|
+
with console.status(f"Registering stack '{stack_name}'...\n"):
|
499
|
+
for component_type_, component_name_ in [
|
500
|
+
(StackComponentType.ARTIFACT_STORE, artifact_store),
|
501
|
+
(StackComponentType.ORCHESTRATOR, orchestrator),
|
502
|
+
(StackComponentType.ALERTER, alerter),
|
503
|
+
(StackComponentType.ANNOTATOR, annotator),
|
504
|
+
(StackComponentType.DATA_VALIDATOR, data_validator),
|
505
|
+
(StackComponentType.FEATURE_STORE, feature_store),
|
506
|
+
(StackComponentType.IMAGE_BUILDER, image_builder),
|
507
|
+
(StackComponentType.MODEL_DEPLOYER, model_deployer),
|
508
|
+
(StackComponentType.MODEL_REGISTRY, model_registry),
|
509
|
+
(StackComponentType.STEP_OPERATOR, step_operator),
|
510
|
+
(StackComponentType.EXPERIMENT_TRACKER, experiment_tracker),
|
511
|
+
(StackComponentType.CONTAINER_REGISTRY, container_registry),
|
512
|
+
]:
|
513
|
+
if component_name_ and component_type_ not in components:
|
514
|
+
components[component_type_] = client.get_stack_component(
|
515
|
+
component_type_, component_name_
|
516
|
+
).id
|
517
|
+
|
260
518
|
try:
|
261
|
-
created_stack = client.
|
262
|
-
|
263
|
-
|
519
|
+
created_stack = client.zen_store.create_full_stack(
|
520
|
+
full_stack=FullStackRequest(
|
521
|
+
user=client.active_user.id,
|
522
|
+
workspace=client.active_workspace.id,
|
523
|
+
name=stack_name,
|
524
|
+
components=components,
|
525
|
+
service_connectors=[service_connector]
|
526
|
+
if service_connector
|
527
|
+
else [],
|
528
|
+
labels=labels,
|
529
|
+
)
|
264
530
|
)
|
265
531
|
except (KeyError, IllegalOperationError) as err:
|
266
532
|
cli_utils.error(str(err))
|
@@ -268,6 +534,10 @@ def register_stack(
|
|
268
534
|
cli_utils.declare(
|
269
535
|
f"Stack '{created_stack.name}' successfully registered!"
|
270
536
|
)
|
537
|
+
cli_utils.print_stack_configuration(
|
538
|
+
stack=created_stack,
|
539
|
+
active=created_stack.id == client.active_stack_model.id,
|
540
|
+
)
|
271
541
|
|
272
542
|
if set_stack:
|
273
543
|
client.activate_stack(created_stack.id)
|
@@ -277,6 +547,30 @@ def register_stack(
|
|
277
547
|
f"Active {scope} stack set to:'{created_stack.name}'"
|
278
548
|
)
|
279
549
|
|
550
|
+
delete_commands = []
|
551
|
+
if "service_connector" in created_objects:
|
552
|
+
created_objects.remove("service_connector")
|
553
|
+
connectors = set()
|
554
|
+
for each in created_objects:
|
555
|
+
if comps_ := created_stack.components[StackComponentType(each)]:
|
556
|
+
if conn_ := comps_[0].connector:
|
557
|
+
connectors.add(conn_.name)
|
558
|
+
for connector in connectors:
|
559
|
+
delete_commands.append(
|
560
|
+
"zenml service-connector delete " + connector
|
561
|
+
)
|
562
|
+
for each in created_objects:
|
563
|
+
if comps_ := created_stack.components[StackComponentType(each)]:
|
564
|
+
delete_commands.append(
|
565
|
+
f"zenml {each.replace('_', '-')} delete {comps_[0].name}"
|
566
|
+
)
|
567
|
+
delete_commands.append("zenml stack delete -y " + created_stack.name)
|
568
|
+
|
569
|
+
Console().print(
|
570
|
+
"To delete the objects created by this command run, please run in a sequence:\n"
|
571
|
+
)
|
572
|
+
Console().print(Syntax("\n".join(delete_commands[::-1]), "bash"))
|
573
|
+
|
280
574
|
print_model_url(get_stack_url(created_stack))
|
281
575
|
|
282
576
|
|
@@ -1286,7 +1580,261 @@ def _get_deployment_params_interactively(
|
|
1286
1580
|
return deployment_values
|
1287
1581
|
|
1288
1582
|
|
1289
|
-
|
1583
|
+
def validate_name(ctx: click.Context, param: str, value: str) -> str:
|
1584
|
+
"""Validate the name of the stack.
|
1585
|
+
|
1586
|
+
Args:
|
1587
|
+
ctx: The click context.
|
1588
|
+
param: The parameter name.
|
1589
|
+
value: The value of the parameter.
|
1590
|
+
|
1591
|
+
Returns:
|
1592
|
+
The validated value.
|
1593
|
+
|
1594
|
+
Raises:
|
1595
|
+
BadParameter: If the name is invalid.
|
1596
|
+
"""
|
1597
|
+
if not value:
|
1598
|
+
return value
|
1599
|
+
|
1600
|
+
if not re.match(r"^[a-zA-Z0-9-]*$", value):
|
1601
|
+
raise click.BadParameter(
|
1602
|
+
"Stack name must contain only alphanumeric characters and hyphens."
|
1603
|
+
)
|
1604
|
+
|
1605
|
+
if len(value) > 16:
|
1606
|
+
raise click.BadParameter(
|
1607
|
+
"Stack name must have a maximum length of 16 characters."
|
1608
|
+
)
|
1609
|
+
|
1610
|
+
return value
|
1611
|
+
|
1612
|
+
|
1613
|
+
@stack.command(
|
1614
|
+
help="""Deploy a fully functional ZenML stack in one of the cloud providers.
|
1615
|
+
|
1616
|
+
Running this command will initiate an assisted process that will walk you
|
1617
|
+
through automatically provisioning all the cloud infrastructure resources
|
1618
|
+
necessary for a fully functional ZenML stack in the cloud provider of your
|
1619
|
+
choice. A corresponding ZenML stack will also be automatically registered along
|
1620
|
+
with all the necessary components and properly authenticated through service
|
1621
|
+
connectors.
|
1622
|
+
"""
|
1623
|
+
)
|
1624
|
+
@click.option(
|
1625
|
+
"--provider",
|
1626
|
+
"-p",
|
1627
|
+
"provider",
|
1628
|
+
required=True,
|
1629
|
+
type=click.Choice(StackDeploymentProvider.values()),
|
1630
|
+
)
|
1631
|
+
@click.option(
|
1632
|
+
"--name",
|
1633
|
+
"-n",
|
1634
|
+
"stack_name",
|
1635
|
+
type=click.STRING,
|
1636
|
+
required=False,
|
1637
|
+
help="Custom string to use as a prefix to generate names for the ZenML "
|
1638
|
+
"stack, its components service connectors as well as provisioned cloud "
|
1639
|
+
"infrastructure resources. May only contain alphanumeric characters and "
|
1640
|
+
"hyphens and have a maximum length of 16 characters.",
|
1641
|
+
callback=validate_name,
|
1642
|
+
)
|
1643
|
+
@click.option(
|
1644
|
+
"--location",
|
1645
|
+
"-l",
|
1646
|
+
type=click.STRING,
|
1647
|
+
required=False,
|
1648
|
+
help="The location to deploy the stack to.",
|
1649
|
+
)
|
1650
|
+
@click.option(
|
1651
|
+
"--set",
|
1652
|
+
"set_stack",
|
1653
|
+
is_flag=True,
|
1654
|
+
help="Immediately set this stack as active.",
|
1655
|
+
type=click.BOOL,
|
1656
|
+
)
|
1657
|
+
@click.pass_context
|
1658
|
+
def deploy(
|
1659
|
+
ctx: click.Context,
|
1660
|
+
provider: str,
|
1661
|
+
stack_name: Optional[str] = None,
|
1662
|
+
location: Optional[str] = None,
|
1663
|
+
set_stack: bool = False,
|
1664
|
+
) -> None:
|
1665
|
+
"""Deploy and register a fully functional cloud ZenML stack.
|
1666
|
+
|
1667
|
+
Args:
|
1668
|
+
ctx: The click context.
|
1669
|
+
provider: The cloud provider to deploy the stack to.
|
1670
|
+
stack_name: A name for the ZenML stack that gets imported as a result
|
1671
|
+
of the recipe deployment.
|
1672
|
+
location: The location to deploy the stack to.
|
1673
|
+
set_stack: Immediately set the deployed stack as active.
|
1674
|
+
|
1675
|
+
Raises:
|
1676
|
+
Abort: If the user aborts the deployment.
|
1677
|
+
KeyboardInterrupt: If the user interrupts the deployment.
|
1678
|
+
"""
|
1679
|
+
stack_name = stack_name or f"zenml-{provider}-stack"
|
1680
|
+
|
1681
|
+
# Set up the markdown renderer to use the old-school markdown heading
|
1682
|
+
Markdown.elements.update(
|
1683
|
+
{
|
1684
|
+
"heading_open": OldSchoolMarkdownHeading,
|
1685
|
+
}
|
1686
|
+
)
|
1687
|
+
|
1688
|
+
client = Client()
|
1689
|
+
if client.zen_store.is_local_store():
|
1690
|
+
cli_utils.error(
|
1691
|
+
"This feature cannot be used with a local ZenML deployment. "
|
1692
|
+
"ZenML needs to be accessible from the cloud provider to allow the "
|
1693
|
+
"stack and its components to be registered automatically. "
|
1694
|
+
"Please deploy ZenML in a remote environment as described in the "
|
1695
|
+
"documentation: https://docs.zenml.io/getting-started/deploying-zenml "
|
1696
|
+
"or use a managed ZenML Pro server instance for quick access to "
|
1697
|
+
"this feature and more: https://www.zenml.io/pro"
|
1698
|
+
)
|
1699
|
+
|
1700
|
+
with track_handler(
|
1701
|
+
event=AnalyticsEvent.DEPLOY_FULL_STACK,
|
1702
|
+
) as analytics_handler:
|
1703
|
+
analytics_handler.metadata = {
|
1704
|
+
"provider": provider,
|
1705
|
+
}
|
1706
|
+
|
1707
|
+
deployment = client.zen_store.get_stack_deployment_info(
|
1708
|
+
provider=StackDeploymentProvider(provider),
|
1709
|
+
)
|
1710
|
+
|
1711
|
+
if location and location not in deployment.locations.values():
|
1712
|
+
cli_utils.error(
|
1713
|
+
f"Invalid location '{location}' for provider '{provider}'. "
|
1714
|
+
f"Valid locations are: {', '.join(deployment.locations.values())}"
|
1715
|
+
)
|
1716
|
+
|
1717
|
+
console.print(
|
1718
|
+
Markdown(
|
1719
|
+
f"# {provider.upper()} ZenML Cloud Stack Deployment\n"
|
1720
|
+
+ deployment.description
|
1721
|
+
)
|
1722
|
+
)
|
1723
|
+
console.print(Markdown("## Instructions\n" + deployment.instructions))
|
1724
|
+
|
1725
|
+
deployment_config = client.zen_store.get_stack_deployment_config(
|
1726
|
+
provider=StackDeploymentProvider(provider),
|
1727
|
+
stack_name=stack_name,
|
1728
|
+
location=location,
|
1729
|
+
)
|
1730
|
+
|
1731
|
+
if deployment_config.configuration:
|
1732
|
+
console.print(
|
1733
|
+
Markdown(
|
1734
|
+
"## Configuration\n"
|
1735
|
+
"You will be asked to provide the following configuration "
|
1736
|
+
"values during the deployment process:"
|
1737
|
+
),
|
1738
|
+
"\n",
|
1739
|
+
)
|
1740
|
+
|
1741
|
+
console.print(
|
1742
|
+
deployment_config.configuration,
|
1743
|
+
no_wrap=True,
|
1744
|
+
overflow="ignore",
|
1745
|
+
crop=False,
|
1746
|
+
style=Style(bgcolor="grey15"),
|
1747
|
+
)
|
1748
|
+
|
1749
|
+
if not cli_utils.confirmation(
|
1750
|
+
"\n\nProceed to continue with the deployment. You will be "
|
1751
|
+
f"automatically redirected to {provider.upper()} in your browser.",
|
1752
|
+
):
|
1753
|
+
raise click.Abort()
|
1754
|
+
|
1755
|
+
date_start = datetime.utcnow()
|
1756
|
+
|
1757
|
+
webbrowser.open(deployment_config.deployment_url)
|
1758
|
+
console.print(
|
1759
|
+
Markdown(
|
1760
|
+
f"If your browser did not open automatically, please open "
|
1761
|
+
f"the following URL into your browser to deploy the stack to "
|
1762
|
+
f"{provider.upper()}: "
|
1763
|
+
f"[{deployment_config.deployment_url_text}]"
|
1764
|
+
f"({deployment_config.deployment_url}).\n\n"
|
1765
|
+
)
|
1766
|
+
)
|
1767
|
+
|
1768
|
+
try:
|
1769
|
+
cli_utils.declare(
|
1770
|
+
"\n\nWaiting for the deployment to complete and the stack to be "
|
1771
|
+
"registered. Press CTRL+C to abort...\n"
|
1772
|
+
)
|
1773
|
+
|
1774
|
+
while True:
|
1775
|
+
deployed_stack = client.zen_store.get_stack_deployment_stack(
|
1776
|
+
provider=StackDeploymentProvider(provider),
|
1777
|
+
stack_name=stack_name,
|
1778
|
+
location=location,
|
1779
|
+
date_start=date_start,
|
1780
|
+
)
|
1781
|
+
if deployed_stack:
|
1782
|
+
break
|
1783
|
+
time.sleep(10)
|
1784
|
+
|
1785
|
+
analytics_handler.metadata.update(
|
1786
|
+
{
|
1787
|
+
"stack_id": deployed_stack.stack.id,
|
1788
|
+
}
|
1789
|
+
)
|
1790
|
+
|
1791
|
+
except KeyboardInterrupt:
|
1792
|
+
cli_utils.declare("Stack deployment aborted.")
|
1793
|
+
raise
|
1794
|
+
|
1795
|
+
stack_desc = f"""## Stack successfully registered! 🚀
|
1796
|
+
Stack [{deployed_stack.stack.name}]({get_stack_url(deployed_stack.stack)}):\n"""
|
1797
|
+
|
1798
|
+
for component_type, components in deployed_stack.stack.components.items():
|
1799
|
+
if components:
|
1800
|
+
component = components[0]
|
1801
|
+
stack_desc += (
|
1802
|
+
f" * `{component.flavor}` {component_type.value}: "
|
1803
|
+
f"[{component.name}]({get_component_url(component)})\n"
|
1804
|
+
)
|
1805
|
+
|
1806
|
+
if deployed_stack.service_connector:
|
1807
|
+
stack_desc += (
|
1808
|
+
f" * Service Connector: {deployed_stack.service_connector.name}\n"
|
1809
|
+
)
|
1810
|
+
|
1811
|
+
console.print(Markdown(stack_desc))
|
1812
|
+
|
1813
|
+
follow_up = f"""
|
1814
|
+
## Follow-up
|
1815
|
+
|
1816
|
+
{deployment.post_deploy_instructions}
|
1817
|
+
|
1818
|
+
To use the `{deployed_stack.stack.name}` stack to run pipelines:
|
1819
|
+
|
1820
|
+
* install the required ZenML integrations by running: `zenml integration install {" ".join(deployment.integrations)}`
|
1821
|
+
"""
|
1822
|
+
if set_stack:
|
1823
|
+
client.activate_stack(deployed_stack.stack.id)
|
1824
|
+
follow_up += f"""
|
1825
|
+
* the `{deployed_stack.stack.name}` stack has already been set as active
|
1826
|
+
"""
|
1827
|
+
else:
|
1828
|
+
follow_up += f"""
|
1829
|
+
* set the `{deployed_stack.stack.name}` stack as active by running: `zenml stack set {deployed_stack.stack.name}`
|
1830
|
+
"""
|
1831
|
+
|
1832
|
+
console.print(
|
1833
|
+
Markdown(follow_up),
|
1834
|
+
)
|
1835
|
+
|
1836
|
+
|
1837
|
+
@stack.command(help="[DEPRECATED] Deploy a stack using mlstacks.")
|
1290
1838
|
@click.option(
|
1291
1839
|
"--provider",
|
1292
1840
|
"-p",
|
@@ -1426,7 +1974,7 @@ def _get_deployment_params_interactively(
|
|
1426
1974
|
help="Deploy the stack interactively.",
|
1427
1975
|
)
|
1428
1976
|
@click.pass_context
|
1429
|
-
def
|
1977
|
+
def deploy_mlstack(
|
1430
1978
|
ctx: click.Context,
|
1431
1979
|
provider: str,
|
1432
1980
|
stack_name: str,
|
@@ -1476,6 +2024,13 @@ def deploy(
|
|
1476
2024
|
region: The region to deploy the stack to.
|
1477
2025
|
interactive: Deploy the stack interactively.
|
1478
2026
|
"""
|
2027
|
+
cli_utils.warning(
|
2028
|
+
"The `zenml stack deploy-mlstack` (former `zenml stack deploy`) CLI "
|
2029
|
+
"command has been deprecated and will be removed in a future release. "
|
2030
|
+
"Please use `zenml stack deploy` instead for a simplified "
|
2031
|
+
"experience."
|
2032
|
+
)
|
2033
|
+
|
1479
2034
|
with track_handler(
|
1480
2035
|
event=AnalyticsEvent.DEPLOY_STACK,
|
1481
2036
|
) as analytics_handler:
|
@@ -1727,3 +2282,221 @@ def connect_stack(
|
|
1727
2282
|
interactive=interactive,
|
1728
2283
|
no_verify=no_verify,
|
1729
2284
|
)
|
2285
|
+
|
2286
|
+
|
2287
|
+
def _get_service_connector_info(
|
2288
|
+
cloud_provider: str,
|
2289
|
+
connector_details: Optional[
|
2290
|
+
Union[ServiceConnectorResponse, ServiceConnectorRequest]
|
2291
|
+
],
|
2292
|
+
) -> ServiceConnectorInfo:
|
2293
|
+
"""Get a service connector info with given cloud provider.
|
2294
|
+
|
2295
|
+
Args:
|
2296
|
+
cloud_provider: The cloud provider to use.
|
2297
|
+
connector_details: Whether to use implicit credentials.
|
2298
|
+
|
2299
|
+
Returns:
|
2300
|
+
The info model of the created service connector.
|
2301
|
+
|
2302
|
+
Raises:
|
2303
|
+
ValueError: If the cloud provider is not supported.
|
2304
|
+
"""
|
2305
|
+
from rich.prompt import Prompt
|
2306
|
+
|
2307
|
+
if cloud_provider not in {"aws", "gcp", "azure"}:
|
2308
|
+
raise ValueError(f"Unknown cloud provider {cloud_provider}")
|
2309
|
+
|
2310
|
+
client = Client()
|
2311
|
+
auth_methods = client.get_service_connector_type(
|
2312
|
+
cloud_provider
|
2313
|
+
).auth_method_dict
|
2314
|
+
if not connector_details:
|
2315
|
+
fixed_auth_methods = list(
|
2316
|
+
[
|
2317
|
+
(key, value)
|
2318
|
+
for key, value in auth_methods.items()
|
2319
|
+
if key != "implicit"
|
2320
|
+
]
|
2321
|
+
)
|
2322
|
+
choices = []
|
2323
|
+
headers = ["Name", "Required"]
|
2324
|
+
for _, value in fixed_auth_methods:
|
2325
|
+
schema = value.config_schema
|
2326
|
+
required = ""
|
2327
|
+
for each_req in schema["required"]:
|
2328
|
+
field = schema["properties"][each_req]
|
2329
|
+
required += f"[bold]{each_req}[/bold] [italic]({field.get('title','no description')})[/italic]\n"
|
2330
|
+
choices.append([value.name, required])
|
2331
|
+
|
2332
|
+
selected_auth_idx = cli_utils.multi_choice_prompt(
|
2333
|
+
object_type=f"authentication methods for {cloud_provider.upper()}",
|
2334
|
+
choices=choices,
|
2335
|
+
headers=headers,
|
2336
|
+
prompt_text="Please choose one of the authentication option above",
|
2337
|
+
)
|
2338
|
+
if selected_auth_idx is None:
|
2339
|
+
cli_utils.error("No authentication method selected.")
|
2340
|
+
auth_type = fixed_auth_methods[selected_auth_idx][0]
|
2341
|
+
else:
|
2342
|
+
auth_type = connector_details.auth_method
|
2343
|
+
|
2344
|
+
selected_auth_model = auth_methods[auth_type]
|
2345
|
+
|
2346
|
+
required_fields = selected_auth_model.config_schema["required"]
|
2347
|
+
properties = selected_auth_model.config_schema["properties"]
|
2348
|
+
|
2349
|
+
answers = {}
|
2350
|
+
for req_field in required_fields:
|
2351
|
+
if connector_details:
|
2352
|
+
if conf_value := connector_details.configuration.get(
|
2353
|
+
req_field, None
|
2354
|
+
):
|
2355
|
+
answers[req_field] = conf_value
|
2356
|
+
elif secret_value := connector_details.secrets.get(
|
2357
|
+
req_field, None
|
2358
|
+
):
|
2359
|
+
answers[req_field] = secret_value.get_secret_value()
|
2360
|
+
if req_field not in answers:
|
2361
|
+
answers[req_field] = Prompt.ask(
|
2362
|
+
f"Please enter value for `{req_field}`:",
|
2363
|
+
password="format" in properties[req_field]
|
2364
|
+
and properties[req_field]["format"] == "password",
|
2365
|
+
)
|
2366
|
+
|
2367
|
+
return ServiceConnectorInfo(
|
2368
|
+
type=cloud_provider,
|
2369
|
+
auth_method=auth_type,
|
2370
|
+
configuration=answers,
|
2371
|
+
)
|
2372
|
+
|
2373
|
+
|
2374
|
+
def _get_stack_component_info(
|
2375
|
+
component_type: str,
|
2376
|
+
cloud_provider: str,
|
2377
|
+
resources_info: ServiceConnectorResourcesInfo,
|
2378
|
+
service_connector_index: Optional[int] = None,
|
2379
|
+
) -> ComponentInfo:
|
2380
|
+
"""Get a stack component info with given type and service connector.
|
2381
|
+
|
2382
|
+
Args:
|
2383
|
+
component_type: The type of component to create.
|
2384
|
+
cloud_provider: The cloud provider to use.
|
2385
|
+
resources_info: The resources info of the service connector.
|
2386
|
+
service_connector_index: The index of the service connector to use.
|
2387
|
+
|
2388
|
+
Returns:
|
2389
|
+
The info model of the stack component.
|
2390
|
+
|
2391
|
+
Raises:
|
2392
|
+
ValueError: If the cloud provider is not supported.
|
2393
|
+
ValueError: If the component type is not supported.
|
2394
|
+
"""
|
2395
|
+
from rich.prompt import Prompt
|
2396
|
+
|
2397
|
+
if cloud_provider not in {"aws", "azure", "gcp"}:
|
2398
|
+
raise ValueError(f"Unknown cloud provider {cloud_provider}")
|
2399
|
+
|
2400
|
+
flavor = "undefined"
|
2401
|
+
service_connector_resource_id = None
|
2402
|
+
config = {}
|
2403
|
+
choices = [
|
2404
|
+
[cri.flavor, resource_id]
|
2405
|
+
for cri in resources_info.components_resources_info[
|
2406
|
+
StackComponentType(component_type)
|
2407
|
+
]
|
2408
|
+
for resource_id in cri.accessible_by_service_connector
|
2409
|
+
]
|
2410
|
+
if component_type == "artifact_store":
|
2411
|
+
selected_storage_idx = cli_utils.multi_choice_prompt(
|
2412
|
+
object_type=f"{cloud_provider.upper()} storages",
|
2413
|
+
choices=choices,
|
2414
|
+
headers=["Artifact Store Type", "Storage"],
|
2415
|
+
prompt_text="Please choose one of the storages for the new artifact store:",
|
2416
|
+
)
|
2417
|
+
if selected_storage_idx is None:
|
2418
|
+
cli_utils.error("No storage selected.")
|
2419
|
+
|
2420
|
+
selected_storage = choices[selected_storage_idx]
|
2421
|
+
|
2422
|
+
flavor = selected_storage[0]
|
2423
|
+
config = {"path": selected_storage[1]}
|
2424
|
+
service_connector_resource_id = selected_storage[1]
|
2425
|
+
elif component_type == "orchestrator":
|
2426
|
+
|
2427
|
+
def query_region(
|
2428
|
+
provider: StackDeploymentProvider,
|
2429
|
+
compute_type: str,
|
2430
|
+
is_skypilot: bool = False,
|
2431
|
+
) -> str:
|
2432
|
+
deployment_info = Client().zen_store.get_stack_deployment_info(
|
2433
|
+
provider
|
2434
|
+
)
|
2435
|
+
region = Prompt.ask(
|
2436
|
+
f"Select the location for your {compute_type}:",
|
2437
|
+
choices=sorted(
|
2438
|
+
deployment_info.skypilot_default_regions.values()
|
2439
|
+
if is_skypilot
|
2440
|
+
else deployment_info.locations.values()
|
2441
|
+
),
|
2442
|
+
show_choices=True,
|
2443
|
+
)
|
2444
|
+
return region
|
2445
|
+
|
2446
|
+
selected_orchestrator_idx = cli_utils.multi_choice_prompt(
|
2447
|
+
object_type=f"orchestrators on {cloud_provider.upper()}",
|
2448
|
+
choices=choices,
|
2449
|
+
headers=["Orchestrator Type", "Details"],
|
2450
|
+
prompt_text="Please choose one of the orchestrators for the new orchestrator:",
|
2451
|
+
)
|
2452
|
+
if selected_orchestrator_idx is None:
|
2453
|
+
cli_utils.error("No orchestrator selected.")
|
2454
|
+
|
2455
|
+
selected_orchestrator = choices[selected_orchestrator_idx]
|
2456
|
+
|
2457
|
+
config = {}
|
2458
|
+
flavor = selected_orchestrator[0]
|
2459
|
+
if flavor == "sagemaker":
|
2460
|
+
execution_role = Prompt.ask("Enter an execution role ARN:")
|
2461
|
+
config["execution_role"] = execution_role
|
2462
|
+
elif flavor == "vm_aws":
|
2463
|
+
config["region"] = selected_orchestrator[1]
|
2464
|
+
elif flavor == "vm_gcp":
|
2465
|
+
config["region"] = query_region(
|
2466
|
+
StackDeploymentProvider.GCP,
|
2467
|
+
"Skypilot cluster",
|
2468
|
+
is_skypilot=True,
|
2469
|
+
)
|
2470
|
+
elif flavor == "vm_azure":
|
2471
|
+
config["region"] = query_region(
|
2472
|
+
StackDeploymentProvider.AZURE,
|
2473
|
+
"Skypilot cluster",
|
2474
|
+
is_skypilot=True,
|
2475
|
+
)
|
2476
|
+
elif flavor == "vertex":
|
2477
|
+
config["location"] = query_region(
|
2478
|
+
StackDeploymentProvider.GCP, "Vertex AI job"
|
2479
|
+
)
|
2480
|
+
service_connector_resource_id = selected_orchestrator[1]
|
2481
|
+
elif component_type == "container_registry":
|
2482
|
+
selected_registry_idx = cli_utils.multi_choice_prompt(
|
2483
|
+
object_type=f"{cloud_provider.upper()} registries",
|
2484
|
+
choices=choices,
|
2485
|
+
headers=["Container Registry Type", "Container Registry"],
|
2486
|
+
prompt_text="Please choose one of the registries for the new container registry:",
|
2487
|
+
)
|
2488
|
+
if selected_registry_idx is None:
|
2489
|
+
cli_utils.error("No container registry selected.")
|
2490
|
+
selected_registry = choices[selected_registry_idx]
|
2491
|
+
flavor = selected_registry[0]
|
2492
|
+
config = {"uri": selected_registry[1]}
|
2493
|
+
service_connector_resource_id = selected_registry[1]
|
2494
|
+
else:
|
2495
|
+
raise ValueError(f"Unknown component type {component_type}")
|
2496
|
+
|
2497
|
+
return ComponentInfo(
|
2498
|
+
flavor=flavor,
|
2499
|
+
configuration=config,
|
2500
|
+
service_connector_index=service_connector_index,
|
2501
|
+
service_connector_resource_id=service_connector_resource_id,
|
2502
|
+
)
|