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.
Files changed (266) hide show
  1. README.md +31 -10
  2. RELEASE_NOTES.md +280 -0
  3. zenml/VERSION +1 -1
  4. zenml/__init__.py +2 -0
  5. zenml/analytics/enums.py +3 -0
  6. zenml/cli/__init__.py +28 -0
  7. zenml/cli/artifact.py +1 -2
  8. zenml/cli/integration.py +9 -8
  9. zenml/cli/server.py +6 -0
  10. zenml/cli/stack.py +812 -39
  11. zenml/cli/stack_components.py +9 -0
  12. zenml/cli/text_utils.py +35 -1
  13. zenml/cli/utils.py +127 -10
  14. zenml/client.py +23 -14
  15. zenml/config/docker_settings.py +8 -5
  16. zenml/constants.py +13 -1
  17. zenml/container_registries/base_container_registry.py +1 -0
  18. zenml/enums.py +23 -0
  19. zenml/event_hub/event_hub.py +5 -8
  20. zenml/integrations/__init__.py +1 -0
  21. zenml/integrations/aws/__init__.py +1 -0
  22. zenml/integrations/azure/__init__.py +3 -2
  23. zenml/integrations/constants.py +1 -0
  24. zenml/integrations/databricks/__init__.py +52 -0
  25. zenml/integrations/databricks/flavors/__init__.py +30 -0
  26. zenml/integrations/databricks/flavors/databricks_model_deployer_flavor.py +118 -0
  27. zenml/integrations/databricks/flavors/databricks_orchestrator_flavor.py +147 -0
  28. zenml/integrations/databricks/model_deployers/__init__.py +20 -0
  29. zenml/integrations/databricks/model_deployers/databricks_model_deployer.py +249 -0
  30. zenml/integrations/databricks/orchestrators/__init__.py +20 -0
  31. zenml/integrations/databricks/orchestrators/databricks_orchestrator.py +497 -0
  32. zenml/integrations/databricks/orchestrators/databricks_orchestrator_entrypoint_config.py +97 -0
  33. zenml/integrations/databricks/services/__init__.py +19 -0
  34. zenml/integrations/databricks/services/databricks_deployment.py +407 -0
  35. zenml/integrations/databricks/utils/__init__.py +14 -0
  36. zenml/integrations/databricks/utils/databricks_utils.py +87 -0
  37. zenml/integrations/deepchecks/__init__.py +1 -0
  38. zenml/integrations/discord/__init__.py +1 -0
  39. zenml/integrations/evidently/__init__.py +1 -0
  40. zenml/integrations/facets/__init__.py +1 -0
  41. zenml/integrations/feast/__init__.py +1 -0
  42. zenml/integrations/gcp/__init__.py +3 -1
  43. zenml/integrations/gcp/google_credentials_mixin.py +1 -1
  44. zenml/integrations/gcp/service_connectors/gcp_service_connector.py +320 -64
  45. zenml/integrations/great_expectations/data_validators/ge_data_validator.py +12 -8
  46. zenml/integrations/huggingface/__init__.py +1 -0
  47. zenml/integrations/huggingface/materializers/huggingface_datasets_materializer.py +88 -3
  48. zenml/integrations/huggingface/steps/accelerate_runner.py +1 -7
  49. zenml/integrations/integration.py +24 -0
  50. zenml/integrations/kubeflow/__init__.py +3 -0
  51. zenml/integrations/kubeflow/flavors/kubeflow_orchestrator_flavor.py +1 -1
  52. zenml/integrations/kubeflow/orchestrators/kubeflow_orchestrator.py +0 -1
  53. zenml/integrations/kubernetes/__init__.py +3 -1
  54. zenml/integrations/kubernetes/orchestrators/kube_utils.py +4 -1
  55. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +1 -13
  56. zenml/integrations/kubernetes/orchestrators/manifest_utils.py +22 -4
  57. zenml/integrations/kubernetes/pod_settings.py +4 -0
  58. zenml/integrations/label_studio/annotators/label_studio_annotator.py +1 -0
  59. zenml/integrations/langchain/__init__.py +1 -0
  60. zenml/integrations/lightgbm/__init__.py +1 -0
  61. zenml/integrations/mlflow/__init__.py +4 -2
  62. zenml/integrations/mlflow/model_registries/mlflow_model_registry.py +6 -2
  63. zenml/integrations/mlflow/services/mlflow_deployment.py +1 -1
  64. zenml/integrations/neural_prophet/__init__.py +1 -0
  65. zenml/integrations/polars/__init__.py +1 -0
  66. zenml/integrations/prodigy/__init__.py +1 -0
  67. zenml/integrations/pycaret/__init__.py +6 -0
  68. zenml/integrations/registry.py +37 -0
  69. zenml/integrations/s3/artifact_stores/s3_artifact_store.py +17 -6
  70. zenml/integrations/seldon/__init__.py +1 -0
  71. zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -0
  72. zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +2 -2
  73. zenml/integrations/skypilot/orchestrators/skypilot_base_vm_orchestrator.py +1 -1
  74. zenml/integrations/skypilot/orchestrators/skypilot_orchestrator_entrypoint.py +2 -2
  75. zenml/integrations/skypilot_aws/__init__.py +2 -1
  76. zenml/integrations/skypilot_azure/__init__.py +1 -3
  77. zenml/integrations/skypilot_gcp/__init__.py +1 -1
  78. zenml/integrations/skypilot_lambda/__init__.py +1 -1
  79. zenml/integrations/skypilot_lambda/flavors/skypilot_orchestrator_lambda_vm_flavor.py +1 -1
  80. zenml/integrations/slack/__init__.py +1 -0
  81. zenml/integrations/tekton/__init__.py +1 -0
  82. zenml/integrations/tensorboard/__init__.py +0 -1
  83. zenml/integrations/tensorflow/__init__.py +18 -6
  84. zenml/integrations/wandb/__init__.py +1 -0
  85. zenml/logging/step_logging.py +34 -35
  86. zenml/materializers/built_in_materializer.py +1 -1
  87. zenml/materializers/cloudpickle_materializer.py +1 -1
  88. zenml/model/model.py +1 -1
  89. zenml/models/__init__.py +11 -0
  90. zenml/models/v2/core/component.py +47 -0
  91. zenml/models/v2/core/model.py +1 -2
  92. zenml/models/v2/core/server_settings.py +0 -20
  93. zenml/models/v2/core/service_connector.py +17 -0
  94. zenml/models/v2/core/stack.py +31 -0
  95. zenml/models/v2/misc/full_stack.py +129 -0
  96. zenml/models/v2/misc/stack_deployment.py +91 -0
  97. zenml/new/pipelines/pipeline.py +1 -1
  98. zenml/new/pipelines/run_utils.py +1 -1
  99. zenml/orchestrators/__init__.py +4 -0
  100. zenml/orchestrators/input_utils.py +3 -6
  101. zenml/orchestrators/step_launcher.py +1 -0
  102. zenml/orchestrators/wheeled_orchestrator.py +147 -0
  103. zenml/service_connectors/service_connector_utils.py +408 -0
  104. zenml/stack/stack.py +3 -6
  105. zenml/stack_deployments/__init__.py +14 -0
  106. zenml/stack_deployments/aws_stack_deployment.py +254 -0
  107. zenml/stack_deployments/azure_stack_deployment.py +179 -0
  108. zenml/stack_deployments/gcp_stack_deployment.py +269 -0
  109. zenml/stack_deployments/stack_deployment.py +218 -0
  110. zenml/stack_deployments/utils.py +48 -0
  111. zenml/steps/base_step.py +7 -5
  112. zenml/utils/function_utils.py +2 -2
  113. zenml/utils/pagination_utils.py +7 -5
  114. zenml/utils/pipeline_docker_image_builder.py +105 -68
  115. zenml/utils/pydantic_utils.py +6 -5
  116. zenml/utils/source_utils.py +4 -1
  117. zenml/zen_server/cloud_utils.py +18 -3
  118. zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-B_YdvmwS.js} +1 -1
  119. zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +85 -0
  120. zenml/zen_server/dashboard/assets/{@react-router-DYovave8.js → @react-router-CO-OsFwI.js} +2 -2
  121. zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-l_1hUr1S.js} +2 -2
  122. zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
  123. zenml/zen_server/dashboard/assets/AwarenessChannel-CFg5iX4Z.js +1 -0
  124. zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-Dvkx_82E.js} +2 -2
  125. zenml/zen_server/dashboard/assets/CollapsibleCard-opiuBHHc.js +1 -0
  126. zenml/zen_server/dashboard/assets/Commands-DoN1xrEq.js +1 -0
  127. zenml/zen_server/dashboard/assets/CopyButton-Cr7xYEPb.js +2 -0
  128. zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-Ck-nZ43m.js} +7 -7
  129. zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +1 -0
  130. zenml/zen_server/dashboard/assets/EmptyState-BMLnFVlB.js +1 -0
  131. zenml/zen_server/dashboard/assets/Error-kLtljEOM.js +1 -0
  132. zenml/zen_server/dashboard/assets/ExecutionStatus-DguLLgTK.js +1 -0
  133. zenml/zen_server/dashboard/assets/Helpbox-BXUMP21n.js +1 -0
  134. zenml/zen_server/dashboard/assets/Infobox-DSt0O-dm.js +1 -0
  135. zenml/zen_server/dashboard/assets/InlineAvatar-xsrsIGE-.js +1 -0
  136. zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
  137. zenml/zen_server/dashboard/assets/Pagination-C6X-mifw.js +1 -0
  138. zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
  139. zenml/zen_server/dashboard/assets/SetPassword-BXGTWiwj.js +1 -0
  140. zenml/zen_server/dashboard/assets/SuccessStep-DZC60t0x.js +1 -0
  141. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DGvwFWO1.js} +1 -1
  142. zenml/zen_server/dashboard/assets/{aws-t0gKCj_R.js → aws-BgKTfTfx.js} +1 -1
  143. zenml/zen_server/dashboard/assets/{check-circle-BVvhm5dy.js → check-circle-i56092KI.js} +1 -1
  144. zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-CZBOf6JM.js} +1 -1
  145. zenml/zen_server/dashboard/assets/cloud-only-C_yFCAkP.js +1 -0
  146. zenml/zen_server/dashboard/assets/{copy-BRhQz3j-.js → copy-BXNk6BjL.js} +1 -1
  147. zenml/zen_server/dashboard/assets/{database-CRRnyFWh.js → database-1xWSgZfO.js} +1 -1
  148. zenml/zen_server/dashboard/assets/{docker-BAonhm6G.js → docker-CQMVm_4d.js} +1 -1
  149. zenml/zen_server/dashboard/assets/{file-text-CbVERUON.js → file-text-CqD_iu6l.js} +1 -1
  150. zenml/zen_server/dashboard/assets/{help-B8rqCvqn.js → help-bu_DgLKI.js} +1 -1
  151. zenml/zen_server/dashboard/assets/index-BczVOqUf.js +55 -0
  152. zenml/zen_server/dashboard/assets/index-EpMIKgrI.css +1 -0
  153. zenml/zen_server/dashboard/assets/index-rK_Wuy2W.js +1 -0
  154. zenml/zen_server/dashboard/assets/index.esm-Corw4lXQ.js +1 -0
  155. zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-CrHrndTI.js} +1 -1
  156. zenml/zen_server/dashboard/assets/logs-D8k8BVFf.js +1 -0
  157. zenml/zen_server/dashboard/assets/not-found-DYa4pC-C.js +1 -0
  158. zenml/zen_server/dashboard/assets/package-B3fWP-Dh.js +1 -0
  159. zenml/zen_server/dashboard/assets/page-1h_sD1jz.js +1 -0
  160. zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-1iL8aMqs.js} +1 -1
  161. zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-2grKx_MY.js} +1 -1
  162. zenml/zen_server/dashboard/assets/page-5NCOHOsy.js +1 -0
  163. zenml/zen_server/dashboard/assets/page-8a4UMKXZ.js +1 -0
  164. zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-B6h3iaHJ.js} +1 -1
  165. zenml/zen_server/dashboard/assets/page-BDns21Iz.js +1 -0
  166. zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-BhgCDInH.js} +2 -2
  167. zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-Bi-wtWiO.js} +2 -2
  168. zenml/zen_server/dashboard/assets/page-BkeAAYwp.js +1 -0
  169. zenml/zen_server/dashboard/assets/page-BkuQDIf-.js +1 -0
  170. zenml/zen_server/dashboard/assets/page-BnaevhnB.js +1 -0
  171. zenml/zen_server/dashboard/assets/page-Bq0YxkLV.js +1 -0
  172. zenml/zen_server/dashboard/assets/page-Bs2F4eoD.js +2 -0
  173. zenml/zen_server/dashboard/assets/page-C6-UGEbH.js +1 -0
  174. zenml/zen_server/dashboard/assets/page-CCNRIt_f.js +1 -0
  175. zenml/zen_server/dashboard/assets/page-CHNxpz3n.js +1 -0
  176. zenml/zen_server/dashboard/assets/page-DgorQFqi.js +1 -0
  177. zenml/zen_server/dashboard/assets/page-K8ebxVIs.js +1 -0
  178. zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-MFQyIJd3.js} +1 -1
  179. zenml/zen_server/dashboard/assets/page-TgCF0P_U.js +1 -0
  180. zenml/zen_server/dashboard/assets/page-ZnCEe-eK.js +9 -0
  181. zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-uA5prJGY.js} +1 -1
  182. zenml/zen_server/dashboard/assets/persist-D7HJNBWx.js +1 -0
  183. zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
  184. zenml/zen_server/dashboard/assets/plus-C8WOyCzt.js +1 -0
  185. zenml/zen_server/dashboard/assets/stack-detail-query-Cficsl6d.js +1 -0
  186. zenml/zen_server/dashboard/assets/{terminal-B2ovgWuz.js → terminal-By9cErXc.js} +1 -1
  187. zenml/zen_server/dashboard/assets/update-server-settings-mutation-7d8xi1tS.js +1 -0
  188. zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-D7mAQGUM.js} +1 -1
  189. zenml/zen_server/dashboard/assets/{zod-DrZvVLjd.js → zod-BhoGpZ63.js} +1 -1
  190. zenml/zen_server/dashboard/index.html +7 -7
  191. zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
  192. zenml/zen_server/dashboard_legacy/index.html +1 -1
  193. zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.12246c7548e71e2c4438e496360de80c.js} +4 -4
  194. zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
  195. zenml/zen_server/dashboard_legacy/static/js/main.3b27024b.chunk.js +2 -0
  196. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.3b27024b.chunk.js.map} +1 -1
  197. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  198. zenml/zen_server/deploy/helm/README.md +2 -2
  199. zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +11 -5
  200. zenml/zen_server/pipeline_deployment/utils.py +57 -44
  201. zenml/zen_server/rbac/utils.py +10 -2
  202. zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
  203. zenml/zen_server/routers/devices_endpoints.py +4 -1
  204. zenml/zen_server/routers/server_endpoints.py +29 -2
  205. zenml/zen_server/routers/service_connectors_endpoints.py +57 -0
  206. zenml/zen_server/routers/stack_deployment_endpoints.py +158 -0
  207. zenml/zen_server/routers/steps_endpoints.py +2 -1
  208. zenml/zen_server/routers/workspaces_endpoints.py +64 -0
  209. zenml/zen_server/zen_server_api.py +2 -0
  210. zenml/zen_stores/migrations/utils.py +1 -1
  211. zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
  212. zenml/zen_stores/migrations/versions/0.61.0_release.py +23 -0
  213. zenml/zen_stores/migrations/versions/0.62.0_release.py +23 -0
  214. zenml/zen_stores/migrations/versions/0d707865f404_adding_labels_to_stacks.py +30 -0
  215. zenml/zen_stores/migrations/versions/b4fca5241eea_migrate_onboarding_state.py +167 -0
  216. zenml/zen_stores/rest_zen_store.py +149 -4
  217. zenml/zen_stores/schemas/component_schemas.py +14 -0
  218. zenml/zen_stores/schemas/server_settings_schemas.py +23 -11
  219. zenml/zen_stores/schemas/stack_schemas.py +10 -0
  220. zenml/zen_stores/schemas/step_run_schemas.py +27 -11
  221. zenml/zen_stores/sql_zen_store.py +450 -6
  222. zenml/zen_stores/zen_store_interface.py +80 -0
  223. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/METADATA +35 -13
  224. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/RECORD +227 -191
  225. zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js +0 -77
  226. zenml/zen_server/dashboard/assets/@tanstack-CEbkxrhX.js +0 -30
  227. zenml/zen_server/dashboard/assets/AwarenessChannel-nXGpmj_f.js +0 -1
  228. zenml/zen_server/dashboard/assets/Cards-nwsvQLVS.js +0 -1
  229. zenml/zen_server/dashboard/assets/Commands-DuIWKg_Q.js +0 -1
  230. zenml/zen_server/dashboard/assets/CopyButton-B_YSm-Ds.js +0 -2
  231. zenml/zen_server/dashboard/assets/DisplayDate-BdguISQF.js +0 -1
  232. zenml/zen_server/dashboard/assets/EmptyState-BkooiGtL.js +0 -1
  233. zenml/zen_server/dashboard/assets/Error-B6M0dPph.js +0 -1
  234. zenml/zen_server/dashboard/assets/Helpbox-BQoqCm04.js +0 -1
  235. zenml/zen_server/dashboard/assets/Infobox-Ce9mefqU.js +0 -1
  236. zenml/zen_server/dashboard/assets/InlineAvatar-DGf3dVhV.js +0 -1
  237. zenml/zen_server/dashboard/assets/PageHeader-DGaemzjc.js +0 -1
  238. zenml/zen_server/dashboard/assets/Pagination-DVYfBCCc.js +0 -1
  239. zenml/zen_server/dashboard/assets/PasswordChecker-DSLBp7Vl.js +0 -1
  240. zenml/zen_server/dashboard/assets/SetPassword-B5s7DJug.js +0 -1
  241. zenml/zen_server/dashboard/assets/SuccessStep-ZzczaM7g.js +0 -1
  242. zenml/zen_server/dashboard/assets/chevron-down-zcvCWmyP.js +0 -1
  243. zenml/zen_server/dashboard/assets/cloud-only-Ba_ShBR5.js +0 -1
  244. zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
  245. zenml/zen_server/dashboard/assets/index-QORVVTMN.js +0 -55
  246. zenml/zen_server/dashboard/assets/index.esm-F7nqy9zY.js +0 -1
  247. zenml/zen_server/dashboard/assets/not-found-Dh2la7kh.js +0 -1
  248. zenml/zen_server/dashboard/assets/page-B-5jAKoO.js +0 -1
  249. zenml/zen_server/dashboard/assets/page-B-vWk8a6.js +0 -1
  250. zenml/zen_server/dashboard/assets/page-B0BrqfS8.js +0 -1
  251. zenml/zen_server/dashboard/assets/page-BQxVFlUl.js +0 -1
  252. zenml/zen_server/dashboard/assets/page-ByrHy6Ss.js +0 -1
  253. zenml/zen_server/dashboard/assets/page-CPtY4Kv_.js +0 -1
  254. zenml/zen_server/dashboard/assets/page-CmmukLsl.js +0 -1
  255. zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
  256. zenml/zen_server/dashboard/assets/page-DAQQyLxT.js +0 -1
  257. zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
  258. zenml/zen_server/dashboard/assets/page-DZCbwOEs.js +0 -2
  259. zenml/zen_server/dashboard/assets/page-DdaIt20-.js +0 -1
  260. zenml/zen_server/dashboard/assets/page-LqLs24Ot.js +0 -1
  261. zenml/zen_server/dashboard/assets/page-lebv0c7C.js +0 -1
  262. zenml/zen_server/dashboard/assets/update-server-settings-mutation-0Wgz8pUE.js +0 -1
  263. zenml/zen_server/dashboard_legacy/static/js/main.ac2f17d0.chunk.js +0 -2
  264. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/LICENSE +0 -0
  265. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.62.0.dev20240726.dist-info}/WHEEL +0 -0
  266. {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 TYPE_CHECKING, Any, Dict, List, Optional, Union
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 CliCategories, StackComponentType
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.utils.dashboard_utils import get_stack_url
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=True,
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=True,
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
- with console.status(f"Registering stack '{stack_name}'...\n"):
228
- client = Client()
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
- components: Dict[StackComponentType, Union[str, UUID]] = dict()
290
+ client = Client()
231
291
 
232
- components[StackComponentType.ARTIFACT_STORE] = artifact_store
233
- components[StackComponentType.ORCHESTRATOR] = orchestrator
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
- if alerter:
236
- components[StackComponentType.ALERTER] = alerter
237
- if annotator:
238
- components[StackComponentType.ANNOTATOR] = annotator
239
- if data_validator:
240
- components[StackComponentType.DATA_VALIDATOR] = data_validator
241
- if feature_store:
242
- components[StackComponentType.FEATURE_STORE] = feature_store
243
- if image_builder:
244
- components[StackComponentType.IMAGE_BUILDER] = image_builder
245
- if model_deployer:
246
- components[StackComponentType.MODEL_DEPLOYER] = model_deployer
247
- if model_registry:
248
- components[StackComponentType.MODEL_REGISTRY] = model_registry
249
- if step_operator:
250
- components[StackComponentType.STEP_OPERATOR] = step_operator
251
- if experiment_tracker:
252
- components[StackComponentType.EXPERIMENT_TRACKER] = (
253
- experiment_tracker
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
- if container_registry:
256
- components[StackComponentType.CONTAINER_REGISTRY] = (
257
- container_registry
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.create_stack(
262
- name=stack_name,
263
- components=components,
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
- @stack.command(help="Deploy a stack using mlstacks.")
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 deploy(
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
+ )