zenml-nightly 0.63.0.dev20240801__py3-none-any.whl → 0.64.0.dev20240811__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +2 -2
- RELEASE_NOTES.md +79 -0
- zenml/VERSION +1 -1
- zenml/__init__.py +0 -4
- zenml/analytics/enums.py +0 -6
- zenml/cli/__init__.py +0 -61
- zenml/cli/base.py +1 -1
- zenml/cli/web_login.py +8 -0
- zenml/client.py +0 -4
- zenml/config/build_configuration.py +43 -17
- zenml/config/docker_settings.py +80 -57
- zenml/config/source.py +58 -0
- zenml/constants.py +9 -2
- zenml/entrypoints/base_entrypoint_configuration.py +53 -8
- zenml/enums.py +1 -1
- zenml/environment.py +25 -9
- zenml/image_builders/base_image_builder.py +1 -1
- zenml/image_builders/build_context.py +25 -72
- zenml/integrations/azure/__init__.py +4 -0
- zenml/integrations/azure/flavors/__init__.py +11 -0
- zenml/integrations/azure/flavors/azureml_orchestrator_flavor.py +263 -0
- zenml/{_hub → integrations/azure/orchestrators}/__init__.py +7 -2
- zenml/integrations/azure/orchestrators/azureml_orchestrator.py +544 -0
- zenml/integrations/azure/orchestrators/azureml_orchestrator_entrypoint_config.py +86 -0
- zenml/integrations/azure/step_operators/azureml_step_operator.py +3 -0
- zenml/integrations/databricks/flavors/databricks_orchestrator_flavor.py +9 -0
- zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +7 -2
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py +123 -6
- zenml/integrations/kaniko/image_builders/kaniko_image_builder.py +1 -1
- zenml/integrations/mlflow/__init__.py +1 -1
- zenml/integrations/mlflow/experiment_trackers/mlflow_experiment_tracker.py +3 -1
- zenml/integrations/mlflow/flavors/mlflow_experiment_tracker_flavor.py +3 -0
- zenml/logger.py +13 -0
- zenml/models/__init__.py +0 -12
- zenml/models/v2/core/pipeline_deployment.py +21 -29
- zenml/models/v2/core/pipeline_run.py +13 -0
- zenml/models/v2/core/server_settings.py +12 -0
- zenml/models/v2/core/user.py +0 -21
- zenml/models/v2/misc/server_models.py +7 -1
- zenml/models/v2/misc/user_auth.py +0 -7
- zenml/new/pipelines/build_utils.py +193 -38
- zenml/new/pipelines/code_archive.py +157 -0
- zenml/new/pipelines/pipeline.py +29 -2
- zenml/new/pipelines/run_utils.py +67 -1
- zenml/service_connectors/service_connector_utils.py +14 -0
- zenml/stack_deployments/aws_stack_deployment.py +26 -3
- zenml/stack_deployments/azure_stack_deployment.py +11 -6
- zenml/stack_deployments/gcp_stack_deployment.py +24 -2
- zenml/stack_deployments/stack_deployment.py +17 -2
- zenml/steps/base_step.py +3 -0
- zenml/utils/archivable.py +149 -0
- zenml/utils/code_utils.py +244 -0
- zenml/utils/notebook_utils.py +122 -0
- zenml/utils/pipeline_docker_image_builder.py +3 -96
- zenml/utils/source_utils.py +109 -1
- zenml/zen_server/dashboard/assets/{404-CI13wQp4.js → 404-CRAA_Lew.js} +1 -1
- zenml/zen_server/dashboard/assets/@radix-BXWm7HOa.js +85 -0
- zenml/zen_server/dashboard/assets/{@react-router-CO-OsFwI.js → @react-router-l3lMcXA2.js} +1 -1
- zenml/zen_server/dashboard/assets/{@reactflow-DIYUhKYX.js → @reactflow-CeVxyqYT.js} +2 -2
- zenml/zen_server/dashboard/assets/{@tanstack-k96lU_C-.js → @tanstack-FmcYZMuX.js} +4 -4
- zenml/zen_server/dashboard/assets/AlertDialogDropdownItem-ErO9aOgK.js +1 -0
- zenml/zen_server/dashboard/assets/{AwarenessChannel-BNg5uWgI.js → AwarenessChannel-CLXo5rKM.js} +1 -1
- zenml/zen_server/dashboard/assets/{CodeSnippet-Cyp7f4dM.js → CodeSnippet-D0VLxT2A.js} +1 -1
- zenml/zen_server/dashboard/assets/{CollapsibleCard-Cu_A9W57.js → CollapsibleCard-BaUPiVg0.js} +1 -1
- zenml/zen_server/dashboard/assets/{Commands-DmQwTXjj.js → Commands-JrcZK-3j.js} +1 -1
- zenml/zen_server/dashboard/assets/CopyButton-Dbo52T1K.js +2 -0
- zenml/zen_server/dashboard/assets/{CsvVizualization-BvqItd-O.js → CsvVizualization-D3kAypDj.js} +3 -3
- zenml/zen_server/dashboard/assets/DisplayDate-DizbSeT-.js +1 -0
- zenml/zen_server/dashboard/assets/EditSecretDialog-Bd7mFLS4.js +1 -0
- zenml/zen_server/dashboard/assets/{EmptyState-BMLnFVlB.js → EmptyState-BHblM39I.js} +1 -1
- zenml/zen_server/dashboard/assets/{Error-DbXCTGua.js → Error-C6LeJSER.js} +1 -1
- zenml/zen_server/dashboard/assets/{ExecutionStatus-9zM7eaLh.js → ExecutionStatus-jH4OrWBq.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-BIiNc-uH.js → Helpbox-aAB2XP-z.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-iv1Nu1A0.js → Infobox-BQ0aty32.js} +1 -1
- zenml/zen_server/dashboard/assets/{InlineAvatar-BvBtO2Dp.js → InlineAvatar-DpTLgM3Q.js} +1 -1
- zenml/zen_server/dashboard/assets/Lock-CNyJvf2r.js +1 -0
- zenml/zen_server/dashboard/assets/{MarkdownVisualization-xp3hhULl.js → MarkdownVisualization-Bajxn0HY.js} +1 -1
- zenml/zen_server/dashboard/assets/NumberBox-BmKE0qnO.js +1 -0
- zenml/zen_server/dashboard/assets/{PasswordChecker-DUveqlva.js → PasswordChecker-yGGoJSB-.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderRadio-pSAvrGRS.js → ProviderRadio-BBqkIuTd.js} +1 -1
- zenml/zen_server/dashboard/assets/RadioItem-xLhXoiFV.js +1 -0
- zenml/zen_server/dashboard/assets/SearchField-C9R0mdaX.js +1 -0
- zenml/zen_server/dashboard/assets/{SetPassword-BOxpgh6N.js → SetPassword-52sNxNiO.js} +1 -1
- zenml/zen_server/dashboard/assets/{SuccessStep-CTSKN2lp.js → SuccessStep-DlkItqYG.js} +1 -1
- zenml/zen_server/dashboard/assets/{Tick-Bnr2TpW6.js → Tick-uxv80Q6a.js} +1 -1
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-BeCeaRW5.js → UpdatePasswordSchemas-oN4G3sKz.js} +1 -1
- zenml/zen_server/dashboard/assets/{aws-BgKTfTfx.js → aws-0_3UsPif.js} +1 -1
- zenml/zen_server/dashboard/assets/{check-circle-i56092KI.js → check-circle-1_I207rW.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-down-D_ZlKMqH.js → chevron-down-BpaF8JqM.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-right-double-CZBOf6JM.js → chevron-right-double-Dk8e2L99.js} +1 -1
- zenml/zen_server/dashboard/assets/{cloud-only-qelmY92E.js → cloud-only-BkUuI0lZ.js} +1 -1
- zenml/zen_server/dashboard/assets/components-Br2ezRib.js +1 -0
- zenml/zen_server/dashboard/assets/{copy-BXNk6BjL.js → copy-f3XGPPxt.js} +1 -1
- zenml/zen_server/dashboard/assets/{database-1xWSgZfO.js → database-cXYNX9tt.js} +1 -1
- zenml/zen_server/dashboard/assets/{docker-CQMVm_4d.js → docker-8uj__HHK.js} +1 -1
- zenml/zen_server/dashboard/assets/{dots-horizontal-BObFzD5l.js → dots-horizontal-sKQlWEni.js} +1 -1
- zenml/zen_server/dashboard/assets/edit-C0MVvPD2.js +1 -0
- zenml/zen_server/dashboard/assets/{file-text-CqD_iu6l.js → file-text-B9JibxTs.js} +1 -1
- zenml/zen_server/dashboard/assets/{help-bu_DgLKI.js → help-FuHlZwn0.js} +1 -1
- zenml/zen_server/dashboard/assets/index-Bd1xgUQG.js +1 -0
- zenml/zen_server/dashboard/assets/index-DaGknux4.css +1 -0
- zenml/zen_server/dashboard/assets/{index-KsTz2dHG.js → index-DhIZtpxB.js} +5 -5
- zenml/zen_server/dashboard/assets/{index.esm-CbHNSeVw.js → index.esm-DT4uyn2i.js} +1 -1
- zenml/zen_server/dashboard/assets/layout-D6oiSbfd.js +1 -0
- zenml/zen_server/dashboard/assets/{login-mutation-DRpbESS7.js → login-mutation-13A_JSVA.js} +1 -1
- zenml/zen_server/dashboard/assets/{logs-D8k8BVFf.js → logs-CgeE2vZP.js} +1 -1
- zenml/zen_server/dashboard/assets/{not-found-Dfx9hfkf.js → not-found-B0Mmb90p.js} +1 -1
- zenml/zen_server/dashboard/assets/{package-ClbU3KUi.js → package-DdkziX79.js} +1 -1
- zenml/zen_server/dashboard/assets/page-7-v2OBm-.js +1 -0
- zenml/zen_server/dashboard/assets/{page-f3jBVI5Z.js → page-B3ozwdD1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DYBNGxJt.js → page-BGwA9B1M.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-C176KxyB.js → page-BkjAUyTA.js} +1 -1
- zenml/zen_server/dashboard/assets/page-BnacgBiy.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CzucfYPo.js → page-BxF_KMQ3.js} +2 -2
- zenml/zen_server/dashboard/assets/page-C4POHC0K.js +1 -0
- zenml/zen_server/dashboard/assets/page-C9kudd44.js +9 -0
- zenml/zen_server/dashboard/assets/page-CA1j3GpJ.js +1 -0
- zenml/zen_server/dashboard/assets/page-CCY6yfmu.js +1 -0
- zenml/zen_server/dashboard/assets/page-CgTe7Bme.js +1 -0
- zenml/zen_server/dashboard/assets/{page-DtpwnNXq.js → page-Cgn-6v2Y.js} +1 -1
- zenml/zen_server/dashboard/assets/page-CxQmQqDw.js +1 -0
- zenml/zen_server/dashboard/assets/page-D2Goey3H.js +1 -0
- zenml/zen_server/dashboard/assets/page-DLpOnf7u.js +1 -0
- zenml/zen_server/dashboard/assets/{page-DVPxY5fT.js → page-DSTQnBk-.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BoFtUD9H.js → page-DTysUGOy.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-p2hLJdS2.js → page-D_EXUFJb.js} +1 -1
- zenml/zen_server/dashboard/assets/page-Db15QzsM.js +1 -0
- zenml/zen_server/dashboard/assets/{page-Btu39x7k.js → page-DugsjcQ_.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CZe9GEBF.js → page-OFKSPyN7.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CDgZmwxP.js → page-RnG-qhv9.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Cjn97HMv.js → page-T2BtjwPl.js} +1 -1
- zenml/zen_server/dashboard/assets/page-TXe1Eo3Z.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BxiWdeyg.js → page-YiF_fNbe.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-399pVZHU.js → page-hQaiQXfg.js} +1 -1
- zenml/zen_server/dashboard/assets/persist-3-5nOJ6m.js +1 -0
- zenml/zen_server/dashboard/assets/{play-circle-CNtZKDnW.js → play-circle-XSkLR12B.js} +1 -1
- zenml/zen_server/dashboard/assets/{plus-DOeLmm7C.js → plus-FB9-lEq_.js} +1 -1
- zenml/zen_server/dashboard/assets/refresh-COb6KYDi.js +1 -0
- zenml/zen_server/dashboard/assets/sharedSchema-BoYx_B_L.js +14 -0
- zenml/zen_server/dashboard/assets/{stack-detail-query-Ck7j7BP_.js → stack-detail-query-B-US_-wa.js} +1 -1
- zenml/zen_server/dashboard/assets/{terminal-By9cErXc.js → terminal-grtjrIEJ.js} +1 -1
- zenml/zen_server/dashboard/assets/trash-Cd5CSFqA.js +1 -0
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-f3ZT7psb.js → update-server-settings-mutation-B8GB_ubU.js} +1 -1
- zenml/zen_server/dashboard/assets/{url-rGEp5Umh.js → url-hcMJkz8p.js} +1 -1
- zenml/zen_server/dashboard/assets/{zod-BtSyGx4C.js → zod-CnykDKJj.js} +1 -1
- zenml/zen_server/dashboard/index.html +7 -7
- zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
- zenml/zen_server/dashboard_legacy/index.html +1 -1
- zenml/zen_server/dashboard_legacy/{precache-manifest.2fa6e528a6e7447caaf35dadfe7514bb.js → precache-manifest.9c473c96a43298343a7ce1256183123b.js} +4 -4
- zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
- zenml/zen_server/dashboard_legacy/static/js/{main.4aab7e98.chunk.js → main.463c90b9.chunk.js} +2 -2
- zenml/zen_server/dashboard_legacy/static/js/{main.4aab7e98.chunk.js.map → main.463c90b9.chunk.js.map} +1 -1
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_server/routers/stack_deployment_endpoints.py +6 -0
- zenml/zen_server/routers/users_endpoints.py +0 -7
- zenml/zen_server/utils.py +75 -0
- zenml/zen_server/zen_server_api.py +52 -1
- zenml/zen_stores/base_zen_store.py +7 -1
- zenml/zen_stores/migrations/versions/0.64.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/026d4577b6a0_add_code_path.py +39 -0
- zenml/zen_stores/migrations/versions/3dcc5d20e82f_add_last_user_activity.py +51 -0
- zenml/zen_stores/migrations/versions/909550c7c4da_remove_user_hub_token.py +36 -0
- zenml/zen_stores/rest_zen_store.py +5 -3
- zenml/zen_stores/schemas/pipeline_deployment_schemas.py +3 -0
- zenml/zen_stores/schemas/pipeline_run_schemas.py +3 -0
- zenml/zen_stores/schemas/server_settings_schemas.py +2 -0
- zenml/zen_stores/schemas/user_schemas.py +0 -2
- zenml/zen_stores/sql_zen_store.py +25 -1
- {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240811.dist-info}/METADATA +3 -3
- {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240811.dist-info}/RECORD +174 -157
- zenml/_hub/client.py +0 -289
- zenml/_hub/constants.py +0 -21
- zenml/_hub/utils.py +0 -79
- zenml/cli/hub.py +0 -1116
- zenml/models/v2/misc/hub_plugin_models.py +0 -79
- zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +0 -85
- zenml/zen_server/dashboard/assets/CopyButton-B3sWVJ4Z.js +0 -2
- zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +0 -1
- zenml/zen_server/dashboard/assets/SearchField-CXoBknpt.js +0 -1
- zenml/zen_server/dashboard/assets/components-DWe4cTjS.js +0 -1
- zenml/zen_server/dashboard/assets/index-vfjX_fJV.css +0 -1
- zenml/zen_server/dashboard/assets/page-C6tXXjnK.js +0 -1
- zenml/zen_server/dashboard/assets/page-CP9obrnG.js +0 -1
- zenml/zen_server/dashboard/assets/page-CaTOsNNw.js +0 -1
- zenml/zen_server/dashboard/assets/page-CmXmB_5i.js +0 -1
- zenml/zen_server/dashboard/assets/page-CvGAOfad.js +0 -1
- zenml/zen_server/dashboard/assets/page-D0bbc-qr.js +0 -5
- zenml/zen_server/dashboard/assets/page-DLEtD2ex.js +0 -1
- zenml/zen_server/dashboard/assets/page-DupV0aBd.js +0 -1
- zenml/zen_server/dashboard/assets/page-EweAR81y.js +0 -1
- zenml/zen_server/dashboard/assets/page-w-YaL77M.js +0 -9
- zenml/zen_server/dashboard/assets/persist-BReKApOc.js +0 -14
- zenml/zen_server/dashboard/assets/secrets-video-OBJ6irhH.svg +0 -21
- zenml/zen_server/dashboard/assets/stacks-video-7gfxpAq4.svg +0 -21
- {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240811.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240811.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.63.0.dev20240801.dist-info → zenml_nightly-0.64.0.dev20240811.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
|
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=
|
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] =
|
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
|
-
-
|
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"
|
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
|
-
"
|
140
|
-
"
|
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.
|
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
|
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
|
-
|
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,
|
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.
|
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)
|