zenml-nightly 0.63.0.dev20240801__py3-none-any.whl → 0.64.0.dev20240809__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 (198) hide show
  1. README.md +2 -2
  2. RELEASE_NOTES.md +79 -0
  3. zenml/VERSION +1 -1
  4. zenml/__init__.py +0 -4
  5. zenml/analytics/enums.py +0 -6
  6. zenml/cli/__init__.py +0 -61
  7. zenml/cli/base.py +1 -1
  8. zenml/cli/web_login.py +8 -0
  9. zenml/client.py +0 -4
  10. zenml/config/build_configuration.py +43 -17
  11. zenml/config/docker_settings.py +80 -57
  12. zenml/config/source.py +58 -0
  13. zenml/constants.py +9 -2
  14. zenml/entrypoints/base_entrypoint_configuration.py +53 -8
  15. zenml/enums.py +1 -1
  16. zenml/environment.py +25 -9
  17. zenml/image_builders/base_image_builder.py +1 -1
  18. zenml/image_builders/build_context.py +25 -72
  19. zenml/integrations/azure/__init__.py +4 -0
  20. zenml/integrations/azure/flavors/__init__.py +11 -0
  21. zenml/integrations/azure/flavors/azureml_orchestrator_flavor.py +263 -0
  22. zenml/{_hub → integrations/azure/orchestrators}/__init__.py +7 -2
  23. zenml/integrations/azure/orchestrators/azureml_orchestrator.py +544 -0
  24. zenml/integrations/azure/orchestrators/azureml_orchestrator_entrypoint_config.py +86 -0
  25. zenml/integrations/azure/step_operators/azureml_step_operator.py +3 -0
  26. zenml/integrations/databricks/flavors/databricks_orchestrator_flavor.py +9 -0
  27. zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +7 -2
  28. zenml/integrations/gcp/service_connectors/gcp_service_connector.py +123 -6
  29. zenml/integrations/kaniko/image_builders/kaniko_image_builder.py +1 -1
  30. zenml/integrations/mlflow/__init__.py +1 -1
  31. zenml/integrations/mlflow/experiment_trackers/mlflow_experiment_tracker.py +3 -1
  32. zenml/integrations/mlflow/flavors/mlflow_experiment_tracker_flavor.py +3 -0
  33. zenml/logger.py +13 -0
  34. zenml/models/__init__.py +0 -12
  35. zenml/models/v2/core/pipeline_deployment.py +21 -29
  36. zenml/models/v2/core/pipeline_run.py +13 -0
  37. zenml/models/v2/core/server_settings.py +12 -0
  38. zenml/models/v2/core/user.py +0 -21
  39. zenml/models/v2/misc/server_models.py +7 -1
  40. zenml/models/v2/misc/user_auth.py +0 -7
  41. zenml/new/pipelines/build_utils.py +193 -38
  42. zenml/new/pipelines/code_archive.py +157 -0
  43. zenml/new/pipelines/pipeline.py +29 -2
  44. zenml/new/pipelines/run_utils.py +67 -1
  45. zenml/service_connectors/service_connector_utils.py +14 -0
  46. zenml/stack_deployments/aws_stack_deployment.py +26 -3
  47. zenml/stack_deployments/azure_stack_deployment.py +11 -6
  48. zenml/stack_deployments/gcp_stack_deployment.py +24 -2
  49. zenml/stack_deployments/stack_deployment.py +17 -2
  50. zenml/steps/base_step.py +3 -0
  51. zenml/utils/archivable.py +149 -0
  52. zenml/utils/code_utils.py +244 -0
  53. zenml/utils/notebook_utils.py +122 -0
  54. zenml/utils/pipeline_docker_image_builder.py +3 -96
  55. zenml/utils/source_utils.py +109 -1
  56. zenml/zen_server/dashboard/assets/{404-CI13wQp4.js → 404-CRAA_Lew.js} +1 -1
  57. zenml/zen_server/dashboard/assets/@radix-BXWm7HOa.js +85 -0
  58. zenml/zen_server/dashboard/assets/{@react-router-CO-OsFwI.js → @react-router-l3lMcXA2.js} +1 -1
  59. zenml/zen_server/dashboard/assets/{@reactflow-DIYUhKYX.js → @reactflow-CeVxyqYT.js} +2 -2
  60. zenml/zen_server/dashboard/assets/{@tanstack-k96lU_C-.js → @tanstack-FmcYZMuX.js} +4 -4
  61. zenml/zen_server/dashboard/assets/AlertDialogDropdownItem-ErO9aOgK.js +1 -0
  62. zenml/zen_server/dashboard/assets/{AwarenessChannel-BNg5uWgI.js → AwarenessChannel-CLXo5rKM.js} +1 -1
  63. zenml/zen_server/dashboard/assets/{CodeSnippet-Cyp7f4dM.js → CodeSnippet-D0VLxT2A.js} +1 -1
  64. zenml/zen_server/dashboard/assets/{CollapsibleCard-Cu_A9W57.js → CollapsibleCard-BaUPiVg0.js} +1 -1
  65. zenml/zen_server/dashboard/assets/{Commands-DmQwTXjj.js → Commands-JrcZK-3j.js} +1 -1
  66. zenml/zen_server/dashboard/assets/CopyButton-Dbo52T1K.js +2 -0
  67. zenml/zen_server/dashboard/assets/{CsvVizualization-BvqItd-O.js → CsvVizualization-D3kAypDj.js} +3 -3
  68. zenml/zen_server/dashboard/assets/DisplayDate-DizbSeT-.js +1 -0
  69. zenml/zen_server/dashboard/assets/EditSecretDialog-Bd7mFLS4.js +1 -0
  70. zenml/zen_server/dashboard/assets/{EmptyState-BMLnFVlB.js → EmptyState-BHblM39I.js} +1 -1
  71. zenml/zen_server/dashboard/assets/{Error-DbXCTGua.js → Error-C6LeJSER.js} +1 -1
  72. zenml/zen_server/dashboard/assets/{ExecutionStatus-9zM7eaLh.js → ExecutionStatus-jH4OrWBq.js} +1 -1
  73. zenml/zen_server/dashboard/assets/{Helpbox-BIiNc-uH.js → Helpbox-aAB2XP-z.js} +1 -1
  74. zenml/zen_server/dashboard/assets/{Infobox-iv1Nu1A0.js → Infobox-BQ0aty32.js} +1 -1
  75. zenml/zen_server/dashboard/assets/{InlineAvatar-BvBtO2Dp.js → InlineAvatar-DpTLgM3Q.js} +1 -1
  76. zenml/zen_server/dashboard/assets/Lock-CNyJvf2r.js +1 -0
  77. zenml/zen_server/dashboard/assets/{MarkdownVisualization-xp3hhULl.js → MarkdownVisualization-Bajxn0HY.js} +1 -1
  78. zenml/zen_server/dashboard/assets/NumberBox-BmKE0qnO.js +1 -0
  79. zenml/zen_server/dashboard/assets/{PasswordChecker-DUveqlva.js → PasswordChecker-yGGoJSB-.js} +1 -1
  80. zenml/zen_server/dashboard/assets/{ProviderRadio-pSAvrGRS.js → ProviderRadio-BBqkIuTd.js} +1 -1
  81. zenml/zen_server/dashboard/assets/RadioItem-xLhXoiFV.js +1 -0
  82. zenml/zen_server/dashboard/assets/SearchField-C9R0mdaX.js +1 -0
  83. zenml/zen_server/dashboard/assets/{SetPassword-BOxpgh6N.js → SetPassword-52sNxNiO.js} +1 -1
  84. zenml/zen_server/dashboard/assets/{SuccessStep-CTSKN2lp.js → SuccessStep-DlkItqYG.js} +1 -1
  85. zenml/zen_server/dashboard/assets/{Tick-Bnr2TpW6.js → Tick-uxv80Q6a.js} +1 -1
  86. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-BeCeaRW5.js → UpdatePasswordSchemas-oN4G3sKz.js} +1 -1
  87. zenml/zen_server/dashboard/assets/{aws-BgKTfTfx.js → aws-0_3UsPif.js} +1 -1
  88. zenml/zen_server/dashboard/assets/{check-circle-i56092KI.js → check-circle-1_I207rW.js} +1 -1
  89. zenml/zen_server/dashboard/assets/{chevron-down-D_ZlKMqH.js → chevron-down-BpaF8JqM.js} +1 -1
  90. zenml/zen_server/dashboard/assets/{chevron-right-double-CZBOf6JM.js → chevron-right-double-Dk8e2L99.js} +1 -1
  91. zenml/zen_server/dashboard/assets/{cloud-only-qelmY92E.js → cloud-only-BkUuI0lZ.js} +1 -1
  92. zenml/zen_server/dashboard/assets/components-Br2ezRib.js +1 -0
  93. zenml/zen_server/dashboard/assets/{copy-BXNk6BjL.js → copy-f3XGPPxt.js} +1 -1
  94. zenml/zen_server/dashboard/assets/{database-1xWSgZfO.js → database-cXYNX9tt.js} +1 -1
  95. zenml/zen_server/dashboard/assets/{docker-CQMVm_4d.js → docker-8uj__HHK.js} +1 -1
  96. zenml/zen_server/dashboard/assets/{dots-horizontal-BObFzD5l.js → dots-horizontal-sKQlWEni.js} +1 -1
  97. zenml/zen_server/dashboard/assets/edit-C0MVvPD2.js +1 -0
  98. zenml/zen_server/dashboard/assets/{file-text-CqD_iu6l.js → file-text-B9JibxTs.js} +1 -1
  99. zenml/zen_server/dashboard/assets/{help-bu_DgLKI.js → help-FuHlZwn0.js} +1 -1
  100. zenml/zen_server/dashboard/assets/index-Bd1xgUQG.js +1 -0
  101. zenml/zen_server/dashboard/assets/index-DaGknux4.css +1 -0
  102. zenml/zen_server/dashboard/assets/{index-KsTz2dHG.js → index-DhIZtpxB.js} +5 -5
  103. zenml/zen_server/dashboard/assets/{index.esm-CbHNSeVw.js → index.esm-DT4uyn2i.js} +1 -1
  104. zenml/zen_server/dashboard/assets/layout-D6oiSbfd.js +1 -0
  105. zenml/zen_server/dashboard/assets/{login-mutation-DRpbESS7.js → login-mutation-13A_JSVA.js} +1 -1
  106. zenml/zen_server/dashboard/assets/{logs-D8k8BVFf.js → logs-CgeE2vZP.js} +1 -1
  107. zenml/zen_server/dashboard/assets/{not-found-Dfx9hfkf.js → not-found-B0Mmb90p.js} +1 -1
  108. zenml/zen_server/dashboard/assets/{package-ClbU3KUi.js → package-DdkziX79.js} +1 -1
  109. zenml/zen_server/dashboard/assets/page-7-v2OBm-.js +1 -0
  110. zenml/zen_server/dashboard/assets/{page-f3jBVI5Z.js → page-B3ozwdD1.js} +1 -1
  111. zenml/zen_server/dashboard/assets/{page-DYBNGxJt.js → page-BGwA9B1M.js} +1 -1
  112. zenml/zen_server/dashboard/assets/{page-C176KxyB.js → page-BkjAUyTA.js} +1 -1
  113. zenml/zen_server/dashboard/assets/page-BnacgBiy.js +1 -0
  114. zenml/zen_server/dashboard/assets/{page-CzucfYPo.js → page-BxF_KMQ3.js} +2 -2
  115. zenml/zen_server/dashboard/assets/page-C4POHC0K.js +1 -0
  116. zenml/zen_server/dashboard/assets/page-C9kudd44.js +9 -0
  117. zenml/zen_server/dashboard/assets/page-CA1j3GpJ.js +1 -0
  118. zenml/zen_server/dashboard/assets/page-CCY6yfmu.js +1 -0
  119. zenml/zen_server/dashboard/assets/page-CgTe7Bme.js +1 -0
  120. zenml/zen_server/dashboard/assets/{page-DtpwnNXq.js → page-Cgn-6v2Y.js} +1 -1
  121. zenml/zen_server/dashboard/assets/page-CxQmQqDw.js +1 -0
  122. zenml/zen_server/dashboard/assets/page-D2Goey3H.js +1 -0
  123. zenml/zen_server/dashboard/assets/page-DLpOnf7u.js +1 -0
  124. zenml/zen_server/dashboard/assets/{page-DVPxY5fT.js → page-DSTQnBk-.js} +1 -1
  125. zenml/zen_server/dashboard/assets/{page-BoFtUD9H.js → page-DTysUGOy.js} +1 -1
  126. zenml/zen_server/dashboard/assets/{page-p2hLJdS2.js → page-D_EXUFJb.js} +1 -1
  127. zenml/zen_server/dashboard/assets/page-Db15QzsM.js +1 -0
  128. zenml/zen_server/dashboard/assets/{page-Btu39x7k.js → page-DugsjcQ_.js} +1 -1
  129. zenml/zen_server/dashboard/assets/{page-CZe9GEBF.js → page-OFKSPyN7.js} +1 -1
  130. zenml/zen_server/dashboard/assets/{page-CDgZmwxP.js → page-RnG-qhv9.js} +1 -1
  131. zenml/zen_server/dashboard/assets/{page-Cjn97HMv.js → page-T2BtjwPl.js} +1 -1
  132. zenml/zen_server/dashboard/assets/page-TXe1Eo3Z.js +1 -0
  133. zenml/zen_server/dashboard/assets/{page-BxiWdeyg.js → page-YiF_fNbe.js} +1 -1
  134. zenml/zen_server/dashboard/assets/{page-399pVZHU.js → page-hQaiQXfg.js} +1 -1
  135. zenml/zen_server/dashboard/assets/persist-3-5nOJ6m.js +1 -0
  136. zenml/zen_server/dashboard/assets/{play-circle-CNtZKDnW.js → play-circle-XSkLR12B.js} +1 -1
  137. zenml/zen_server/dashboard/assets/{plus-DOeLmm7C.js → plus-FB9-lEq_.js} +1 -1
  138. zenml/zen_server/dashboard/assets/refresh-COb6KYDi.js +1 -0
  139. zenml/zen_server/dashboard/assets/sharedSchema-BoYx_B_L.js +14 -0
  140. zenml/zen_server/dashboard/assets/{stack-detail-query-Ck7j7BP_.js → stack-detail-query-B-US_-wa.js} +1 -1
  141. zenml/zen_server/dashboard/assets/{terminal-By9cErXc.js → terminal-grtjrIEJ.js} +1 -1
  142. zenml/zen_server/dashboard/assets/trash-Cd5CSFqA.js +1 -0
  143. zenml/zen_server/dashboard/assets/{update-server-settings-mutation-f3ZT7psb.js → update-server-settings-mutation-B8GB_ubU.js} +1 -1
  144. zenml/zen_server/dashboard/assets/{url-rGEp5Umh.js → url-hcMJkz8p.js} +1 -1
  145. zenml/zen_server/dashboard/assets/{zod-BtSyGx4C.js → zod-CnykDKJj.js} +1 -1
  146. zenml/zen_server/dashboard/index.html +7 -7
  147. zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
  148. zenml/zen_server/dashboard_legacy/index.html +1 -1
  149. zenml/zen_server/dashboard_legacy/{precache-manifest.2fa6e528a6e7447caaf35dadfe7514bb.js → precache-manifest.9c473c96a43298343a7ce1256183123b.js} +4 -4
  150. zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
  151. zenml/zen_server/dashboard_legacy/static/js/{main.4aab7e98.chunk.js → main.463c90b9.chunk.js} +2 -2
  152. zenml/zen_server/dashboard_legacy/static/js/{main.4aab7e98.chunk.js.map → main.463c90b9.chunk.js.map} +1 -1
  153. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  154. zenml/zen_server/deploy/helm/README.md +2 -2
  155. zenml/zen_server/routers/stack_deployment_endpoints.py +6 -0
  156. zenml/zen_server/routers/users_endpoints.py +0 -7
  157. zenml/zen_server/utils.py +75 -0
  158. zenml/zen_server/zen_server_api.py +52 -1
  159. zenml/zen_stores/base_zen_store.py +7 -1
  160. zenml/zen_stores/migrations/versions/0.64.0_release.py +23 -0
  161. zenml/zen_stores/migrations/versions/026d4577b6a0_add_code_path.py +39 -0
  162. zenml/zen_stores/migrations/versions/3dcc5d20e82f_add_last_user_activity.py +51 -0
  163. zenml/zen_stores/migrations/versions/909550c7c4da_remove_user_hub_token.py +36 -0
  164. zenml/zen_stores/rest_zen_store.py +5 -3
  165. zenml/zen_stores/schemas/pipeline_deployment_schemas.py +3 -0
  166. zenml/zen_stores/schemas/pipeline_run_schemas.py +3 -0
  167. zenml/zen_stores/schemas/server_settings_schemas.py +2 -0
  168. zenml/zen_stores/schemas/user_schemas.py +0 -2
  169. zenml/zen_stores/sql_zen_store.py +25 -1
  170. {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240809.dist-info}/METADATA +3 -3
  171. {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240809.dist-info}/RECORD +174 -157
  172. zenml/_hub/client.py +0 -289
  173. zenml/_hub/constants.py +0 -21
  174. zenml/_hub/utils.py +0 -79
  175. zenml/cli/hub.py +0 -1116
  176. zenml/models/v2/misc/hub_plugin_models.py +0 -79
  177. zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +0 -85
  178. zenml/zen_server/dashboard/assets/CopyButton-B3sWVJ4Z.js +0 -2
  179. zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +0 -1
  180. zenml/zen_server/dashboard/assets/SearchField-CXoBknpt.js +0 -1
  181. zenml/zen_server/dashboard/assets/components-DWe4cTjS.js +0 -1
  182. zenml/zen_server/dashboard/assets/index-vfjX_fJV.css +0 -1
  183. zenml/zen_server/dashboard/assets/page-C6tXXjnK.js +0 -1
  184. zenml/zen_server/dashboard/assets/page-CP9obrnG.js +0 -1
  185. zenml/zen_server/dashboard/assets/page-CaTOsNNw.js +0 -1
  186. zenml/zen_server/dashboard/assets/page-CmXmB_5i.js +0 -1
  187. zenml/zen_server/dashboard/assets/page-CvGAOfad.js +0 -1
  188. zenml/zen_server/dashboard/assets/page-D0bbc-qr.js +0 -5
  189. zenml/zen_server/dashboard/assets/page-DLEtD2ex.js +0 -1
  190. zenml/zen_server/dashboard/assets/page-DupV0aBd.js +0 -1
  191. zenml/zen_server/dashboard/assets/page-EweAR81y.js +0 -1
  192. zenml/zen_server/dashboard/assets/page-w-YaL77M.js +0 -9
  193. zenml/zen_server/dashboard/assets/persist-BReKApOc.js +0 -14
  194. zenml/zen_server/dashboard/assets/secrets-video-OBJ6irhH.svg +0 -21
  195. zenml/zen_server/dashboard/assets/stacks-video-7gfxpAq4.svg +0 -21
  196. {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240809.dist-info}/LICENSE +0 -0
  197. {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240809.dist-info}/WHEEL +0 -0
  198. {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240809.dist-info}/entry_points.txt +0 -0
