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.
Files changed (214) hide show
  1. README.md +30 -9
  2. RELEASE_NOTES.md +240 -0
  3. zenml/VERSION +1 -1
  4. zenml/analytics/enums.py +3 -0
  5. zenml/cli/__init__.py +28 -0
  6. zenml/cli/artifact.py +1 -2
  7. zenml/cli/integration.py +9 -8
  8. zenml/cli/server.py +6 -0
  9. zenml/cli/stack.py +946 -39
  10. zenml/cli/stack_components.py +7 -0
  11. zenml/cli/text_utils.py +35 -1
  12. zenml/cli/utils.py +127 -10
  13. zenml/client.py +23 -14
  14. zenml/config/docker_settings.py +8 -5
  15. zenml/constants.py +10 -1
  16. zenml/container_registries/base_container_registry.py +1 -0
  17. zenml/enums.py +7 -0
  18. zenml/event_hub/event_hub.py +5 -8
  19. zenml/integrations/aws/__init__.py +1 -0
  20. zenml/integrations/azure/__init__.py +1 -0
  21. zenml/integrations/deepchecks/__init__.py +1 -0
  22. zenml/integrations/discord/__init__.py +1 -0
  23. zenml/integrations/evidently/__init__.py +1 -0
  24. zenml/integrations/facets/__init__.py +1 -0
  25. zenml/integrations/feast/__init__.py +1 -0
  26. zenml/integrations/gcp/__init__.py +3 -1
  27. zenml/integrations/gcp/google_credentials_mixin.py +1 -1
  28. zenml/integrations/gcp/service_connectors/gcp_service_connector.py +320 -64
  29. zenml/integrations/huggingface/__init__.py +1 -0
  30. zenml/integrations/integration.py +24 -0
  31. zenml/integrations/kubeflow/__init__.py +3 -0
  32. zenml/integrations/kubeflow/flavors/kubeflow_orchestrator_flavor.py +1 -1
  33. zenml/integrations/kubeflow/orchestrators/kubeflow_orchestrator.py +0 -1
  34. zenml/integrations/kubernetes/__init__.py +3 -1
  35. zenml/integrations/kubernetes/orchestrators/kube_utils.py +4 -1
  36. zenml/integrations/label_studio/annotators/label_studio_annotator.py +1 -0
  37. zenml/integrations/langchain/__init__.py +1 -0
  38. zenml/integrations/mlflow/__init__.py +3 -1
  39. zenml/integrations/neural_prophet/__init__.py +1 -0
  40. zenml/integrations/polars/__init__.py +1 -0
  41. zenml/integrations/prodigy/__init__.py +1 -0
  42. zenml/integrations/pycaret/__init__.py +6 -0
  43. zenml/integrations/registry.py +37 -0
  44. zenml/integrations/s3/artifact_stores/s3_artifact_store.py +17 -6
  45. zenml/integrations/seldon/__init__.py +1 -0
  46. zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -0
  47. zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +2 -2
  48. zenml/integrations/skypilot/orchestrators/skypilot_base_vm_orchestrator.py +1 -1
  49. zenml/integrations/skypilot/orchestrators/skypilot_orchestrator_entrypoint.py +2 -2
  50. zenml/integrations/skypilot_aws/__init__.py +2 -1
  51. zenml/integrations/skypilot_azure/__init__.py +1 -1
  52. zenml/integrations/skypilot_gcp/__init__.py +1 -1
  53. zenml/integrations/skypilot_lambda/__init__.py +1 -1
  54. zenml/integrations/skypilot_lambda/flavors/skypilot_orchestrator_lambda_vm_flavor.py +1 -1
  55. zenml/integrations/slack/__init__.py +1 -0
  56. zenml/integrations/tekton/__init__.py +1 -0
  57. zenml/integrations/tensorboard/__init__.py +0 -1
  58. zenml/integrations/tensorflow/__init__.py +18 -6
  59. zenml/integrations/wandb/__init__.py +1 -0
  60. zenml/models/__init__.py +11 -0
  61. zenml/models/v2/core/component.py +18 -0
  62. zenml/models/v2/core/model.py +1 -2
  63. zenml/models/v2/core/service_connector.py +17 -0
  64. zenml/models/v2/core/stack.py +31 -0
  65. zenml/models/v2/misc/full_stack.py +97 -0
  66. zenml/models/v2/misc/stack_deployment.py +86 -0
  67. zenml/new/pipelines/pipeline.py +1 -1
  68. zenml/orchestrators/input_utils.py +3 -6
  69. zenml/stack/stack.py +3 -6
  70. zenml/stack_deployments/__init__.py +14 -0
  71. zenml/stack_deployments/aws_stack_deployment.py +254 -0
  72. zenml/stack_deployments/gcp_stack_deployment.py +260 -0
  73. zenml/stack_deployments/stack_deployment.py +208 -0
  74. zenml/stack_deployments/utils.py +44 -0
  75. zenml/utils/function_utils.py +1 -1
  76. zenml/utils/pagination_utils.py +7 -5
  77. zenml/utils/pipeline_docker_image_builder.py +97 -68
  78. zenml/utils/pydantic_utils.py +6 -5
  79. zenml/zen_server/cloud_utils.py +18 -3
  80. zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-DpJaNHKF.js} +1 -1
  81. zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +85 -0
  82. zenml/zen_server/dashboard/assets/{@react-router-DYovave8.js → @react-router-CO-OsFwI.js} +2 -2
  83. zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-DJfzkHO1.js} +2 -2
  84. zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
  85. zenml/zen_server/dashboard/assets/AwarenessChannel-BYDLT2xC.js +1 -0
  86. zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-BkOuRmyq.js} +2 -2
  87. zenml/zen_server/dashboard/assets/Commands-ZvWR1BRs.js +1 -0
  88. zenml/zen_server/dashboard/assets/CopyButton-DVwLkafa.js +2 -0
  89. zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-C2IiqX4I.js} +7 -7
  90. zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +1 -0
  91. zenml/zen_server/dashboard/assets/EmptyState-BMLnFVlB.js +1 -0
  92. zenml/zen_server/dashboard/assets/Error-CqX0VqW_.js +1 -0
  93. zenml/zen_server/dashboard/assets/ExecutionStatus-BoLUXR9t.js +1 -0
  94. zenml/zen_server/dashboard/assets/Helpbox-LFydyVwh.js +1 -0
  95. zenml/zen_server/dashboard/assets/Infobox-DnENC0sh.js +1 -0
  96. zenml/zen_server/dashboard/assets/InlineAvatar-CbJtYr0t.js +1 -0
  97. zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
  98. zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +1 -0
  99. zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
  100. zenml/zen_server/dashboard/assets/SetPassword-BYBdbQDo.js +1 -0
  101. zenml/zen_server/dashboard/assets/SuccessStep-Nx743hll.js +1 -0
  102. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DF9gSzE0.js} +1 -1
  103. zenml/zen_server/dashboard/assets/{aws-t0gKCj_R.js → aws-BgKTfTfx.js} +1 -1
  104. zenml/zen_server/dashboard/assets/{check-circle-BVvhm5dy.js → check-circle-i56092KI.js} +1 -1
  105. zenml/zen_server/dashboard/assets/{chevron-down-zcvCWmyP.js → chevron-down-D_ZlKMqH.js} +1 -1
  106. zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-BiEMg7rd.js} +1 -1
  107. zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.js +1 -0
  108. zenml/zen_server/dashboard/assets/{copy-BRhQz3j-.js → copy-BXNk6BjL.js} +1 -1
  109. zenml/zen_server/dashboard/assets/{database-CRRnyFWh.js → database-1xWSgZfO.js} +1 -1
  110. zenml/zen_server/dashboard/assets/{docker-BAonhm6G.js → docker-CQMVm_4d.js} +1 -1
  111. zenml/zen_server/dashboard/assets/{file-text-CbVERUON.js → file-text-CqD_iu6l.js} +1 -1
  112. zenml/zen_server/dashboard/assets/{help-B8rqCvqn.js → help-bu_DgLKI.js} +1 -1
  113. zenml/zen_server/dashboard/assets/index-C_CrU4vI.js +1 -0
  114. zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +55 -0
  115. zenml/zen_server/dashboard/assets/index-inApY3KQ.css +1 -0
  116. zenml/zen_server/dashboard/assets/index-rK_Wuy2W.js +1 -0
  117. zenml/zen_server/dashboard/assets/index.esm-Corw4lXQ.js +1 -0
  118. zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-BUnVASxp.js} +1 -1
  119. zenml/zen_server/dashboard/assets/not-found-B4VnX8gK.js +1 -0
  120. zenml/zen_server/dashboard/assets/package-CsUhPmou.js +1 -0
  121. zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-3efNCDeb.js} +2 -2
  122. zenml/zen_server/dashboard/assets/page-7zTHbhhI.js +1 -0
  123. zenml/zen_server/dashboard/assets/page-BEs6jK71.js +1 -0
  124. zenml/zen_server/dashboard/assets/page-BpSqIf4B.js +1 -0
  125. zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-Bx6o0ARS.js} +1 -1
  126. zenml/zen_server/dashboard/assets/page-C43QGHTt.js +9 -0
  127. zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +1 -0
  128. zenml/zen_server/dashboard/assets/page-CRTJ0UuR.js +1 -0
  129. zenml/zen_server/dashboard/assets/page-CUZIGO-3.js +1 -0
  130. zenml/zen_server/dashboard/assets/page-CaopxiU1.js +1 -0
  131. zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-Cx67M0QT.js} +1 -1
  132. zenml/zen_server/dashboard/assets/page-D7Z399xy.js +1 -0
  133. zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +1 -0
  134. zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-DKlIdAe5.js} +1 -1
  135. zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-DMOYZppS.js} +1 -1
  136. zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +2 -0
  137. zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-Dc_7KMQE.js} +1 -1
  138. zenml/zen_server/dashboard/assets/page-DvCvroOM.js +1 -0
  139. zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +1 -0
  140. zenml/zen_server/dashboard/assets/page-JyfeDUfu.js +1 -0
  141. zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-Sxn82W-5.js} +1 -1
  142. zenml/zen_server/dashboard/assets/page-TKXERe16.js +1 -0
  143. zenml/zen_server/dashboard/assets/page-Xu8JEjSU.js +1 -0
  144. zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
  145. zenml/zen_server/dashboard/assets/plus-DOeLmm7C.js +1 -0
  146. zenml/zen_server/dashboard/assets/{terminal-B2ovgWuz.js → terminal-By9cErXc.js} +1 -1
  147. zenml/zen_server/dashboard/assets/{update-server-settings-mutation-0Wgz8pUE.js → update-server-settings-mutation-CR8e3Sir.js} +1 -1
  148. zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-DuQMeqYA.js} +1 -1
  149. zenml/zen_server/dashboard/assets/{zod-DrZvVLjd.js → zod-BhoGpZ63.js} +1 -1
  150. zenml/zen_server/dashboard/index.html +7 -7
  151. zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
  152. zenml/zen_server/dashboard_legacy/index.html +1 -1
  153. zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.c8c57fb0d2132b1d3c2119e776b7dfb3.js} +4 -4
  154. zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
  155. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js → main.382439a7.chunk.js} +2 -2
  156. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.382439a7.chunk.js.map} +1 -1
  157. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  158. zenml/zen_server/deploy/helm/README.md +2 -2
  159. zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +11 -5
  160. zenml/zen_server/pipeline_deployment/utils.py +57 -44
  161. zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
  162. zenml/zen_server/routers/stack_deployment_endpoints.py +158 -0
  163. zenml/zen_server/routers/workspaces_endpoints.py +64 -0
  164. zenml/zen_server/zen_server_api.py +2 -0
  165. zenml/zen_stores/migrations/utils.py +1 -1
  166. zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
  167. zenml/zen_stores/migrations/versions/0.61.0_release.py +23 -0
  168. zenml/zen_stores/migrations/versions/0d707865f404_adding_labels_to_stacks.py +30 -0
  169. zenml/zen_stores/rest_zen_store.py +145 -4
  170. zenml/zen_stores/schemas/stack_schemas.py +10 -0
  171. zenml/zen_stores/schemas/step_run_schemas.py +27 -11
  172. zenml/zen_stores/sql_zen_store.py +300 -6
  173. zenml/zen_stores/zen_store_interface.py +80 -0
  174. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/METADATA +32 -10
  175. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/RECORD +178 -162
  176. zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js +0 -77
  177. zenml/zen_server/dashboard/assets/@tanstack-CEbkxrhX.js +0 -30
  178. zenml/zen_server/dashboard/assets/AwarenessChannel-nXGpmj_f.js +0 -1
  179. zenml/zen_server/dashboard/assets/Cards-nwsvQLVS.js +0 -1
  180. zenml/zen_server/dashboard/assets/Commands-DuIWKg_Q.js +0 -1
  181. zenml/zen_server/dashboard/assets/CopyButton-B_YSm-Ds.js +0 -2
  182. zenml/zen_server/dashboard/assets/DisplayDate-BdguISQF.js +0 -1
  183. zenml/zen_server/dashboard/assets/EmptyState-BkooiGtL.js +0 -1
  184. zenml/zen_server/dashboard/assets/Error-B6M0dPph.js +0 -1
  185. zenml/zen_server/dashboard/assets/Helpbox-BQoqCm04.js +0 -1
  186. zenml/zen_server/dashboard/assets/Infobox-Ce9mefqU.js +0 -1
  187. zenml/zen_server/dashboard/assets/InlineAvatar-DGf3dVhV.js +0 -1
  188. zenml/zen_server/dashboard/assets/PageHeader-DGaemzjc.js +0 -1
  189. zenml/zen_server/dashboard/assets/Pagination-DVYfBCCc.js +0 -1
  190. zenml/zen_server/dashboard/assets/PasswordChecker-DSLBp7Vl.js +0 -1
  191. zenml/zen_server/dashboard/assets/SetPassword-B5s7DJug.js +0 -1
  192. zenml/zen_server/dashboard/assets/SuccessStep-ZzczaM7g.js +0 -1
  193. zenml/zen_server/dashboard/assets/cloud-only-Ba_ShBR5.js +0 -1
  194. zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
  195. zenml/zen_server/dashboard/assets/index-QORVVTMN.js +0 -55
  196. zenml/zen_server/dashboard/assets/index.esm-F7nqy9zY.js +0 -1
  197. zenml/zen_server/dashboard/assets/not-found-Dh2la7kh.js +0 -1
  198. zenml/zen_server/dashboard/assets/page-B-5jAKoO.js +0 -1
  199. zenml/zen_server/dashboard/assets/page-B-vWk8a6.js +0 -1
  200. zenml/zen_server/dashboard/assets/page-B0BrqfS8.js +0 -1
  201. zenml/zen_server/dashboard/assets/page-BQxVFlUl.js +0 -1
  202. zenml/zen_server/dashboard/assets/page-ByrHy6Ss.js +0 -1
  203. zenml/zen_server/dashboard/assets/page-CPtY4Kv_.js +0 -1
  204. zenml/zen_server/dashboard/assets/page-CmmukLsl.js +0 -1
  205. zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
  206. zenml/zen_server/dashboard/assets/page-DAQQyLxT.js +0 -1
  207. zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
  208. zenml/zen_server/dashboard/assets/page-DZCbwOEs.js +0 -2
  209. zenml/zen_server/dashboard/assets/page-DdaIt20-.js +0 -1
  210. zenml/zen_server/dashboard/assets/page-LqLs24Ot.js +0 -1
  211. zenml/zen_server/dashboard/assets/page-lebv0c7C.js +0 -1
  212. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/LICENSE +0 -0
  213. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/WHEEL +0 -0
  214. {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 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,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.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
+ )
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=True,
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=True,
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
- with console.status(f"Registering stack '{stack_name}'...\n"):
228
- client = Client()
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
- components: Dict[StackComponentType, Union[str, UUID]] = dict()
289
+ client = Client()
231
290
 
232
- components[StackComponentType.ARTIFACT_STORE] = artifact_store
233
- components[StackComponentType.ORCHESTRATOR] = orchestrator
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
- 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
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
- if container_registry:
256
- components[StackComponentType.CONTAINER_REGISTRY] = (
257
- container_registry
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.create_stack(
262
- name=stack_name,
263
- components=components,
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
- @stack.command(help="Deploy a stack using mlstacks.")
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 deploy(
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
+ )