zenml-nightly 0.58.2.dev20240626__py3-none-any.whl → 0.61.0.dev20240712__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 +30 -9
- RELEASE_NOTES.md +240 -0
- zenml/VERSION +1 -1
- 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 +946 -39
- zenml/cli/stack_components.py +7 -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 +10 -1
- zenml/container_registries/base_container_registry.py +1 -0
- zenml/enums.py +7 -0
- zenml/event_hub/event_hub.py +5 -8
- zenml/integrations/aws/__init__.py +1 -0
- zenml/integrations/azure/__init__.py +1 -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/huggingface/__init__.py +1 -0
- 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/label_studio/annotators/label_studio_annotator.py +1 -0
- zenml/integrations/langchain/__init__.py +1 -0
- zenml/integrations/mlflow/__init__.py +3 -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 -1
- 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/models/__init__.py +11 -0
- zenml/models/v2/core/component.py +18 -0
- zenml/models/v2/core/model.py +1 -2
- zenml/models/v2/core/service_connector.py +17 -0
- zenml/models/v2/core/stack.py +31 -0
- zenml/models/v2/misc/full_stack.py +97 -0
- zenml/models/v2/misc/stack_deployment.py +86 -0
- zenml/new/pipelines/pipeline.py +1 -1
- zenml/orchestrators/input_utils.py +3 -6
- 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/gcp_stack_deployment.py +260 -0
- zenml/stack_deployments/stack_deployment.py +208 -0
- zenml/stack_deployments/utils.py +44 -0
- zenml/utils/function_utils.py +1 -1
- zenml/utils/pagination_utils.py +7 -5
- zenml/utils/pipeline_docker_image_builder.py +97 -68
- zenml/utils/pydantic_utils.py +6 -5
- zenml/zen_server/cloud_utils.py +18 -3
- zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-DpJaNHKF.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-DJfzkHO1.js} +2 -2
- zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
- zenml/zen_server/dashboard/assets/AwarenessChannel-BYDLT2xC.js +1 -0
- zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-BkOuRmyq.js} +2 -2
- zenml/zen_server/dashboard/assets/Commands-ZvWR1BRs.js +1 -0
- zenml/zen_server/dashboard/assets/CopyButton-DVwLkafa.js +2 -0
- zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-C2IiqX4I.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-CqX0VqW_.js +1 -0
- zenml/zen_server/dashboard/assets/ExecutionStatus-BoLUXR9t.js +1 -0
- zenml/zen_server/dashboard/assets/Helpbox-LFydyVwh.js +1 -0
- zenml/zen_server/dashboard/assets/Infobox-DnENC0sh.js +1 -0
- zenml/zen_server/dashboard/assets/InlineAvatar-CbJtYr0t.js +1 -0
- zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
- zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +1 -0
- zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
- zenml/zen_server/dashboard/assets/SetPassword-BYBdbQDo.js +1 -0
- zenml/zen_server/dashboard/assets/SuccessStep-Nx743hll.js +1 -0
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DF9gSzE0.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-down-zcvCWmyP.js → chevron-down-D_ZlKMqH.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-BiEMg7rd.js} +1 -1
- zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.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-C_CrU4vI.js +1 -0
- zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +55 -0
- zenml/zen_server/dashboard/assets/index-inApY3KQ.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-BUnVASxp.js} +1 -1
- zenml/zen_server/dashboard/assets/not-found-B4VnX8gK.js +1 -0
- zenml/zen_server/dashboard/assets/package-CsUhPmou.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-3efNCDeb.js} +2 -2
- zenml/zen_server/dashboard/assets/page-7zTHbhhI.js +1 -0
- zenml/zen_server/dashboard/assets/page-BEs6jK71.js +1 -0
- zenml/zen_server/dashboard/assets/page-BpSqIf4B.js +1 -0
- zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-Bx6o0ARS.js} +1 -1
- zenml/zen_server/dashboard/assets/page-C43QGHTt.js +9 -0
- zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +1 -0
- zenml/zen_server/dashboard/assets/page-CRTJ0UuR.js +1 -0
- zenml/zen_server/dashboard/assets/page-CUZIGO-3.js +1 -0
- zenml/zen_server/dashboard/assets/page-CaopxiU1.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-Cx67M0QT.js} +1 -1
- zenml/zen_server/dashboard/assets/page-D7Z399xy.js +1 -0
- zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-DKlIdAe5.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-DMOYZppS.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +2 -0
- zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-Dc_7KMQE.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DvCvroOM.js +1 -0
- zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +1 -0
- zenml/zen_server/dashboard/assets/page-JyfeDUfu.js +1 -0
- zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-Sxn82W-5.js} +1 -1
- zenml/zen_server/dashboard/assets/page-TKXERe16.js +1 -0
- zenml/zen_server/dashboard/assets/page-Xu8JEjSU.js +1 -0
- zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
- zenml/zen_server/dashboard/assets/plus-DOeLmm7C.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-0Wgz8pUE.js → update-server-settings-mutation-CR8e3Sir.js} +1 -1
- zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-DuQMeqYA.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.c8c57fb0d2132b1d3c2119e776b7dfb3.js} +4 -4
- zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js → main.382439a7.chunk.js} +2 -2
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.382439a7.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/zenml_cloud_rbac.py +11 -5
- zenml/zen_server/routers/stack_deployment_endpoints.py +158 -0
- 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/0d707865f404_adding_labels_to_stacks.py +30 -0
- zenml/zen_stores/rest_zen_store.py +145 -4
- 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 +300 -6
- zenml/zen_stores/zen_store_interface.py +80 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/METADATA +32 -10
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/RECORD +178 -162
- 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/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_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.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,19 @@ 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
|
+
)
|
87
|
+
from zenml.models.v2.misc.service_connector_type import (
|
88
|
+
ServiceConnectorTypedResourcesModel,
|
89
|
+
)
|
90
|
+
from zenml.utils.dashboard_utils import get_component_url, get_stack_url
|
57
91
|
from zenml.utils.io_utils import create_dir_recursive_if_not_exists
|
58
92
|
from zenml.utils.mlstacks_utils import (
|
59
93
|
convert_click_params_to_mlstacks_primitives,
|
@@ -93,7 +127,7 @@ def stack() -> None:
|
|
93
127
|
"artifact_store",
|
94
128
|
help="Name of the artifact store for this stack.",
|
95
129
|
type=str,
|
96
|
-
required=
|
130
|
+
required=False,
|
97
131
|
)
|
98
132
|
@click.option(
|
99
133
|
"-o",
|
@@ -101,7 +135,7 @@ def stack() -> None:
|
|
101
135
|
"orchestrator",
|
102
136
|
help="Name of the orchestrator for this stack.",
|
103
137
|
type=str,
|
104
|
-
required=
|
138
|
+
required=False,
|
105
139
|
)
|
106
140
|
@click.option(
|
107
141
|
"-c",
|
@@ -190,10 +224,24 @@ def stack() -> None:
|
|
190
224
|
help="Immediately set this stack as active.",
|
191
225
|
type=click.BOOL,
|
192
226
|
)
|
227
|
+
@click.option(
|
228
|
+
"-p",
|
229
|
+
"--provider",
|
230
|
+
help="Name of the cloud provider for this stack.",
|
231
|
+
type=click.Choice(["aws", "azure", "gcp"]),
|
232
|
+
required=False,
|
233
|
+
)
|
234
|
+
@click.option(
|
235
|
+
"-sc",
|
236
|
+
"--connector",
|
237
|
+
help="Name of the service connector for this stack.",
|
238
|
+
type=str,
|
239
|
+
required=False,
|
240
|
+
)
|
193
241
|
def register_stack(
|
194
242
|
stack_name: str,
|
195
|
-
artifact_store: str,
|
196
|
-
orchestrator: str,
|
243
|
+
artifact_store: Optional[str] = None,
|
244
|
+
orchestrator: Optional[str] = None,
|
197
245
|
container_registry: Optional[str] = None,
|
198
246
|
model_registry: Optional[str] = None,
|
199
247
|
step_operator: Optional[str] = None,
|
@@ -205,6 +253,8 @@ def register_stack(
|
|
205
253
|
data_validator: Optional[str] = None,
|
206
254
|
image_builder: Optional[str] = None,
|
207
255
|
set_stack: bool = False,
|
256
|
+
provider: Optional[str] = None,
|
257
|
+
connector: Optional[str] = None,
|
208
258
|
) -> None:
|
209
259
|
"""Register a stack.
|
210
260
|
|
@@ -223,44 +273,279 @@ def register_stack(
|
|
223
273
|
data_validator: Name of the data validator for this stack.
|
224
274
|
image_builder: Name of the new image builder for this stack.
|
225
275
|
set_stack: Immediately set this stack as active.
|
276
|
+
provider: Name of the cloud provider for this stack.
|
277
|
+
connector: Name of the service connector for this stack.
|
226
278
|
"""
|
227
|
-
|
228
|
-
|
279
|
+
if (provider is None and connector is None) and (
|
280
|
+
artifact_store is None or orchestrator is None
|
281
|
+
):
|
282
|
+
cli_utils.error(
|
283
|
+
"Only stack using service connector can be registered "
|
284
|
+
"without specifying an artifact store and an orchestrator. "
|
285
|
+
"Please specify the artifact store and the orchestrator or "
|
286
|
+
"the service connector or cloud type settings."
|
287
|
+
)
|
229
288
|
|
230
|
-
|
289
|
+
client = Client()
|
231
290
|
|
232
|
-
|
233
|
-
|
291
|
+
if provider is not None or connector is not None:
|
292
|
+
if client.zen_store.is_local_store():
|
293
|
+
cli_utils.error(
|
294
|
+
"You are registering a stack using a service connector, but "
|
295
|
+
"this feature cannot be used with a local ZenML deployment. "
|
296
|
+
"ZenML needs to be accessible from the cloud provider to allow the "
|
297
|
+
"stack and its components to be registered automatically. "
|
298
|
+
"Please deploy ZenML in a remote environment as described in the "
|
299
|
+
"documentation: https://docs.zenml.io/getting-started/deploying-zenml "
|
300
|
+
"or use a managed ZenML Pro server instance for quick access to "
|
301
|
+
"this feature and more: https://www.zenml.io/pro"
|
302
|
+
)
|
234
303
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
304
|
+
try:
|
305
|
+
client.get_stack(
|
306
|
+
name_id_or_prefix=stack_name,
|
307
|
+
allow_name_prefix_match=False,
|
308
|
+
)
|
309
|
+
cli_utils.error(
|
310
|
+
f"A stack with name `{stack_name}` already exists, "
|
311
|
+
"please use a different name."
|
312
|
+
)
|
313
|
+
except KeyError:
|
314
|
+
pass
|
315
|
+
|
316
|
+
labels: Dict[str, str] = {}
|
317
|
+
components: Dict[StackComponentType, Union[UUID, ComponentInfo]] = {}
|
318
|
+
# cloud flow
|
319
|
+
created_objects: Set[str] = set()
|
320
|
+
service_connector: Optional[Union[UUID, ServiceConnectorInfo]] = None
|
321
|
+
if provider is not None and connector is None:
|
322
|
+
service_connector_response = None
|
323
|
+
use_auto_configure = False
|
324
|
+
try:
|
325
|
+
service_connector_response, _ = client.create_service_connector(
|
326
|
+
name=stack_name,
|
327
|
+
connector_type=provider,
|
328
|
+
register=False,
|
329
|
+
auto_configure=True,
|
330
|
+
verify=False,
|
254
331
|
)
|
255
|
-
|
256
|
-
|
257
|
-
|
332
|
+
except Exception:
|
333
|
+
pass
|
334
|
+
if service_connector_response:
|
335
|
+
use_auto_configure = Confirm.ask(
|
336
|
+
f"[bold]{provider.upper()} cloud service connector[/bold] "
|
337
|
+
"has detected connection credentials in your environment.\n"
|
338
|
+
"Would you like to use these credentials or create a new "
|
339
|
+
"configuration by providing connection details?",
|
340
|
+
default=True,
|
341
|
+
show_choices=True,
|
342
|
+
show_default=True,
|
343
|
+
)
|
344
|
+
|
345
|
+
connector_selected: Optional[int] = None
|
346
|
+
if not use_auto_configure:
|
347
|
+
service_connector_response = None
|
348
|
+
existing_connectors = client.list_service_connectors(
|
349
|
+
connector_type=provider, size=100
|
350
|
+
)
|
351
|
+
if existing_connectors.total:
|
352
|
+
connector_selected = cli_utils.multi_choice_prompt(
|
353
|
+
object_type=f"{provider.upper()} service connectors",
|
354
|
+
choices=[
|
355
|
+
[connector.name]
|
356
|
+
for connector in existing_connectors.items
|
357
|
+
],
|
358
|
+
headers=["Name"],
|
359
|
+
prompt_text=f"We found these {provider.upper()} service connectors. "
|
360
|
+
"Do you want to create a new one or use one of the existing ones?",
|
361
|
+
default_choice="0",
|
362
|
+
allow_zero_be_a_new_object=True,
|
363
|
+
)
|
364
|
+
if use_auto_configure or connector_selected is None:
|
365
|
+
service_connector = _get_service_connector_info(
|
366
|
+
cloud_provider=provider,
|
367
|
+
connector_details=service_connector_response,
|
258
368
|
)
|
369
|
+
created_objects.add("service_connector")
|
370
|
+
else:
|
371
|
+
selected_connector = existing_connectors.items[connector_selected]
|
372
|
+
service_connector = selected_connector.id
|
373
|
+
connector = selected_connector.name
|
374
|
+
if isinstance(selected_connector.connector_type, str):
|
375
|
+
provider = selected_connector.connector_type
|
376
|
+
else:
|
377
|
+
provider = selected_connector.connector_type.connector_type
|
378
|
+
elif connector is not None:
|
379
|
+
service_connector_response = client.get_service_connector(connector)
|
380
|
+
service_connector = service_connector_response.id
|
381
|
+
if provider:
|
382
|
+
if service_connector_response.type != provider:
|
383
|
+
cli_utils.warning(
|
384
|
+
f"The service connector `{connector}` is not of type `{provider}`."
|
385
|
+
)
|
386
|
+
else:
|
387
|
+
provider = service_connector_response.type
|
388
|
+
|
389
|
+
if service_connector:
|
390
|
+
labels["zenml:wizard"] = "true"
|
391
|
+
if provider:
|
392
|
+
labels["zenml:provider"] = provider
|
393
|
+
service_connector_resource_model = None
|
394
|
+
can_generate_long_tokens = False
|
395
|
+
# create components
|
396
|
+
needed_components = (
|
397
|
+
(StackComponentType.ARTIFACT_STORE, artifact_store),
|
398
|
+
(StackComponentType.ORCHESTRATOR, orchestrator),
|
399
|
+
(StackComponentType.CONTAINER_REGISTRY, container_registry),
|
400
|
+
)
|
401
|
+
for component_type, preset_name in needed_components:
|
402
|
+
component_info: Optional[Union[UUID, ComponentInfo]] = None
|
403
|
+
if preset_name is not None:
|
404
|
+
component_response = client.get_stack_component(
|
405
|
+
component_type, preset_name
|
406
|
+
)
|
407
|
+
component_info = component_response.id
|
408
|
+
else:
|
409
|
+
if isinstance(service_connector, UUID):
|
410
|
+
# find existing components under same connector
|
411
|
+
existing_components = client.list_stack_components(
|
412
|
+
type=component_type.value,
|
413
|
+
connector_id=service_connector,
|
414
|
+
size=100,
|
415
|
+
)
|
416
|
+
# if some existing components are found - prompt user what to do
|
417
|
+
component_selected: Optional[int] = None
|
418
|
+
if existing_components.total > 0:
|
419
|
+
component_selected = cli_utils.multi_choice_prompt(
|
420
|
+
object_type=component_type.value.replace("_", " "),
|
421
|
+
choices=[
|
422
|
+
[component.name]
|
423
|
+
for component in existing_components.items
|
424
|
+
],
|
425
|
+
headers=["Name"],
|
426
|
+
prompt_text=f"We found these {component_type.value.replace('_', ' ')} "
|
427
|
+
"connected using the current service connector. Do you "
|
428
|
+
"want to create a new one or use existing one?",
|
429
|
+
default_choice="0",
|
430
|
+
allow_zero_be_a_new_object=True,
|
431
|
+
)
|
432
|
+
else:
|
433
|
+
component_selected = None
|
434
|
+
|
435
|
+
if component_selected is None:
|
436
|
+
if service_connector_resource_model is None:
|
437
|
+
with console.status(
|
438
|
+
"Exploring resources available to the service connector...\n"
|
439
|
+
):
|
440
|
+
if isinstance(service_connector, UUID):
|
441
|
+
service_connector_resource_model = (
|
442
|
+
client.verify_service_connector(
|
443
|
+
service_connector
|
444
|
+
)
|
445
|
+
)
|
446
|
+
existing_service_connector_info = (
|
447
|
+
client.get_service_connector(
|
448
|
+
service_connector
|
449
|
+
)
|
450
|
+
)
|
451
|
+
can_generate_long_tokens = not existing_service_connector_info.configuration.get(
|
452
|
+
"generate_temporary_tokens", True
|
453
|
+
)
|
454
|
+
else:
|
455
|
+
_, service_connector_resource_model = (
|
456
|
+
client.create_service_connector(
|
457
|
+
name=stack_name,
|
458
|
+
connector_type=service_connector.type,
|
459
|
+
auth_method=service_connector.auth_method,
|
460
|
+
configuration=service_connector.configuration,
|
461
|
+
register=False,
|
462
|
+
)
|
463
|
+
)
|
464
|
+
can_generate_long_tokens = True
|
465
|
+
if service_connector_resource_model is None:
|
466
|
+
cli_utils.error(
|
467
|
+
f"Failed to validate service connector {service_connector}..."
|
468
|
+
)
|
469
|
+
if provider is None:
|
470
|
+
if isinstance(
|
471
|
+
service_connector_resource_model.connector_type,
|
472
|
+
str,
|
473
|
+
):
|
474
|
+
provider = (
|
475
|
+
service_connector_resource_model.connector_type
|
476
|
+
)
|
477
|
+
else:
|
478
|
+
provider = service_connector_resource_model.connector_type.connector_type
|
479
|
+
|
480
|
+
component_info = _get_stack_component_info(
|
481
|
+
component_type=component_type.value,
|
482
|
+
cloud_provider=provider,
|
483
|
+
service_connector_resource_models=service_connector_resource_model.resources,
|
484
|
+
service_connector_index=0,
|
485
|
+
can_generate_long_tokens=can_generate_long_tokens,
|
486
|
+
)
|
487
|
+
component_name = stack_name
|
488
|
+
created_objects.add(component_type.value)
|
489
|
+
else:
|
490
|
+
selected_component = existing_components.items[
|
491
|
+
component_selected
|
492
|
+
]
|
493
|
+
component_info = selected_component.id
|
494
|
+
component_name = selected_component.name
|
495
|
+
|
496
|
+
components[component_type] = component_info
|
497
|
+
if component_type == StackComponentType.ARTIFACT_STORE:
|
498
|
+
artifact_store = component_name
|
499
|
+
if component_type == StackComponentType.ORCHESTRATOR:
|
500
|
+
orchestrator = component_name
|
501
|
+
if not isinstance(
|
502
|
+
component_info, UUID
|
503
|
+
) and component_info.flavor.startswith("vm"):
|
504
|
+
if isinstance(
|
505
|
+
service_connector, ServiceConnectorInfo
|
506
|
+
) and service_connector.auth_method in {
|
507
|
+
"service-account",
|
508
|
+
"external-account",
|
509
|
+
}:
|
510
|
+
service_connector.configuration[
|
511
|
+
"generate_temporary_tokens"
|
512
|
+
] = False
|
513
|
+
if component_type == StackComponentType.CONTAINER_REGISTRY:
|
514
|
+
container_registry = component_name
|
515
|
+
|
516
|
+
# normal flow once all components are defined
|
517
|
+
with console.status(f"Registering stack '{stack_name}'...\n"):
|
518
|
+
for component_type_, component_name_ in [
|
519
|
+
(StackComponentType.ARTIFACT_STORE, artifact_store),
|
520
|
+
(StackComponentType.ORCHESTRATOR, orchestrator),
|
521
|
+
(StackComponentType.ALERTER, alerter),
|
522
|
+
(StackComponentType.ANNOTATOR, annotator),
|
523
|
+
(StackComponentType.DATA_VALIDATOR, data_validator),
|
524
|
+
(StackComponentType.FEATURE_STORE, feature_store),
|
525
|
+
(StackComponentType.IMAGE_BUILDER, image_builder),
|
526
|
+
(StackComponentType.MODEL_DEPLOYER, model_deployer),
|
527
|
+
(StackComponentType.MODEL_REGISTRY, model_registry),
|
528
|
+
(StackComponentType.STEP_OPERATOR, step_operator),
|
529
|
+
(StackComponentType.EXPERIMENT_TRACKER, experiment_tracker),
|
530
|
+
(StackComponentType.CONTAINER_REGISTRY, container_registry),
|
531
|
+
]:
|
532
|
+
if component_name_ and component_type_ not in components:
|
533
|
+
components[component_type_] = client.get_stack_component(
|
534
|
+
component_type_, component_name_
|
535
|
+
).id
|
259
536
|
|
260
537
|
try:
|
261
|
-
created_stack = client.
|
262
|
-
|
263
|
-
|
538
|
+
created_stack = client.zen_store.create_full_stack(
|
539
|
+
full_stack=FullStackRequest(
|
540
|
+
user=client.active_user.id,
|
541
|
+
workspace=client.active_workspace.id,
|
542
|
+
name=stack_name,
|
543
|
+
components=components,
|
544
|
+
service_connectors=[service_connector]
|
545
|
+
if service_connector
|
546
|
+
else [],
|
547
|
+
labels=labels,
|
548
|
+
)
|
264
549
|
)
|
265
550
|
except (KeyError, IllegalOperationError) as err:
|
266
551
|
cli_utils.error(str(err))
|
@@ -268,6 +553,10 @@ def register_stack(
|
|
268
553
|
cli_utils.declare(
|
269
554
|
f"Stack '{created_stack.name}' successfully registered!"
|
270
555
|
)
|
556
|
+
cli_utils.print_stack_configuration(
|
557
|
+
stack=created_stack,
|
558
|
+
active=created_stack.id == client.active_stack_model.id,
|
559
|
+
)
|
271
560
|
|
272
561
|
if set_stack:
|
273
562
|
client.activate_stack(created_stack.id)
|
@@ -277,6 +566,30 @@ def register_stack(
|
|
277
566
|
f"Active {scope} stack set to:'{created_stack.name}'"
|
278
567
|
)
|
279
568
|
|
569
|
+
delete_commands = []
|
570
|
+
if "service_connector" in created_objects:
|
571
|
+
created_objects.remove("service_connector")
|
572
|
+
connectors = set()
|
573
|
+
for each in created_objects:
|
574
|
+
if comps_ := created_stack.components[StackComponentType(each)]:
|
575
|
+
if conn_ := comps_[0].connector:
|
576
|
+
connectors.add(conn_.name)
|
577
|
+
for connector in connectors:
|
578
|
+
delete_commands.append(
|
579
|
+
"zenml service-connector delete " + connector
|
580
|
+
)
|
581
|
+
for each in created_objects:
|
582
|
+
if comps_ := created_stack.components[StackComponentType(each)]:
|
583
|
+
delete_commands.append(
|
584
|
+
f"zenml {each.replace('_', '-')} delete {comps_[0].name}"
|
585
|
+
)
|
586
|
+
delete_commands.append("zenml stack delete -y " + created_stack.name)
|
587
|
+
|
588
|
+
Console().print(
|
589
|
+
"To delete the objects created by this command run, please run in a sequence:\n"
|
590
|
+
)
|
591
|
+
Console().print(Syntax("\n".join(delete_commands[::-1]), "bash"))
|
592
|
+
|
280
593
|
print_model_url(get_stack_url(created_stack))
|
281
594
|
|
282
595
|
|
@@ -1286,7 +1599,261 @@ def _get_deployment_params_interactively(
|
|
1286
1599
|
return deployment_values
|
1287
1600
|
|
1288
1601
|
|
1289
|
-
|
1602
|
+
def validate_name(ctx: click.Context, param: str, value: str) -> str:
|
1603
|
+
"""Validate the name of the stack.
|
1604
|
+
|
1605
|
+
Args:
|
1606
|
+
ctx: The click context.
|
1607
|
+
param: The parameter name.
|
1608
|
+
value: The value of the parameter.
|
1609
|
+
|
1610
|
+
Returns:
|
1611
|
+
The validated value.
|
1612
|
+
|
1613
|
+
Raises:
|
1614
|
+
BadParameter: If the name is invalid.
|
1615
|
+
"""
|
1616
|
+
if not value:
|
1617
|
+
return value
|
1618
|
+
|
1619
|
+
if not re.match(r"^[a-zA-Z0-9-]*$", value):
|
1620
|
+
raise click.BadParameter(
|
1621
|
+
"Stack name must contain only alphanumeric characters and hyphens."
|
1622
|
+
)
|
1623
|
+
|
1624
|
+
if len(value) > 16:
|
1625
|
+
raise click.BadParameter(
|
1626
|
+
"Stack name must have a maximum length of 16 characters."
|
1627
|
+
)
|
1628
|
+
|
1629
|
+
return value
|
1630
|
+
|
1631
|
+
|
1632
|
+
@stack.command(
|
1633
|
+
help="""Deploy a fully functional ZenML stack in one of the cloud providers.
|
1634
|
+
|
1635
|
+
Running this command will initiate an assisted process that will walk you
|
1636
|
+
through automatically provisioning all the cloud infrastructure resources
|
1637
|
+
necessary for a fully functional ZenML stack in the cloud provider of your
|
1638
|
+
choice. A corresponding ZenML stack will also be automatically registered along
|
1639
|
+
with all the necessary components and properly authenticated through service
|
1640
|
+
connectors.
|
1641
|
+
"""
|
1642
|
+
)
|
1643
|
+
@click.option(
|
1644
|
+
"--provider",
|
1645
|
+
"-p",
|
1646
|
+
"provider",
|
1647
|
+
required=True,
|
1648
|
+
type=click.Choice(StackDeploymentProvider.values()),
|
1649
|
+
)
|
1650
|
+
@click.option(
|
1651
|
+
"--name",
|
1652
|
+
"-n",
|
1653
|
+
"stack_name",
|
1654
|
+
type=click.STRING,
|
1655
|
+
required=False,
|
1656
|
+
help="Custom string to use as a prefix to generate names for the ZenML "
|
1657
|
+
"stack, its components service connectors as well as provisioned cloud "
|
1658
|
+
"infrastructure resources. May only contain alphanumeric characters and "
|
1659
|
+
"hyphens and have a maximum length of 16 characters.",
|
1660
|
+
callback=validate_name,
|
1661
|
+
)
|
1662
|
+
@click.option(
|
1663
|
+
"--location",
|
1664
|
+
"-l",
|
1665
|
+
type=click.STRING,
|
1666
|
+
required=False,
|
1667
|
+
help="The location to deploy the stack to.",
|
1668
|
+
)
|
1669
|
+
@click.option(
|
1670
|
+
"--set",
|
1671
|
+
"set_stack",
|
1672
|
+
is_flag=True,
|
1673
|
+
help="Immediately set this stack as active.",
|
1674
|
+
type=click.BOOL,
|
1675
|
+
)
|
1676
|
+
@click.pass_context
|
1677
|
+
def deploy(
|
1678
|
+
ctx: click.Context,
|
1679
|
+
provider: str,
|
1680
|
+
stack_name: Optional[str] = None,
|
1681
|
+
location: Optional[str] = None,
|
1682
|
+
set_stack: bool = False,
|
1683
|
+
) -> None:
|
1684
|
+
"""Deploy and register a fully functional cloud ZenML stack.
|
1685
|
+
|
1686
|
+
Args:
|
1687
|
+
ctx: The click context.
|
1688
|
+
provider: The cloud provider to deploy the stack to.
|
1689
|
+
stack_name: A name for the ZenML stack that gets imported as a result
|
1690
|
+
of the recipe deployment.
|
1691
|
+
location: The location to deploy the stack to.
|
1692
|
+
set_stack: Immediately set the deployed stack as active.
|
1693
|
+
|
1694
|
+
Raises:
|
1695
|
+
Abort: If the user aborts the deployment.
|
1696
|
+
KeyboardInterrupt: If the user interrupts the deployment.
|
1697
|
+
"""
|
1698
|
+
stack_name = stack_name or f"zenml-{provider}-stack"
|
1699
|
+
|
1700
|
+
# Set up the markdown renderer to use the old-school markdown heading
|
1701
|
+
Markdown.elements.update(
|
1702
|
+
{
|
1703
|
+
"heading_open": OldSchoolMarkdownHeading,
|
1704
|
+
}
|
1705
|
+
)
|
1706
|
+
|
1707
|
+
client = Client()
|
1708
|
+
if client.zen_store.is_local_store():
|
1709
|
+
cli_utils.error(
|
1710
|
+
"This feature cannot be used with a local ZenML deployment. "
|
1711
|
+
"ZenML needs to be accessible from the cloud provider to allow the "
|
1712
|
+
"stack and its components to be registered automatically. "
|
1713
|
+
"Please deploy ZenML in a remote environment as described in the "
|
1714
|
+
"documentation: https://docs.zenml.io/getting-started/deploying-zenml "
|
1715
|
+
"or use a managed ZenML Pro server instance for quick access to "
|
1716
|
+
"this feature and more: https://www.zenml.io/pro"
|
1717
|
+
)
|
1718
|
+
|
1719
|
+
with track_handler(
|
1720
|
+
event=AnalyticsEvent.DEPLOY_FULL_STACK,
|
1721
|
+
) as analytics_handler:
|
1722
|
+
analytics_handler.metadata = {
|
1723
|
+
"provider": provider,
|
1724
|
+
}
|
1725
|
+
|
1726
|
+
deployment = client.zen_store.get_stack_deployment_info(
|
1727
|
+
provider=StackDeploymentProvider(provider),
|
1728
|
+
)
|
1729
|
+
|
1730
|
+
if location and location not in deployment.locations.values():
|
1731
|
+
cli_utils.error(
|
1732
|
+
f"Invalid location '{location}' for provider '{provider}'. "
|
1733
|
+
f"Valid locations are: {', '.join(deployment.locations.values())}"
|
1734
|
+
)
|
1735
|
+
|
1736
|
+
console.print(
|
1737
|
+
Markdown(
|
1738
|
+
f"# {provider.upper()} ZenML Cloud Stack Deployment\n"
|
1739
|
+
+ deployment.description
|
1740
|
+
)
|
1741
|
+
)
|
1742
|
+
console.print(Markdown("## Instructions\n" + deployment.instructions))
|
1743
|
+
|
1744
|
+
deployment_config = client.zen_store.get_stack_deployment_config(
|
1745
|
+
provider=StackDeploymentProvider(provider),
|
1746
|
+
stack_name=stack_name,
|
1747
|
+
location=location,
|
1748
|
+
)
|
1749
|
+
|
1750
|
+
if deployment_config.configuration:
|
1751
|
+
console.print(
|
1752
|
+
Markdown(
|
1753
|
+
"## Configuration\n"
|
1754
|
+
"You will be asked to provide the following configuration "
|
1755
|
+
"values during the deployment process:\n"
|
1756
|
+
)
|
1757
|
+
)
|
1758
|
+
|
1759
|
+
console.print(
|
1760
|
+
"\n",
|
1761
|
+
deployment_config.configuration,
|
1762
|
+
no_wrap=True,
|
1763
|
+
overflow="ignore",
|
1764
|
+
crop=False,
|
1765
|
+
style=Style(bgcolor="grey15"),
|
1766
|
+
)
|
1767
|
+
|
1768
|
+
if not cli_utils.confirmation(
|
1769
|
+
"\n\nProceed to continue with the deployment. You will be "
|
1770
|
+
f"automatically redirected to {provider.upper()} in your browser.",
|
1771
|
+
):
|
1772
|
+
raise click.Abort()
|
1773
|
+
|
1774
|
+
date_start = datetime.utcnow()
|
1775
|
+
|
1776
|
+
webbrowser.open(deployment_config.deployment_url)
|
1777
|
+
console.print(
|
1778
|
+
Markdown(
|
1779
|
+
f"If your browser did not open automatically, please open "
|
1780
|
+
f"the following URL into your browser to deploy the stack to "
|
1781
|
+
f"{provider.upper()}: "
|
1782
|
+
f"[{deployment_config.deployment_url_text}]"
|
1783
|
+
f"({deployment_config.deployment_url}).\n\n"
|
1784
|
+
)
|
1785
|
+
)
|
1786
|
+
|
1787
|
+
try:
|
1788
|
+
cli_utils.declare(
|
1789
|
+
"\n\nWaiting for the deployment to complete and the stack to be "
|
1790
|
+
"registered. Press CTRL+C to abort...\n"
|
1791
|
+
)
|
1792
|
+
|
1793
|
+
while True:
|
1794
|
+
deployed_stack = client.zen_store.get_stack_deployment_stack(
|
1795
|
+
provider=StackDeploymentProvider(provider),
|
1796
|
+
stack_name=stack_name,
|
1797
|
+
location=location,
|
1798
|
+
date_start=date_start,
|
1799
|
+
)
|
1800
|
+
if deployed_stack:
|
1801
|
+
break
|
1802
|
+
time.sleep(10)
|
1803
|
+
|
1804
|
+
analytics_handler.metadata.update(
|
1805
|
+
{
|
1806
|
+
"stack_id": deployed_stack.stack.id,
|
1807
|
+
}
|
1808
|
+
)
|
1809
|
+
|
1810
|
+
except KeyboardInterrupt:
|
1811
|
+
cli_utils.declare("Stack deployment aborted.")
|
1812
|
+
raise
|
1813
|
+
|
1814
|
+
stack_desc = f"""## Stack successfully registered! 🚀
|
1815
|
+
Stack [{deployed_stack.stack.name}]({get_stack_url(deployed_stack.stack)}):\n"""
|
1816
|
+
|
1817
|
+
for component_type, components in deployed_stack.stack.components.items():
|
1818
|
+
if components:
|
1819
|
+
component = components[0]
|
1820
|
+
stack_desc += (
|
1821
|
+
f" * `{component.flavor}` {component_type.value}: "
|
1822
|
+
f"[{component.name}]({get_component_url(component)})\n"
|
1823
|
+
)
|
1824
|
+
|
1825
|
+
if deployed_stack.service_connector:
|
1826
|
+
stack_desc += (
|
1827
|
+
f" * Service Connector: {deployed_stack.service_connector.name}\n"
|
1828
|
+
)
|
1829
|
+
|
1830
|
+
console.print(Markdown(stack_desc))
|
1831
|
+
|
1832
|
+
follow_up = f"""
|
1833
|
+
## Follow-up
|
1834
|
+
|
1835
|
+
{deployment.post_deploy_instructions}
|
1836
|
+
|
1837
|
+
To use the `{deployed_stack.stack.name}` stack to run pipelines:
|
1838
|
+
|
1839
|
+
* install the required ZenML integrations by running: `zenml integration install {" ".join(deployment.integrations)}`
|
1840
|
+
"""
|
1841
|
+
if set_stack:
|
1842
|
+
client.activate_stack(deployed_stack.stack.id)
|
1843
|
+
follow_up += f"""
|
1844
|
+
* the `{deployed_stack.stack.name}` stack has already been set as active
|
1845
|
+
"""
|
1846
|
+
else:
|
1847
|
+
follow_up += f"""
|
1848
|
+
* set the `{deployed_stack.stack.name}` stack as active by running: `zenml stack set {deployed_stack.stack.name}`
|
1849
|
+
"""
|
1850
|
+
|
1851
|
+
console.print(
|
1852
|
+
Markdown(follow_up),
|
1853
|
+
)
|
1854
|
+
|
1855
|
+
|
1856
|
+
@stack.command(help="[DEPRECATED] Deploy a stack using mlstacks.")
|
1290
1857
|
@click.option(
|
1291
1858
|
"--provider",
|
1292
1859
|
"-p",
|
@@ -1426,7 +1993,7 @@ def _get_deployment_params_interactively(
|
|
1426
1993
|
help="Deploy the stack interactively.",
|
1427
1994
|
)
|
1428
1995
|
@click.pass_context
|
1429
|
-
def
|
1996
|
+
def deploy_mlstack(
|
1430
1997
|
ctx: click.Context,
|
1431
1998
|
provider: str,
|
1432
1999
|
stack_name: str,
|
@@ -1476,6 +2043,13 @@ def deploy(
|
|
1476
2043
|
region: The region to deploy the stack to.
|
1477
2044
|
interactive: Deploy the stack interactively.
|
1478
2045
|
"""
|
2046
|
+
cli_utils.warning(
|
2047
|
+
"The `zenml stack deploy-mlstack` (former `zenml stack deploy`) CLI "
|
2048
|
+
"command has been deprecated and will be removed in a future release. "
|
2049
|
+
"Please use `zenml stack deploy` instead for a simplified "
|
2050
|
+
"experience."
|
2051
|
+
)
|
2052
|
+
|
1479
2053
|
with track_handler(
|
1480
2054
|
event=AnalyticsEvent.DEPLOY_STACK,
|
1481
2055
|
) as analytics_handler:
|
@@ -1727,3 +2301,336 @@ def connect_stack(
|
|
1727
2301
|
interactive=interactive,
|
1728
2302
|
no_verify=no_verify,
|
1729
2303
|
)
|
2304
|
+
|
2305
|
+
|
2306
|
+
def _get_service_connector_info(
|
2307
|
+
cloud_provider: str,
|
2308
|
+
connector_details: Optional[
|
2309
|
+
Union[ServiceConnectorResponse, ServiceConnectorRequest]
|
2310
|
+
],
|
2311
|
+
) -> ServiceConnectorInfo:
|
2312
|
+
"""Get a service connector info with given cloud provider.
|
2313
|
+
|
2314
|
+
Args:
|
2315
|
+
cloud_provider: The cloud provider to use.
|
2316
|
+
connector_details: Whether to use implicit credentials.
|
2317
|
+
|
2318
|
+
Returns:
|
2319
|
+
The info model of the created service connector.
|
2320
|
+
|
2321
|
+
Raises:
|
2322
|
+
ValueError: If the cloud provider is not supported.
|
2323
|
+
"""
|
2324
|
+
from rich.prompt import Prompt
|
2325
|
+
|
2326
|
+
if cloud_provider not in {"aws", "gcp"}:
|
2327
|
+
raise ValueError(f"Unknown cloud provider {cloud_provider}")
|
2328
|
+
|
2329
|
+
client = Client()
|
2330
|
+
auth_methods = client.get_service_connector_type(
|
2331
|
+
cloud_provider
|
2332
|
+
).auth_method_dict
|
2333
|
+
if not connector_details:
|
2334
|
+
fixed_auth_methods = list(
|
2335
|
+
[
|
2336
|
+
(key, value)
|
2337
|
+
for key, value in auth_methods.items()
|
2338
|
+
if key != "implicit"
|
2339
|
+
]
|
2340
|
+
)
|
2341
|
+
choices = []
|
2342
|
+
headers = ["Name", "Required"]
|
2343
|
+
for _, value in fixed_auth_methods:
|
2344
|
+
schema = value.config_schema
|
2345
|
+
required = ""
|
2346
|
+
for each_req in schema["required"]:
|
2347
|
+
field = schema["properties"][each_req]
|
2348
|
+
required += f"[bold]{each_req}[/bold] [italic]({field.get('title','no description')})[/italic]\n"
|
2349
|
+
choices.append([value.name, required])
|
2350
|
+
|
2351
|
+
selected_auth_idx = cli_utils.multi_choice_prompt(
|
2352
|
+
object_type=f"authentication methods for {cloud_provider}",
|
2353
|
+
choices=choices,
|
2354
|
+
headers=headers,
|
2355
|
+
prompt_text="Please choose one of the authentication option above",
|
2356
|
+
)
|
2357
|
+
if selected_auth_idx is None:
|
2358
|
+
cli_utils.error("No authentication method selected.")
|
2359
|
+
auth_type = fixed_auth_methods[selected_auth_idx][0]
|
2360
|
+
else:
|
2361
|
+
auth_type = connector_details.auth_method
|
2362
|
+
|
2363
|
+
selected_auth_model = auth_methods[auth_type]
|
2364
|
+
|
2365
|
+
required_fields = selected_auth_model.config_schema["required"]
|
2366
|
+
properties = selected_auth_model.config_schema["properties"]
|
2367
|
+
|
2368
|
+
answers = {}
|
2369
|
+
for req_field in required_fields:
|
2370
|
+
if connector_details:
|
2371
|
+
if conf_value := connector_details.configuration.get(
|
2372
|
+
req_field, None
|
2373
|
+
):
|
2374
|
+
answers[req_field] = conf_value
|
2375
|
+
elif secret_value := connector_details.secrets.get(
|
2376
|
+
req_field, None
|
2377
|
+
):
|
2378
|
+
answers[req_field] = secret_value.get_secret_value()
|
2379
|
+
if req_field not in answers:
|
2380
|
+
answers[req_field] = Prompt.ask(
|
2381
|
+
f"Please enter value for `{req_field}`:",
|
2382
|
+
password="format" in properties[req_field]
|
2383
|
+
and properties[req_field]["format"] == "password",
|
2384
|
+
)
|
2385
|
+
|
2386
|
+
return ServiceConnectorInfo(
|
2387
|
+
type=cloud_provider,
|
2388
|
+
auth_method=auth_type,
|
2389
|
+
configuration=answers,
|
2390
|
+
)
|
2391
|
+
|
2392
|
+
|
2393
|
+
def _get_stack_component_info(
|
2394
|
+
component_type: str,
|
2395
|
+
cloud_provider: str,
|
2396
|
+
service_connector_resource_models: List[
|
2397
|
+
ServiceConnectorTypedResourcesModel
|
2398
|
+
],
|
2399
|
+
can_generate_long_tokens: bool,
|
2400
|
+
service_connector_index: Optional[int] = None,
|
2401
|
+
) -> ComponentInfo:
|
2402
|
+
"""Get a stack component info with given type and service connector.
|
2403
|
+
|
2404
|
+
Args:
|
2405
|
+
component_type: The type of component to create.
|
2406
|
+
cloud_provider: The cloud provider to use.
|
2407
|
+
service_connector_resource_models: The list of the available service connector resource models.
|
2408
|
+
can_generate_long_tokens: Whether connector can generate long-living tokens.
|
2409
|
+
service_connector_index: The index of the service connector to use.
|
2410
|
+
|
2411
|
+
Returns:
|
2412
|
+
The info model of the stack component.
|
2413
|
+
|
2414
|
+
Raises:
|
2415
|
+
ValueError: If the cloud provider is not supported.
|
2416
|
+
ValueError: If the component type is not supported.
|
2417
|
+
"""
|
2418
|
+
from rich.prompt import Prompt
|
2419
|
+
|
2420
|
+
if cloud_provider not in {"aws", "azure", "gcp"}:
|
2421
|
+
raise ValueError(f"Unknown cloud provider {cloud_provider}")
|
2422
|
+
|
2423
|
+
AWS_DOCS = (
|
2424
|
+
"https://docs.zenml.io/how-to/auth-management/aws-service-connector"
|
2425
|
+
)
|
2426
|
+
GCP_DOCS = (
|
2427
|
+
"https://docs.zenml.io/how-to/auth-management/gcp-service-connector"
|
2428
|
+
)
|
2429
|
+
|
2430
|
+
flavor = "undefined"
|
2431
|
+
service_connector_resource_id = None
|
2432
|
+
config = {}
|
2433
|
+
if component_type == "artifact_store":
|
2434
|
+
available_storages: List[str] = []
|
2435
|
+
if cloud_provider == "aws":
|
2436
|
+
for each in service_connector_resource_models:
|
2437
|
+
if each.resource_type == "s3-bucket":
|
2438
|
+
available_storages = each.resource_ids or []
|
2439
|
+
flavor = "s3"
|
2440
|
+
if not available_storages:
|
2441
|
+
cli_utils.error(
|
2442
|
+
"We were unable to find any S3 buckets available "
|
2443
|
+
"to configured service connector. Please, verify "
|
2444
|
+
"that needed permission are granted for the "
|
2445
|
+
"service connector.\nDocumentation for the S3 "
|
2446
|
+
"Buckets configuration can be found at "
|
2447
|
+
f"{AWS_DOCS}#s3-bucket"
|
2448
|
+
)
|
2449
|
+
elif cloud_provider == "azure":
|
2450
|
+
flavor = "azure"
|
2451
|
+
elif cloud_provider == "gcp":
|
2452
|
+
flavor = "gcp"
|
2453
|
+
for each in service_connector_resource_models:
|
2454
|
+
if each.resource_type == "gcs-bucket":
|
2455
|
+
available_storages = each.resource_ids or []
|
2456
|
+
if not available_storages:
|
2457
|
+
cli_utils.error(
|
2458
|
+
"We were unable to find any GCS buckets available "
|
2459
|
+
"to configured service connector. Please, verify "
|
2460
|
+
"that needed permission are granted for the "
|
2461
|
+
"service connector.\nDocumentation for the GCS "
|
2462
|
+
"Buckets configuration can be found at "
|
2463
|
+
f"{GCP_DOCS}#gcs-bucket"
|
2464
|
+
)
|
2465
|
+
|
2466
|
+
selected_storage_idx = cli_utils.multi_choice_prompt(
|
2467
|
+
object_type=f"{cloud_provider.upper()} storages",
|
2468
|
+
choices=[[st] for st in available_storages],
|
2469
|
+
headers=["Storage"],
|
2470
|
+
prompt_text="Please choose one of the storages for the new artifact store:",
|
2471
|
+
)
|
2472
|
+
if selected_storage_idx is None:
|
2473
|
+
cli_utils.error("No storage selected.")
|
2474
|
+
|
2475
|
+
selected_storage = available_storages[selected_storage_idx]
|
2476
|
+
|
2477
|
+
config = {"path": selected_storage}
|
2478
|
+
service_connector_resource_id = selected_storage
|
2479
|
+
elif component_type == "orchestrator":
|
2480
|
+
|
2481
|
+
def query_gcp_region(compute_type: str) -> str:
|
2482
|
+
region = Prompt.ask(
|
2483
|
+
f"Select the location for your {compute_type}:",
|
2484
|
+
choices=sorted(
|
2485
|
+
Client()
|
2486
|
+
.zen_store.get_stack_deployment_info(
|
2487
|
+
StackDeploymentProvider.GCP
|
2488
|
+
)
|
2489
|
+
.locations.values()
|
2490
|
+
),
|
2491
|
+
show_choices=True,
|
2492
|
+
)
|
2493
|
+
return region
|
2494
|
+
|
2495
|
+
if cloud_provider == "aws":
|
2496
|
+
available_orchestrators = []
|
2497
|
+
for each in service_connector_resource_models:
|
2498
|
+
types = []
|
2499
|
+
if each.resource_type == "aws-generic":
|
2500
|
+
types = ["Sagemaker"]
|
2501
|
+
if can_generate_long_tokens:
|
2502
|
+
types.append("Skypilot (EC2)")
|
2503
|
+
if each.resource_type == "kubernetes-cluster":
|
2504
|
+
types = ["Kubernetes"]
|
2505
|
+
|
2506
|
+
if each.resource_ids:
|
2507
|
+
for orchestrator in each.resource_ids:
|
2508
|
+
for t in types:
|
2509
|
+
available_orchestrators.append([t, orchestrator])
|
2510
|
+
if not available_orchestrators:
|
2511
|
+
cli_utils.error(
|
2512
|
+
"We were unable to find any orchestrator engines "
|
2513
|
+
"available to the service connector. Please, verify "
|
2514
|
+
"that needed permission are granted for the "
|
2515
|
+
"service connector.\nDocumentation for the Generic "
|
2516
|
+
"AWS resource configuration can be found at "
|
2517
|
+
f"{AWS_DOCS}#generic-aws-resource\n"
|
2518
|
+
"Documentation for the Kubernetes resource "
|
2519
|
+
"configuration can be found at "
|
2520
|
+
f"{AWS_DOCS}#eks-kubernetes-cluster"
|
2521
|
+
)
|
2522
|
+
elif cloud_provider == "gcp":
|
2523
|
+
available_orchestrators = []
|
2524
|
+
for each in service_connector_resource_models:
|
2525
|
+
types = []
|
2526
|
+
if each.resource_type == "gcp-generic":
|
2527
|
+
types = ["Vertex AI"]
|
2528
|
+
if can_generate_long_tokens:
|
2529
|
+
types.append("Skypilot (Compute)")
|
2530
|
+
if each.resource_type == "kubernetes-cluster":
|
2531
|
+
types = ["Kubernetes"]
|
2532
|
+
|
2533
|
+
if each.resource_ids:
|
2534
|
+
for orchestrator in each.resource_ids:
|
2535
|
+
for t in types:
|
2536
|
+
available_orchestrators.append([t, orchestrator])
|
2537
|
+
if not available_orchestrators:
|
2538
|
+
cli_utils.error(
|
2539
|
+
"We were unable to find any orchestrator engines "
|
2540
|
+
"available to the service connector. Please, verify "
|
2541
|
+
"that needed permission are granted for the "
|
2542
|
+
"service connector.\nDocumentation for the Generic "
|
2543
|
+
"GCP resource configuration can be found at "
|
2544
|
+
f"{GCP_DOCS}#generic-gcp-resource\n"
|
2545
|
+
"Documentation for the GKE Kubernetes resource "
|
2546
|
+
"configuration can be found at "
|
2547
|
+
f"{GCP_DOCS}#gke-kubernetes-cluster"
|
2548
|
+
)
|
2549
|
+
elif cloud_provider == "azure":
|
2550
|
+
pass
|
2551
|
+
|
2552
|
+
selected_orchestrator_idx = cli_utils.multi_choice_prompt(
|
2553
|
+
object_type=f"orchestrators on {cloud_provider.upper()}",
|
2554
|
+
choices=available_orchestrators,
|
2555
|
+
headers=["Orchestrator Type", "Orchestrator details"],
|
2556
|
+
prompt_text="Please choose one of the orchestrators for the new orchestrator:",
|
2557
|
+
)
|
2558
|
+
if selected_orchestrator_idx is None:
|
2559
|
+
cli_utils.error("No orchestrator selected.")
|
2560
|
+
|
2561
|
+
selected_orchestrator = available_orchestrators[
|
2562
|
+
selected_orchestrator_idx
|
2563
|
+
]
|
2564
|
+
|
2565
|
+
config = {}
|
2566
|
+
if selected_orchestrator[0] == "Sagemaker":
|
2567
|
+
flavor = "sagemaker"
|
2568
|
+
execution_role = Prompt.ask("Enter an execution role ARN:")
|
2569
|
+
config["execution_role"] = execution_role
|
2570
|
+
elif selected_orchestrator[0] == "Skypilot (EC2)":
|
2571
|
+
flavor = "vm_aws"
|
2572
|
+
config["region"] = selected_orchestrator[1]
|
2573
|
+
elif selected_orchestrator[0] == "Skypilot (Compute)":
|
2574
|
+
flavor = "vm_gcp"
|
2575
|
+
config["region"] = query_gcp_region("Skypilot cluster")
|
2576
|
+
elif selected_orchestrator[0] == "Vertex AI":
|
2577
|
+
flavor = "vertex"
|
2578
|
+
config["location"] = query_gcp_region("Vertex AI job")
|
2579
|
+
elif selected_orchestrator[0] == "Kubernetes":
|
2580
|
+
flavor = "kubernetes"
|
2581
|
+
else:
|
2582
|
+
raise ValueError(
|
2583
|
+
f"Unknown orchestrator type {selected_orchestrator[0]}"
|
2584
|
+
)
|
2585
|
+
service_connector_resource_id = selected_orchestrator[1]
|
2586
|
+
elif component_type == "container_registry":
|
2587
|
+
|
2588
|
+
def _get_registries(registry_name: str, docs_link: str) -> List[str]:
|
2589
|
+
available_registries: List[str] = []
|
2590
|
+
for each in service_connector_resource_models:
|
2591
|
+
if each.resource_type == "docker-registry":
|
2592
|
+
available_registries = each.resource_ids or []
|
2593
|
+
if not available_registries:
|
2594
|
+
cli_utils.error(
|
2595
|
+
"We were unable to find any container registries "
|
2596
|
+
"available to the service connector. Please, verify "
|
2597
|
+
"that needed permission are granted for the "
|
2598
|
+
f"service connector.\nDocumentation for the {registry_name} "
|
2599
|
+
"container registry resource configuration can "
|
2600
|
+
f"be found at {docs_link}"
|
2601
|
+
)
|
2602
|
+
return available_registries
|
2603
|
+
|
2604
|
+
if cloud_provider == "aws":
|
2605
|
+
flavor = "aws"
|
2606
|
+
available_registries = _get_registries(
|
2607
|
+
"ECR", f"{AWS_DOCS}#ecr-container-registry"
|
2608
|
+
)
|
2609
|
+
if cloud_provider == "gcp":
|
2610
|
+
flavor = "gcp"
|
2611
|
+
available_registries = _get_registries(
|
2612
|
+
"GCR", f"{GCP_DOCS}#gcr-container-registry"
|
2613
|
+
)
|
2614
|
+
if cloud_provider == "azure":
|
2615
|
+
flavor = "azure"
|
2616
|
+
|
2617
|
+
selected_registry_idx = cli_utils.multi_choice_prompt(
|
2618
|
+
object_type=f"{cloud_provider.upper()} registries",
|
2619
|
+
choices=[[st] for st in available_registries],
|
2620
|
+
headers=["Container Registry"],
|
2621
|
+
prompt_text="Please choose one of the registries for the new container registry:",
|
2622
|
+
)
|
2623
|
+
if selected_registry_idx is None:
|
2624
|
+
cli_utils.error("No container registry selected.")
|
2625
|
+
selected_registry = available_registries[selected_registry_idx]
|
2626
|
+
config = {"uri": selected_registry}
|
2627
|
+
service_connector_resource_id = selected_registry
|
2628
|
+
else:
|
2629
|
+
raise ValueError(f"Unknown component type {component_type}")
|
2630
|
+
|
2631
|
+
return ComponentInfo(
|
2632
|
+
flavor=flavor,
|
2633
|
+
configuration=config,
|
2634
|
+
service_connector_index=service_connector_index,
|
2635
|
+
service_connector_resource_id=service_connector_resource_id,
|
2636
|
+
)
|