@@ -13,11 +13,14 @@
13
13
  # permissions and limitations under the License.
14
14
  """Functionality to deploy a ZenML stack to AWS."""
15
15
 
16
- from typing import ClassVar, Dict, List
16
+ from typing import ClassVar, Dict, List, Optional
17
17
 
18
18
  from zenml.enums import StackDeploymentProvider
19
19
  from zenml.models import StackDeploymentConfig
20
- from zenml.stack_deployments.stack_deployment import ZenMLCloudStackDeployment
20
+ from zenml.stack_deployments.stack_deployment import (
21
+ STACK_DEPLOYMENT_TERRAFORM,
22
+ ZenMLCloudStackDeployment,
23
+ )
21
24
  from zenml.utils.string_utils import random_str
22
25
 
23
26
  AWS_DEPLOYMENT_TYPE = "cloud-formation"
@@ -217,6 +220,7 @@ console.
217
220
  deploy the ZenML stack. The URL should include as many pre-filled
218
221
  URL query parameters as possible.
219
222
  * a textual description of the URL
223
+ * a Terraform script used to deploy the ZenML stack
220
224
  * some deployment providers may require additional configuration
221
225
  parameters or scripts to be passed to the cloud provider in addition to
222
226
  the deployment URL query parameters. Where that is the case, this method
@@ -248,8 +252,27 @@ console.
248
252
  f"{region}#/stacks/create/review?{query_params}"
249
253
  )
250
254
 
255
+ config: Optional[str] = None
256
+ if self.deployment_type == STACK_DEPLOYMENT_TERRAFORM:
257
+ config = f"""module "zenml_stack" {{
258
+ source = "zenml-io/zenml-stack/aws"
259
+
260
+ region = "{self.location or "eu-central-1"}"
261
+ zenml_server_url = "{self.zenml_server_url}"
262
+ zenml_api_key = ""
263
+ zenml_api_token = "{self.zenml_server_api_token}"
264
+ zenml_stack_name = "{self.stack_name}"
265
+ zenml_stack_deployment = "{self.deployment_type}"
266
+ }}
267
+ output "zenml_stack_id" {{
268
+ value = module.zenml_stack.zenml_stack_id
269
+ }}
270
+ output "zenml_stack_name" {{
271
+ value = module.zenml_stack.zenml_stack_name
272
+ }}"""
273
+
251
274
  return StackDeploymentConfig(
252
275
  deployment_url=url,
253
276
  deployment_url_text="AWS CloudFormation Console",
254
- configuration=None,
277
+ configuration=config,
255
278
  )
@@ -20,12 +20,14 @@ from zenml.enums import StackDeploymentProvider
20
20
  from zenml.models import StackDeploymentConfig
21
21
  from zenml.stack_deployments.stack_deployment import ZenMLCloudStackDeployment
22
22
 
23
+ AZURE_DEPLOYMENT_TYPE = "azure-cloud-shell"
24
+
23
25
 
24
26
  class AZUREZenMLCloudStackDeployment(ZenMLCloudStackDeployment):
25
27
  """Azure ZenML Cloud Stack Deployment."""
26
28
 
27
29
  provider: ClassVar[StackDeploymentProvider] = StackDeploymentProvider.AZURE
28
- deployment: ClassVar[str] = "azure-cloud-shell"
30
+ deployment: ClassVar[str] = AZURE_DEPLOYMENT_TYPE
29
31
 
30
32
  @classmethod
31
33
  def description(cls) -> str:
@@ -67,7 +69,8 @@ permissions and are aware of any potential costs:
67
69
  - An Azure Resource Group to contain all the resources required for the ZenML stack
68
70
  - An Azure Storage Account and Blob Storage Container registered as a [ZenML artifact store](https://docs.zenml.io/stack-components/artifact-stores/azure).
69
71
  - An Azure Container Registry registered as a [ZenML container registry](https://docs.zenml.io/stack-components/container-registries/azure).
70
- - SkyPilot will be registered as a [ZenML orchestrator](https://docs.zenml.io/stack-components/orchestrators/skypilot-vm) and used to run pipelines in your Azure subscription.
72
+ - An AzureML Workspace registered as a [ZenML orchestrator](https://docs.zenml.io/stack-components/orchestrators/azureml) and used to run pipelines.
73
+ A Key Vault and Application Insights instance will also be created in the same Resource Group and used to construct the AzureML Workspace.
71
74
  - An Azure Service Principal with the minimum necessary permissions to access
72
75
  the above resources.
73
76
  - An Azure Service Principal client secret used to give access to ZenML to
@@ -117,7 +120,7 @@ ZenML's access to your Azure subscription.
117
120
  The list of ZenML integrations that need to be installed for the
118
121
  stack to be usable.
119
122
  """
120
- return ["azure", "skypilot_azure"]
123
+ return ["azure"]
121
124
 
122
125
  @classmethod
123
126
  def permissions(cls) -> Dict[str, List[str]]:
@@ -136,8 +139,9 @@ ZenML's access to your Azure subscription.
136
139
  "AcrPush",
137
140
  "Contributor",
138
141
  ],
139
- "Subscription": [
140
- "Owner (required by SkyPilot)",
142
+ "AzureML Workspace": [
143
+ "AzureML Compute Operator",
144
+ "AzureML Data Scientist",
141
145
  ],
142
146
  }
143
147
 
@@ -258,11 +262,12 @@ ZenML's access to your Azure subscription.
258
262
  source = "zenml-io/zenml-stack/azure"
259
263
 
260
264
  location = "{self.location or "eastus"}"
265
+ orchestrator = "azureml"
261
266
  zenml_server_url = "{self.zenml_server_url}"
262
267
  zenml_api_key = ""
263
268
  zenml_api_token = "{self.zenml_server_api_token}"
264
269
  zenml_stack_name = "{self.stack_name}"
265
- zenml_stack_deployment = "{self.deployment}"
270
+ zenml_stack_deployment = "{self.deployment_type}"
266
271
  }}
267
272
  output "zenml_stack_id" {{
268
273
  value = module.zenml_stack.zenml_stack_id
@@ -18,7 +18,10 @@ from typing import ClassVar, Dict, List
18
18
 
19
19
  from zenml.enums import StackDeploymentProvider
20
20
  from zenml.models import StackDeploymentConfig
21
- from zenml.stack_deployments.stack_deployment import ZenMLCloudStackDeployment
21
+ from zenml.stack_deployments.stack_deployment import (
22
+ STACK_DEPLOYMENT_TERRAFORM,
23
+ ZenMLCloudStackDeployment,
24
+ )
22
25
 
23
26
  GCP_DEPLOYMENT_TYPE = "deployment-manager"
24
27
 
@@ -255,7 +258,26 @@ GCP project and to clean up the resources created by the stack by using
255
258
  f"https://shell.cloud.google.com/cloudshell/editor?{query_params}"
256
259
  )
257
260
 
258
- config = f"""
261
+ if self.deployment_type == STACK_DEPLOYMENT_TERRAFORM:
262
+ config = f"""module "zenml_stack" {{
263
+ source = "zenml-io/zenml-stack/gcp"
264
+
265
+ project_id = "my-gcp-project"
266
+ region = "{self.location or "europe-west3"}"
267
+ zenml_server_url = "{self.zenml_server_url}"
268
+ zenml_api_key = ""
269
+ zenml_api_token = "{self.zenml_server_api_token}"
270
+ zenml_stack_name = "{self.stack_name}"
271
+ zenml_stack_deployment = "{self.deployment_type}"
272
+ }}
273
+ output "zenml_stack_id" {{
274
+ value = module.zenml_stack.zenml_stack_id
275
+ }}
276
+ output "zenml_stack_name" {{
277
+ value = module.zenml_stack.zenml_stack_name
278
+ }}"""
279
+ else:
280
+ config = f"""
259
281
  ### BEGIN CONFIGURATION ###
260
282
  ZENML_STACK_NAME={self.stack_name}
261
283
  ZENML_STACK_REGION={self.location or "europe-west3"}
@@ -27,12 +27,15 @@ from zenml.models import (
27
27
  StackDeploymentInfo,
28
28
  )
29
29
 
30
+ STACK_DEPLOYMENT_TERRAFORM = "terraform"
31
+
30
32
 
31
33
  class ZenMLCloudStackDeployment(BaseModel):
32
34
  """ZenML Cloud Stack CLI Deployment base class."""
33
35
 
34
36
  provider: ClassVar[StackDeploymentProvider]
35
37
  deployment: ClassVar[str]
38
+ terraform: bool = False
36
39
  stack_name: str
37
40
  zenml_server_url: str
38
41
  zenml_server_api_token: str
@@ -105,6 +108,17 @@ class ZenMLCloudStackDeployment(BaseModel):
105
108
  names to region descriptions.
106
109
  """
107
110
 
111
+ @property
112
+ def deployment_type(self) -> str:
113
+ """Return the type of deployment.
114
+
115
+ Returns:
116
+ The type of deployment.
117
+ """
118
+ if self.terraform:
119
+ return STACK_DEPLOYMENT_TERRAFORM
120
+ return self.deployment
121
+
108
122
  @classmethod
109
123
  def skypilot_default_regions(cls) -> Dict[str, str]:
110
124
  """Returns the regions supported by default for the Skypilot.
@@ -158,7 +172,8 @@ class ZenMLCloudStackDeployment(BaseModel):
158
172
  """
159
173
 
160
174
  def get_stack(
161
- self, date_start: Optional[datetime.datetime] = None
175
+ self,
176
+ date_start: Optional[datetime.datetime] = None,
162
177
  ) -> Optional[DeployedStack]:
163
178
  """Return the ZenML stack that was deployed and registered.
164
179
 
@@ -201,7 +216,7 @@ class ZenMLCloudStackDeployment(BaseModel):
201
216
  if stack.labels.get("zenml:provider") != self.provider.value:
202
217
  continue
203
218
 
204
- if stack.labels.get("zenml:deployment") != self.deployment:
219
+ if stack.labels.get("zenml:deployment") != self.deployment_type:
205
220
  continue
206
221
 
207
222
  artifact_store = stack.components[
zenml/steps/base_step.py CHANGED
@@ -54,6 +54,7 @@ from zenml.steps.utils import (
54
54
  )
55
55
  from zenml.utils import (
56
56
  dict_utils,
57
+ notebook_utils,
57
58
  pydantic_utils,
58
59
  settings_utils,
59
60
  source_code_utils,
@@ -249,6 +250,8 @@ class BaseStep(metaclass=BaseStepMeta):
249
250
  )
250
251
  self._verify_and_apply_init_params(*args, **kwargs)
251
252
 
253
+ notebook_utils.try_to_save_notebook_cell_code(self.source_object)
254
+
252
255
  @abstractmethod
253
256
  def entrypoint(self, *args: Any, **kwargs: Any) -> Any:
254
257
  """Abstract method for core step logic.
@@ -0,0 +1,149 @@
1
+ # Copyright (c) ZenML GmbH 2024. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at:
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12
+ # or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+ """Archivable mixin."""
15
+
16
+ import io
17
+ import tarfile
18
+ from abc import ABC, abstractmethod
19
+ from pathlib import Path
20
+ from typing import IO, Any, Dict
21
+
22
+ from zenml.io import fileio
23
+
24
+
25
+ class Archivable(ABC):
26
+ """Archivable mixin class."""
27
+
28
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
29
+ """Initialize the object.
30
+
31
+ Args:
32
+ *args: Unused args for subclasses.
33
+ **kwargs: Unused keyword args for subclasses.
34
+ """
35
+ self._extra_files: Dict[str, str] = {}
36
+
37
+ def add_file(self, source: str, destination: str) -> None:
38
+ """Adds a file to the archive.
39
+
40
+ Args:
41
+ source: The source of the file to add. This can either be a path
42
+ or the file content.
43
+ destination: The path inside the archive where the file
44
+ should be added.
45
+ """
46
+ if fileio.exists(source):
47
+ with fileio.open(source) as f:
48
+ self._extra_files[destination] = f.read()
49
+ else:
50
+ self._extra_files[destination] = source
51
+
52
+ def add_directory(self, source: str, destination: str) -> None:
53
+ """Adds a directory to the archive.
54
+
55
+ Args:
56
+ source: Path to the directory.
57
+ destination: The path inside the build context where the directory
58
+ should be added.
59
+
60
+ Raises:
61
+ ValueError: If `source` does not point to a directory.
62
+ """
63
+ if not fileio.isdir(source):
64
+ raise ValueError(
65
+ f"Can't add directory {source} to the build context as it "
66
+ "does not exist or is not a directory."
67
+ )
68
+
69
+ for dir, _, files in fileio.walk(source):
70
+ dir_path = Path(fileio.convert_to_str(dir))
71
+ for file_name in files:
72
+ file_name = fileio.convert_to_str(file_name)
73
+ file_source = dir_path / file_name
74
+ file_destination = (
75
+ Path(destination)
76
+ / dir_path.relative_to(source)
77
+ / file_name
78
+ )
79
+
80
+ with file_source.open("r") as f:
81
+ self._extra_files[file_destination.as_posix()] = f.read()
82
+
83
+ def write_archive(
84
+ self, output_file: IO[bytes], use_gzip: bool = True
85
+ ) -> None:
86
+ """Writes an archive of the build context to the given file.
87
+
88
+ Args:
89
+ output_file: The file to write the archive to.
90
+ use_gzip: Whether to use `gzip` to compress the file.
91
+ """
92
+ files = self.get_files()
93
+ extra_files = self.get_extra_files()
94
+
95
+ if use_gzip:
96
+ from gzip import GzipFile
97
+
98
+ # We don't use the builtin gzip functionality of the `tarfile`
99
+ # library as that one includes the tar filename and creation
100
+ # timestamp in the archive which causes the hash of the resulting
101
+ # file to be different each time. We use this hash to avoid
102
+ # duplicate uploads, which is why we pass empty values for filename
103
+ # and mtime here.
104
+ fileobj: Any = GzipFile(
105
+ filename="", mode="wb", fileobj=output_file, mtime=0.0
106
+ )
107
+ else:
108
+ fileobj = output_file
109
+
110
+ with tarfile.open(mode="w", fileobj=fileobj) as tf:
111
+ for archive_path, file_path in files.items():
112
+ if archive_path in extra_files:
113
+ continue
114
+
115
+ if info := tf.gettarinfo(file_path, arcname=archive_path):
116
+ if info.isfile():
117
+ with open(file_path, "rb") as f:
118
+ tf.addfile(info, f)
119
+ else:
120
+ tf.addfile(info, None)
121
+
122
+ for archive_path, contents in extra_files.items():
123
+ info = tarfile.TarInfo(archive_path)
124
+ contents_encoded = contents.encode("utf-8")
125
+ info.size = len(contents_encoded)
126
+ tf.addfile(info, io.BytesIO(contents_encoded))
127
+
128
+ if use_gzip:
129
+ fileobj.close()
130
+
131
+ output_file.seek(0)
132
+
133
+ @abstractmethod
134
+ def get_files(self) -> Dict[str, str]:
135
+ """Gets all regular files that should be included in the archive.
136
+
137
+ Returns:
138
+ A dict {path_in_archive: path_on_filesystem} for all regular files
139
+ in the archive.
140
+ """
141
+
142
+ def get_extra_files(self) -> Dict[str, str]:
143
+ """Gets all extra files that should be included in the archive.
144
+
145
+ Returns:
146
+ A dict {path_in_archive: file_content} for all extra files in the
147
+ archive.
148
+ """
149
+ return self._extra_files.copy()
@@ -0,0 +1,244 @@
1
+ # Copyright (c) ZenML GmbH 2024. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at:
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12
+ # or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+ """Code utilities."""
15
+
16
+ import hashlib
17
+ import os
18
+ import shutil
19
+ import tempfile
20
+ from pathlib import Path
21
+ from typing import IO, TYPE_CHECKING, Dict, Optional
22
+
23
+ from zenml.client import Client
24
+ from zenml.io import fileio
25
+ from zenml.logger import get_logger
26
+ from zenml.utils import string_utils
27
+ from zenml.utils.archivable import Archivable
28
+
29
+ if TYPE_CHECKING:
30
+ from git.repo.base import Repo
31
+
32
+
33
+ logger = get_logger(__name__)
34
+
35
+
36
+ class CodeArchive(Archivable):
37
+ """Code archive class.
38
+
39
+ This class is used to archive user code before uploading it to the artifact
40
+ store. If the user code is stored in a Git repository, only files not
41
+ excluded by gitignores will be included in the archive.
42
+ """
43
+
44
+ def __init__(self, root: Optional[str] = None) -> None:
45
+ """Initialize the object.
46
+
47
+ Args:
48
+ root: Root directory of the archive.
49
+ """
50
+ super().__init__()
51
+ self._root = root
52
+
53
+ @property
54
+ def git_repo(self) -> Optional["Repo"]:
55
+ """Git repository active at the code archive root.
56
+
57
+ Returns:
58
+ The git repository if available.
59
+ """
60
+ try:
61
+ # These imports fail when git is not installed on the machine
62
+ from git.exc import InvalidGitRepositoryError
63
+ from git.repo.base import Repo
64
+ except ImportError:
65
+ return None
66
+
67
+ try:
68
+ git_repo = Repo(path=self._root, search_parent_directories=True)
69
+ except InvalidGitRepositoryError:
70
+ return None
71
+
72
+ return git_repo
73
+
74
+ def _get_all_files(self, archive_root: str) -> Dict[str, str]:
75
+ """Get all files inside the archive root.
76
+
77
+ Args:
78
+ archive_root: The root directory from which to get all files.
79
+
80
+ Returns:
81
+ All files inside the archive root.
82
+ """
83
+ all_files = {}
84
+ for root, _, files in os.walk(archive_root):
85
+ for file in files:
86
+ file_path = os.path.join(root, file)
87
+ path_in_archive = os.path.relpath(file_path, archive_root)
88
+ all_files[path_in_archive] = file_path
89
+
90
+ return all_files
91
+
92
+ def get_files(self) -> Dict[str, str]:
93
+ """Gets all regular files that should be included in the archive.
94
+
95
+ Raises:
96
+ RuntimeError: If the code archive would not include any files.
97
+
98
+ Returns:
99
+ A dict {path_in_archive: path_on_filesystem} for all regular files
100
+ in the archive.
101
+ """
102
+ if not self._root:
103
+ return {}
104
+
105
+ all_files = {}
106
+
107
+ if repo := self.git_repo:
108
+ try:
109
+ result = repo.git.ls_files(
110
+ "--cached",
111
+ "--others",
112
+ "--modified",
113
+ "--exclude-standard",
114
+ self._root,
115
+ )
116
+ except Exception as e:
117
+ logger.warning(
118
+ "Failed to get non-ignored files from git: %s", str(e)
119
+ )
120
+ all_files = self._get_all_files(archive_root=self._root)
121
+ else:
122
+ for file in result.split():
123
+ file_path = os.path.join(repo.working_dir, file)
124
+ path_in_archive = os.path.relpath(file_path, self._root)
125
+
126
+ if os.path.exists(file_path):
127
+ all_files[path_in_archive] = file_path
128
+ else:
129
+ all_files = self._get_all_files(archive_root=self._root)
130
+
131
+ if not all_files:
132
+ raise RuntimeError(
133
+ "The code archive to be uploaded does not contain any files. "
134
+ "This is probably because all files in your source root "
135
+ f"`{self._root}` are ignored by a .gitignore file."
136
+ )
137
+
138
+ # Explicitly remove .zen directories as we write an updated version
139
+ # to disk everytime ZenML is called. This updates the mtime of the
140
+ # file, which invalidates the code upload caching. The values in
141
+ # the .zen directory are not needed anyway as we set them as
142
+ # environment variables.
143
+ all_files = {
144
+ path_in_archive: file_path
145
+ for path_in_archive, file_path in sorted(all_files.items())
146
+ if ".zen" not in Path(path_in_archive).parts[:-1]
147
+ }
148
+
149
+ return all_files
150
+
151
+ def write_archive(
152
+ self, output_file: IO[bytes], use_gzip: bool = True
153
+ ) -> None:
154
+ """Writes an archive of the build context to the given file.
155
+
156
+ Args:
157
+ output_file: The file to write the archive to.
158
+ use_gzip: Whether to use `gzip` to compress the file.
159
+ """
160
+ super().write_archive(output_file=output_file, use_gzip=use_gzip)
161
+ archive_size = os.path.getsize(output_file.name)
162
+ if archive_size > 20 * 1024 * 1024:
163
+ logger.warning(
164
+ "Code archive size: `%s`. If you believe this is "
165
+ "unreasonably large, make sure to version your code in git and "
166
+ "ignore unnecessary files using a `.gitignore` file.",
167
+ string_utils.get_human_readable_filesize(archive_size),
168
+ )
169
+
170
+
171
+ def upload_code_if_necessary(code_archive: CodeArchive) -> str:
172
+ """Upload code to the artifact store if necessary.
173
+
174
+ This function computes a hash of the code to be uploaded, and if an archive
175
+ with the same hash already exists it will not re-upload but instead return
176
+ the path to the existing archive.
177
+
178
+ Args:
179
+ code_archive: The code archive to upload.
180
+
181
+ Returns:
182
+ The path where to archived code is uploaded.
183
+ """
184
+ artifact_store = Client().active_stack.artifact_store
185
+
186
+ with tempfile.NamedTemporaryFile(
187
+ mode="w+b", delete=False, suffix=".tar.gz"
188
+ ) as f:
189
+ code_archive.write_archive(f)
190
+
191
+ hash_ = hashlib.sha1() # nosec
192
+
193
+ while True:
194
+ data = f.read(64 * 1024)
195
+ if not data:
196
+ break
197
+ hash_.update(data)
198
+
199
+ filename = f"{hash_.hexdigest()}.tar.gz"
200
+ upload_dir = os.path.join(artifact_store.path, "code_uploads")
201
+ fileio.makedirs(upload_dir)
202
+ upload_path = os.path.join(upload_dir, filename)
203
+
204
+ if not fileio.exists(upload_path):
205
+ archive_size = string_utils.get_human_readable_filesize(
206
+ os.path.getsize(f.name)
207
+ )
208
+ logger.info(
209
+ "Uploading code to `%s` (Size: %s).", upload_path, archive_size
210
+ )
211
+ fileio.copy(f.name, upload_path)
212
+ logger.info("Code upload finished.")
213
+ else:
214
+ logger.info(
215
+ "Code already exists in artifact store, skipping upload."
216
+ )
217
+
218
+ if os.path.exists(f.name):
219
+ os.remove(f.name)
220
+
221
+ return upload_path
222
+
223
+
224
+ def download_and_extract_code(code_path: str, extract_dir: str) -> None:
225
+ """Download and extract code.
226
+
227
+ Args:
228
+ code_path: Path where the code is uploaded.
229
+ extract_dir: Directory where to code should be extracted to.
230
+
231
+ Raises:
232
+ RuntimeError: If the code is stored in an artifact store which is
233
+ not active.
234
+ """
235
+ artifact_store = Client().active_stack.artifact_store
236
+
237
+ if not code_path.startswith(artifact_store.path):
238
+ raise RuntimeError("Code stored in different artifact store.")
239
+
240
+ download_path = os.path.basename(code_path)
241
+ fileio.copy(code_path, download_path)
242
+
243
+ shutil.unpack_archive(filename=download_path, extract_dir=extract_dir)
244
+ os.remove(download_path)