zenml-nightly 0.58.2.dev20240626__py3-none-any.whl → 0.61.0.dev20240710__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +30 -9
- RELEASE_NOTES.md +240 -0
- zenml/VERSION +1 -1
- zenml/analytics/enums.py +3 -0
- zenml/cli/__init__.py +28 -0
- zenml/cli/artifact.py +1 -2
- zenml/cli/integration.py +9 -8
- zenml/cli/server.py +6 -0
- zenml/cli/stack.py +797 -39
- zenml/cli/stack_components.py +7 -0
- zenml/cli/text_utils.py +35 -1
- zenml/cli/utils.py +127 -10
- zenml/client.py +23 -14
- zenml/config/docker_settings.py +8 -5
- zenml/constants.py +5 -1
- zenml/container_registries/base_container_registry.py +1 -0
- zenml/enums.py +6 -0
- zenml/event_hub/event_hub.py +5 -8
- zenml/integrations/aws/__init__.py +1 -0
- zenml/integrations/azure/__init__.py +1 -0
- zenml/integrations/deepchecks/__init__.py +1 -0
- zenml/integrations/discord/__init__.py +1 -0
- zenml/integrations/evidently/__init__.py +1 -0
- zenml/integrations/facets/__init__.py +1 -0
- zenml/integrations/feast/__init__.py +1 -0
- zenml/integrations/gcp/__init__.py +3 -1
- zenml/integrations/gcp/google_credentials_mixin.py +1 -1
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py +266 -58
- zenml/integrations/huggingface/__init__.py +1 -0
- zenml/integrations/integration.py +24 -0
- zenml/integrations/kubeflow/__init__.py +3 -0
- zenml/integrations/kubeflow/flavors/kubeflow_orchestrator_flavor.py +1 -1
- zenml/integrations/kubeflow/orchestrators/kubeflow_orchestrator.py +0 -1
- zenml/integrations/kubernetes/__init__.py +3 -1
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +4 -1
- zenml/integrations/label_studio/annotators/label_studio_annotator.py +1 -0
- zenml/integrations/langchain/__init__.py +1 -0
- zenml/integrations/mlflow/__init__.py +3 -1
- zenml/integrations/neural_prophet/__init__.py +1 -0
- zenml/integrations/polars/__init__.py +1 -0
- zenml/integrations/prodigy/__init__.py +1 -0
- zenml/integrations/pycaret/__init__.py +6 -0
- zenml/integrations/registry.py +37 -0
- zenml/integrations/s3/artifact_stores/s3_artifact_store.py +17 -6
- zenml/integrations/seldon/__init__.py +1 -0
- zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -0
- zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +2 -2
- zenml/integrations/skypilot/orchestrators/skypilot_base_vm_orchestrator.py +1 -1
- zenml/integrations/skypilot/orchestrators/skypilot_orchestrator_entrypoint.py +2 -2
- zenml/integrations/skypilot_aws/__init__.py +2 -1
- zenml/integrations/skypilot_azure/__init__.py +1 -1
- zenml/integrations/skypilot_gcp/__init__.py +1 -1
- zenml/integrations/skypilot_lambda/__init__.py +1 -1
- zenml/integrations/skypilot_lambda/flavors/skypilot_orchestrator_lambda_vm_flavor.py +1 -1
- zenml/integrations/slack/__init__.py +1 -0
- zenml/integrations/tekton/__init__.py +1 -0
- zenml/integrations/tensorboard/__init__.py +0 -1
- zenml/integrations/tensorflow/__init__.py +18 -6
- zenml/integrations/wandb/__init__.py +1 -0
- zenml/models/__init__.py +9 -0
- zenml/models/v2/core/component.py +18 -0
- zenml/models/v2/core/model.py +1 -2
- zenml/models/v2/core/service_connector.py +17 -0
- zenml/models/v2/core/stack.py +31 -0
- zenml/models/v2/misc/full_stack.py +97 -0
- zenml/models/v2/misc/stack_deployment.py +66 -0
- zenml/new/pipelines/pipeline.py +1 -1
- zenml/orchestrators/input_utils.py +3 -6
- zenml/stack/stack.py +3 -6
- zenml/stack_deployments/__init__.py +14 -0
- zenml/stack_deployments/aws_stack_deployment.py +289 -0
- zenml/stack_deployments/stack_deployment.py +130 -0
- zenml/stack_deployments/utils.py +40 -0
- zenml/utils/function_utils.py +1 -1
- zenml/utils/pagination_utils.py +7 -5
- zenml/utils/pipeline_docker_image_builder.py +97 -68
- zenml/utils/pydantic_utils.py +6 -5
- zenml/zen_server/cloud_utils.py +18 -3
- zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-DpJaNHKF.js} +1 -1
- zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +85 -0
- zenml/zen_server/dashboard/assets/{@react-router-DYovave8.js → @react-router-CO-OsFwI.js} +2 -2
- zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-DJfzkHO1.js} +2 -2
- zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
- zenml/zen_server/dashboard/assets/AwarenessChannel-BYDLT2xC.js +1 -0
- zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-BkOuRmyq.js} +2 -2
- zenml/zen_server/dashboard/assets/Commands-ZvWR1BRs.js +1 -0
- zenml/zen_server/dashboard/assets/CopyButton-DVwLkafa.js +2 -0
- zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-C2IiqX4I.js} +7 -7
- zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +1 -0
- zenml/zen_server/dashboard/assets/EmptyState-BMLnFVlB.js +1 -0
- zenml/zen_server/dashboard/assets/Error-CqX0VqW_.js +1 -0
- zenml/zen_server/dashboard/assets/ExecutionStatus-BoLUXR9t.js +1 -0
- zenml/zen_server/dashboard/assets/Helpbox-LFydyVwh.js +1 -0
- zenml/zen_server/dashboard/assets/Infobox-DnENC0sh.js +1 -0
- zenml/zen_server/dashboard/assets/InlineAvatar-CbJtYr0t.js +1 -0
- zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
- zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +1 -0
- zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
- zenml/zen_server/dashboard/assets/SetPassword-BYBdbQDo.js +1 -0
- zenml/zen_server/dashboard/assets/SuccessStep-Nx743hll.js +1 -0
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DF9gSzE0.js} +1 -1
- zenml/zen_server/dashboard/assets/{aws-t0gKCj_R.js → aws-BgKTfTfx.js} +1 -1
- zenml/zen_server/dashboard/assets/{check-circle-BVvhm5dy.js → check-circle-i56092KI.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-down-zcvCWmyP.js → chevron-down-D_ZlKMqH.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-BiEMg7rd.js} +1 -1
- zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.js +1 -0
- zenml/zen_server/dashboard/assets/{copy-BRhQz3j-.js → copy-BXNk6BjL.js} +1 -1
- zenml/zen_server/dashboard/assets/{database-CRRnyFWh.js → database-1xWSgZfO.js} +1 -1
- zenml/zen_server/dashboard/assets/{docker-BAonhm6G.js → docker-CQMVm_4d.js} +1 -1
- zenml/zen_server/dashboard/assets/{file-text-CbVERUON.js → file-text-CqD_iu6l.js} +1 -1
- zenml/zen_server/dashboard/assets/{help-B8rqCvqn.js → help-bu_DgLKI.js} +1 -1
- zenml/zen_server/dashboard/assets/index-C_CrU4vI.js +1 -0
- zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +55 -0
- zenml/zen_server/dashboard/assets/index-inApY3KQ.css +1 -0
- zenml/zen_server/dashboard/assets/index-rK_Wuy2W.js +1 -0
- zenml/zen_server/dashboard/assets/index.esm-Corw4lXQ.js +1 -0
- zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-BUnVASxp.js} +1 -1
- zenml/zen_server/dashboard/assets/not-found-B4VnX8gK.js +1 -0
- zenml/zen_server/dashboard/assets/package-CsUhPmou.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-3efNCDeb.js} +2 -2
- zenml/zen_server/dashboard/assets/page-7zTHbhhI.js +1 -0
- zenml/zen_server/dashboard/assets/page-BEs6jK71.js +1 -0
- zenml/zen_server/dashboard/assets/page-BpSqIf4B.js +1 -0
- zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-Bx6o0ARS.js} +1 -1
- zenml/zen_server/dashboard/assets/page-C43QGHTt.js +9 -0
- zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +1 -0
- zenml/zen_server/dashboard/assets/page-CRTJ0UuR.js +1 -0
- zenml/zen_server/dashboard/assets/page-CUZIGO-3.js +1 -0
- zenml/zen_server/dashboard/assets/page-CaopxiU1.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-Cx67M0QT.js} +1 -1
- zenml/zen_server/dashboard/assets/page-D7Z399xy.js +1 -0
- zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-DKlIdAe5.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-DMOYZppS.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +2 -0
- zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-Dc_7KMQE.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DvCvroOM.js +1 -0
- zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +1 -0
- zenml/zen_server/dashboard/assets/page-JyfeDUfu.js +1 -0
- zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-Sxn82W-5.js} +1 -1
- zenml/zen_server/dashboard/assets/page-TKXERe16.js +1 -0
- zenml/zen_server/dashboard/assets/page-Xu8JEjSU.js +1 -0
- zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
- zenml/zen_server/dashboard/assets/plus-DOeLmm7C.js +1 -0
- zenml/zen_server/dashboard/assets/{terminal-B2ovgWuz.js → terminal-By9cErXc.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-0Wgz8pUE.js → update-server-settings-mutation-CR8e3Sir.js} +1 -1
- zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-DuQMeqYA.js} +1 -1
- zenml/zen_server/dashboard/assets/{zod-DrZvVLjd.js → zod-BhoGpZ63.js} +1 -1
- zenml/zen_server/dashboard/index.html +7 -7
- zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
- zenml/zen_server/dashboard_legacy/index.html +1 -1
- zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.c8c57fb0d2132b1d3c2119e776b7dfb3.js} +4 -4
- zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js → main.382439a7.chunk.js} +2 -2
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.382439a7.chunk.js.map} +1 -1
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +11 -5
- zenml/zen_server/pipeline_deployment/utils.py +57 -44
- zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
- zenml/zen_server/routers/stack_deployment_endpoints.py +144 -0
- zenml/zen_server/routers/workspaces_endpoints.py +64 -0
- zenml/zen_server/zen_server_api.py +2 -0
- zenml/zen_stores/migrations/utils.py +1 -1
- zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/0.61.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/0d707865f404_adding_labels_to_stacks.py +30 -0
- zenml/zen_stores/rest_zen_store.py +117 -0
- zenml/zen_stores/schemas/stack_schemas.py +10 -0
- zenml/zen_stores/schemas/step_run_schemas.py +27 -11
- zenml/zen_stores/sql_zen_store.py +283 -0
- zenml/zen_stores/zen_store_interface.py +79 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/METADATA +32 -10
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/RECORD +177 -162
- zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js +0 -77
- zenml/zen_server/dashboard/assets/@tanstack-CEbkxrhX.js +0 -30
- zenml/zen_server/dashboard/assets/AwarenessChannel-nXGpmj_f.js +0 -1
- zenml/zen_server/dashboard/assets/Cards-nwsvQLVS.js +0 -1
- zenml/zen_server/dashboard/assets/Commands-DuIWKg_Q.js +0 -1
- zenml/zen_server/dashboard/assets/CopyButton-B_YSm-Ds.js +0 -2
- zenml/zen_server/dashboard/assets/DisplayDate-BdguISQF.js +0 -1
- zenml/zen_server/dashboard/assets/EmptyState-BkooiGtL.js +0 -1
- zenml/zen_server/dashboard/assets/Error-B6M0dPph.js +0 -1
- zenml/zen_server/dashboard/assets/Helpbox-BQoqCm04.js +0 -1
- zenml/zen_server/dashboard/assets/Infobox-Ce9mefqU.js +0 -1
- zenml/zen_server/dashboard/assets/InlineAvatar-DGf3dVhV.js +0 -1
- zenml/zen_server/dashboard/assets/PageHeader-DGaemzjc.js +0 -1
- zenml/zen_server/dashboard/assets/Pagination-DVYfBCCc.js +0 -1
- zenml/zen_server/dashboard/assets/PasswordChecker-DSLBp7Vl.js +0 -1
- zenml/zen_server/dashboard/assets/SetPassword-B5s7DJug.js +0 -1
- zenml/zen_server/dashboard/assets/SuccessStep-ZzczaM7g.js +0 -1
- zenml/zen_server/dashboard/assets/cloud-only-Ba_ShBR5.js +0 -1
- zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
- zenml/zen_server/dashboard/assets/index-QORVVTMN.js +0 -55
- zenml/zen_server/dashboard/assets/index.esm-F7nqy9zY.js +0 -1
- zenml/zen_server/dashboard/assets/not-found-Dh2la7kh.js +0 -1
- zenml/zen_server/dashboard/assets/page-B-5jAKoO.js +0 -1
- zenml/zen_server/dashboard/assets/page-B-vWk8a6.js +0 -1
- zenml/zen_server/dashboard/assets/page-B0BrqfS8.js +0 -1
- zenml/zen_server/dashboard/assets/page-BQxVFlUl.js +0 -1
- zenml/zen_server/dashboard/assets/page-ByrHy6Ss.js +0 -1
- zenml/zen_server/dashboard/assets/page-CPtY4Kv_.js +0 -1
- zenml/zen_server/dashboard/assets/page-CmmukLsl.js +0 -1
- zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
- zenml/zen_server/dashboard/assets/page-DAQQyLxT.js +0 -1
- zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
- zenml/zen_server/dashboard/assets/page-DZCbwOEs.js +0 -2
- zenml/zen_server/dashboard/assets/page-DdaIt20-.js +0 -1
- zenml/zen_server/dashboard/assets/page-LqLs24Ot.js +0 -1
- zenml/zen_server/dashboard/assets/page-lebv0c7C.js +0 -1
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/entry_points.txt +0 -0
@@ -43,7 +43,8 @@ from google.auth._default import (
|
|
43
43
|
_get_external_account_credentials,
|
44
44
|
)
|
45
45
|
from google.auth.transport.requests import Request
|
46
|
-
from google.cloud import container_v1, storage
|
46
|
+
from google.cloud import artifactregistry_v1, container_v1, storage
|
47
|
+
from google.cloud.location import locations_pb2
|
47
48
|
from google.oauth2 import credentials as gcp_credentials
|
48
49
|
from google.oauth2 import service_account as gcp_service_account
|
49
50
|
from pydantic import Field, field_validator, model_validator
|
@@ -351,24 +352,72 @@ class GCPOAuth2Token(AuthenticationConfig):
|
|
351
352
|
class GCPBaseConfig(AuthenticationConfig):
|
352
353
|
"""GCP base configuration."""
|
353
354
|
|
355
|
+
@property
|
356
|
+
def gcp_project_id(self) -> str:
|
357
|
+
"""Get the GCP project ID.
|
358
|
+
|
359
|
+
This method must be implemented by subclasses to ensure that the GCP
|
360
|
+
project ID is always available.
|
361
|
+
|
362
|
+
Raises:
|
363
|
+
NotImplementedError: If the method is not implemented.
|
364
|
+
"""
|
365
|
+
raise NotImplementedError
|
366
|
+
|
367
|
+
|
368
|
+
class GCPBaseProjectIDConfig(GCPBaseConfig):
|
369
|
+
"""GCP base configuration with included project ID."""
|
370
|
+
|
354
371
|
project_id: str = Field(
|
355
372
|
title="GCP Project ID where the target resource is located.",
|
356
373
|
)
|
357
374
|
|
375
|
+
@property
|
376
|
+
def gcp_project_id(self) -> str:
|
377
|
+
"""Get the GCP project ID.
|
378
|
+
|
379
|
+
Returns:
|
380
|
+
The GCP project ID.
|
381
|
+
"""
|
382
|
+
return self.project_id
|
358
383
|
|
359
|
-
|
384
|
+
|
385
|
+
class GCPUserAccountConfig(GCPBaseProjectIDConfig, GCPUserAccountCredentials):
|
360
386
|
"""GCP user account configuration."""
|
361
387
|
|
362
388
|
|
363
389
|
class GCPServiceAccountConfig(GCPBaseConfig, GCPServiceAccountCredentials):
|
364
390
|
"""GCP service account configuration."""
|
365
391
|
|
392
|
+
_project_id: Optional[str] = None
|
393
|
+
|
394
|
+
@property
|
395
|
+
def gcp_project_id(self) -> str:
|
396
|
+
"""Get the GCP project ID.
|
397
|
+
|
398
|
+
When a service account JSON is provided, the project ID can be extracted
|
399
|
+
from it instead of being provided explicitly.
|
400
|
+
|
401
|
+
Returns:
|
402
|
+
The GCP project ID.
|
403
|
+
"""
|
404
|
+
if self._project_id is None:
|
405
|
+
self._project_id = json.loads(
|
406
|
+
self.service_account_json.get_secret_value()
|
407
|
+
)["project_id"]
|
408
|
+
# Guaranteed by the field validator
|
409
|
+
assert self._project_id is not None
|
410
|
+
|
411
|
+
return self._project_id
|
412
|
+
|
366
413
|
|
367
|
-
class GCPExternalAccountConfig(
|
414
|
+
class GCPExternalAccountConfig(
|
415
|
+
GCPBaseProjectIDConfig, GCPExternalAccountCredentials
|
416
|
+
):
|
368
417
|
"""GCP external account configuration."""
|
369
418
|
|
370
419
|
|
371
|
-
class GCPOAuth2TokenConfig(
|
420
|
+
class GCPOAuth2TokenConfig(GCPBaseProjectIDConfig, GCPOAuth2Token):
|
372
421
|
"""GCP OAuth 2.0 configuration."""
|
373
422
|
|
374
423
|
service_account_email: Optional[str] = Field(
|
@@ -540,7 +589,7 @@ resources in the specified project. When used remotely in a GCP workload, the
|
|
540
589
|
configured project has to be the same as the project of the attached service
|
541
590
|
account.
|
542
591
|
""",
|
543
|
-
config_class=
|
592
|
+
config_class=GCPBaseProjectIDConfig,
|
544
593
|
),
|
545
594
|
AuthenticationMethodModel(
|
546
595
|
name="GCP User Account",
|
@@ -786,14 +835,53 @@ GKE clusters in the GCP project that it is configured to use.
|
|
786
835
|
emoji=":cyclone:",
|
787
836
|
),
|
788
837
|
ResourceTypeModel(
|
789
|
-
name="GCP
|
838
|
+
name="GCP GAR container registry",
|
790
839
|
resource_type=DOCKER_REGISTRY_RESOURCE_TYPE,
|
791
840
|
description="""
|
792
|
-
Allows Stack Components to access a
|
841
|
+
Allows Stack Components to access a Google Artifact Registry as a standard
|
793
842
|
Docker registry resource. When used by Stack Components, they are provided a
|
794
843
|
pre-authenticated Python Docker client instance.
|
795
844
|
|
796
|
-
The configured credentials must have at least the following [GCP permissions](https://cloud.google.com/iam/docs/
|
845
|
+
The configured credentials must have at least the following [GCP permissions](https://cloud.google.com/iam/docs/understanding-roles#artifact-registry-roles):
|
846
|
+
|
847
|
+
- `artifactregistry.repositories.createOnPush`
|
848
|
+
- `artifactregistry.repositories.downloadArtifacts`
|
849
|
+
- `artifactregistry.repositories.get`
|
850
|
+
- `artifactregistry.repositories.list`
|
851
|
+
- `artifactregistry.repositories.readViaVirtualRepository`
|
852
|
+
- `artifactregistry.repositories.uploadArtifacts`
|
853
|
+
- `artifactregistry.locations.list`
|
854
|
+
|
855
|
+
The Artifact Registry Create-on-Push Writer role includes all of the above
|
856
|
+
permissions.
|
857
|
+
|
858
|
+
This resource type also includes legacy GCR container registry support.
|
859
|
+
|
860
|
+
**Important Notice: Google Container Registry** [**is being replaced by Artifact Registry**](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr).
|
861
|
+
Please start using Artifact Registry for your containers. As per Google's
|
862
|
+
documentation, *"after May 15, 2024, Artifact Registry will host images for the
|
863
|
+
gcr.io domain in Google Cloud projects without previous Container Registry
|
864
|
+
usage. After March 18, 2025, Container Registry will be shut down."*.
|
865
|
+
|
866
|
+
Support for legacy GCR registries is still included in the GCP service
|
867
|
+
connector. Users that already have GCP service connectors configured to access
|
868
|
+
GCR registries may continue to use them without taking any action. However, it
|
869
|
+
is recommended to transition to Google Artifact Registries as soon as possible
|
870
|
+
by following [the GCP guide on this subject](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)
|
871
|
+
and making the following updates to ZenML GCP Service Connectors that are used
|
872
|
+
to access GCR resources:
|
873
|
+
|
874
|
+
* add the IAM permissions documented here to the GCP Service Connector
|
875
|
+
credentials to enable them to access the Artifact Registries.
|
876
|
+
* users may keep the gcr.io GCR URLs already configured in the GCP Service
|
877
|
+
Connectors as well as those used in linked Container Registry stack components
|
878
|
+
given that these domains are redirected by Google to GAR as covered in the GCR
|
879
|
+
transition guide. Alternatively, users may update the GCP Service Connector
|
880
|
+
configuration and/or the Container Registry stack components to use the
|
881
|
+
replacement Artifact Registry URLs.
|
882
|
+
|
883
|
+
When used with GCR registries, the configured credentials must have at least the
|
884
|
+
following [GCP permissions](https://cloud.google.com/iam/docs/understanding-roles#cloud-storage-roles):
|
797
885
|
|
798
886
|
- `storage.buckets.get`
|
799
887
|
- `storage.multipartUploads.abort`
|
@@ -807,17 +895,21 @@ The configured credentials must have at least the following [GCP permissions](ht
|
|
807
895
|
The Storage Legacy Bucket Writer role includes all of the above permissions
|
808
896
|
while at the same time restricting access to only the GCR buckets.
|
809
897
|
|
810
|
-
|
811
|
-
|
812
|
-
|
898
|
+
If set, the resource name must identify a GAR or GCR registry using one of the
|
899
|
+
following formats:
|
900
|
+
|
901
|
+
- Google Artifact Registry repository URI: `[https://]<region>-docker.pkg.dev/<project-id>/<registry-id>[/<repository-name>]`
|
902
|
+
- Google Artifact Registry name: `projects/<project-id>/locations/<location>/repositories/<repository-id>`
|
903
|
+
- (legacy) GCR repository URI: `[https://][us.|eu.|asia.]gcr.io/<project-id>[/<repository-name>]`
|
813
904
|
|
814
|
-
|
905
|
+
The connector can only be used to access GAR and GCR registries in the GCP
|
906
|
+
project that it is configured to use.
|
815
907
|
""",
|
816
908
|
auth_methods=GCPAuthenticationMethods.values(),
|
817
|
-
#
|
818
|
-
#
|
819
|
-
#
|
820
|
-
supports_instances=
|
909
|
+
# The connector provides access to the entire GCR container registry
|
910
|
+
# for the configured GCP project as well as any number of artifact
|
911
|
+
# registry repositories.
|
912
|
+
supports_instances=True,
|
821
913
|
logo_url="https://public-flavor-logos.s3.eu-central-1.amazonaws.com/container_registry/docker.png",
|
822
914
|
emoji=":whale:",
|
823
915
|
),
|
@@ -1006,6 +1098,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1006
1098
|
# service account authentication)
|
1007
1099
|
|
1008
1100
|
assert isinstance(cfg, GCPServiceAccountConfig)
|
1101
|
+
|
1009
1102
|
credentials = (
|
1010
1103
|
gcp_service_account.Credentials.from_service_account_info(
|
1011
1104
|
json.loads(
|
@@ -1093,60 +1186,101 @@ class GCPServiceConnector(ServiceConnector):
|
|
1093
1186
|
|
1094
1187
|
return bucket_name
|
1095
1188
|
|
1096
|
-
def
|
1189
|
+
def _parse_gar_resource_id(
|
1097
1190
|
self,
|
1098
1191
|
resource_id: str,
|
1099
|
-
) -> str:
|
1100
|
-
"""Validate and convert
|
1192
|
+
) -> Tuple[str, Optional[str]]:
|
1193
|
+
"""Validate and convert a GAR resource ID to a Google Artifact Registry ID and name.
|
1101
1194
|
|
1102
1195
|
Args:
|
1103
1196
|
resource_id: The resource ID to convert.
|
1104
1197
|
|
1105
1198
|
Returns:
|
1106
|
-
The
|
1199
|
+
The Google Artifact Registry ID and name. The name is omitted if the
|
1200
|
+
resource ID is a GCR repository URI.
|
1107
1201
|
|
1108
1202
|
Raises:
|
1109
|
-
ValueError: If the provided resource ID is not a valid
|
1110
|
-
repository URI.
|
1203
|
+
ValueError: If the provided resource ID is not a valid GAR
|
1204
|
+
or GCR repository URI.
|
1111
1205
|
"""
|
1112
1206
|
# The resource ID could mean different things:
|
1113
1207
|
#
|
1114
|
-
# -
|
1208
|
+
# - a GAR repository URI
|
1209
|
+
# - a GAR repository name
|
1210
|
+
# - a GCR repository URI (backwards-compatibility)
|
1115
1211
|
#
|
1116
1212
|
# We need to extract the project ID and registry ID from
|
1117
1213
|
# the provided resource ID
|
1118
|
-
config_project_id = self.config.
|
1214
|
+
config_project_id = self.config.gcp_project_id
|
1119
1215
|
project_id: Optional[str] = None
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1216
|
+
canonical_url: str
|
1217
|
+
registry_name: Optional[str] = None
|
1218
|
+
|
1219
|
+
# A Google Artifact Registry URI uses the <location>-docker-pkg.dev
|
1220
|
+
# domain format with the project ID as the first part of the URL path
|
1221
|
+
# and the registry name as the second part of the URL path
|
1222
|
+
if match := re.match(
|
1223
|
+
r"^(https://)?(([a-z0-9-]+)-docker\.pkg\.dev/([a-z0-9-]+)/([a-z0-9-.]+))(/.+)*$",
|
1125
1224
|
resource_id,
|
1126
1225
|
):
|
1127
|
-
# The resource ID is a
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1226
|
+
# The resource ID is a Google Artifact Registry URI
|
1227
|
+
project_id = match[4]
|
1228
|
+
location = match[3]
|
1229
|
+
repository = match[5]
|
1230
|
+
|
1231
|
+
# Return the GAR URL without the image name and without the protocol
|
1232
|
+
canonical_url = match[2]
|
1233
|
+
registry_name = f"projects/{project_id}/locations/{location}/repositories/{repository}"
|
1234
|
+
|
1235
|
+
# Alternatively, the Google Artifact Registry name uses the
|
1236
|
+
# projects/<project-id>/locations/<location>/repositories/<repository-id>
|
1237
|
+
# format
|
1238
|
+
elif match := re.match(
|
1239
|
+
r"^projects/([a-z0-9-]+)/locations/([a-z0-9-]+)/repositories/([a-z0-9-.]+)$",
|
1240
|
+
resource_id,
|
1241
|
+
):
|
1242
|
+
# The resource ID is a Google Artifact Registry name
|
1243
|
+
project_id = match[1]
|
1244
|
+
location = match[2]
|
1245
|
+
repository = match[3]
|
1246
|
+
|
1247
|
+
# Return the GAR URL
|
1248
|
+
canonical_url = (
|
1249
|
+
f"{location}-docker.pkg.dev/{project_id}/{repository}"
|
1250
|
+
)
|
1251
|
+
registry_name = resource_id
|
1252
|
+
|
1253
|
+
# A legacy GCR repository URI uses one of several hostnames (gcr.io,
|
1254
|
+
# us.gcr.io, eu.gcr.io, asia.gcr.io) and the project ID is the
|
1255
|
+
# first part of the URL path
|
1256
|
+
elif match := re.match(
|
1257
|
+
r"^(https://)?(((us|eu|asia)\.)?gcr\.io/[a-z0-9-]+)(/.+)*$",
|
1258
|
+
resource_id,
|
1259
|
+
):
|
1260
|
+
# The resource ID is a legacy GCR repository URI.
|
1261
|
+
# Return the GAR URL without the image name and without the protocol
|
1262
|
+
canonical_url = match[2]
|
1263
|
+
|
1132
1264
|
else:
|
1133
1265
|
raise ValueError(
|
1134
|
-
f"Invalid resource ID for a
|
1135
|
-
f"Supported formats are:\n"
|
1266
|
+
f"Invalid resource ID for a Google Artifact Registry: "
|
1267
|
+
f"{resource_id}. Supported formats are:\n"
|
1268
|
+
f"Google Artifact Registry URI: [https://]<region>-docker.pkg.dev/<project-id>/<registry-id>[/<repository-name>]\n"
|
1269
|
+
f"Google Artifact Registry name: projects/<project-id>/locations/<location>/repositories/<repository-id>\n"
|
1136
1270
|
f"GCR repository URI: [https://][us.|eu.|asia.]gcr.io/<project-id>[/<repository-name>]"
|
1137
1271
|
)
|
1138
1272
|
|
1139
1273
|
# If the connector is configured with a project and the resource ID
|
1140
|
-
# is
|
1274
|
+
# is a GAR repository URI that specifies a different project,
|
1141
1275
|
# we raise an error
|
1142
1276
|
if project_id and project_id != config_project_id:
|
1143
1277
|
raise ValueError(
|
1144
|
-
f"The GCP project for the {resource_id}
|
1145
|
-
f"'{project_id}' does not match the project
|
1146
|
-
f"the connector: '{config_project_id}'."
|
1278
|
+
f"The GCP project for the {resource_id} Google Artifact "
|
1279
|
+
f"Registry '{project_id}' does not match the project "
|
1280
|
+
f"configured in the connector: '{config_project_id}'."
|
1147
1281
|
)
|
1148
1282
|
|
1149
|
-
return
|
1283
|
+
return canonical_url, registry_name
|
1150
1284
|
|
1151
1285
|
def _parse_gke_resource_id(self, resource_id: str) -> str:
|
1152
1286
|
"""Validate and convert an GKE resource ID to a GKE cluster name.
|
@@ -1195,7 +1329,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1195
1329
|
cluster_name = self._parse_gke_resource_id(resource_id)
|
1196
1330
|
return cluster_name
|
1197
1331
|
elif resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1198
|
-
registry_id = self.
|
1332
|
+
registry_id, _ = self._parse_gar_resource_id(
|
1199
1333
|
resource_id,
|
1200
1334
|
)
|
1201
1335
|
return registry_id
|
@@ -1219,9 +1353,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1219
1353
|
authorized.
|
1220
1354
|
"""
|
1221
1355
|
if resource_type == GCP_RESOURCE_TYPE:
|
1222
|
-
return self.config.
|
1223
|
-
elif resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1224
|
-
return f"gcr.io/{self.config.project_id}"
|
1356
|
+
return self.config.gcp_project_id
|
1225
1357
|
|
1226
1358
|
raise RuntimeError(
|
1227
1359
|
f"Default resource ID not supported for '{resource_type}' resource "
|
@@ -1278,7 +1410,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1278
1410
|
|
1279
1411
|
# Create an GCS client for the bucket
|
1280
1412
|
client = storage.Client(
|
1281
|
-
project=self.config.
|
1413
|
+
project=self.config.gcp_project_id, credentials=credentials
|
1282
1414
|
)
|
1283
1415
|
return client
|
1284
1416
|
|
@@ -1384,7 +1516,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1384
1516
|
"config",
|
1385
1517
|
"set",
|
1386
1518
|
"project",
|
1387
|
-
self.config.
|
1519
|
+
self.config.gcp_project_id,
|
1388
1520
|
],
|
1389
1521
|
check=True,
|
1390
1522
|
stderr=subprocess.STDOUT,
|
@@ -1488,7 +1620,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1488
1620
|
)
|
1489
1621
|
|
1490
1622
|
if auth_method == GCPAuthenticationMethods.IMPLICIT:
|
1491
|
-
auth_config =
|
1623
|
+
auth_config = GCPBaseProjectIDConfig(
|
1492
1624
|
project_id=project_id,
|
1493
1625
|
)
|
1494
1626
|
elif auth_method == GCPAuthenticationMethods.OAUTH2_TOKEN:
|
@@ -1697,7 +1829,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1697
1829
|
|
1698
1830
|
if resource_type == GCS_RESOURCE_TYPE:
|
1699
1831
|
gcs_client = storage.Client(
|
1700
|
-
project=self.config.
|
1832
|
+
project=self.config.gcp_project_id, credentials=credentials
|
1701
1833
|
)
|
1702
1834
|
if not resource_id:
|
1703
1835
|
# List all GCS buckets
|
@@ -1722,11 +1854,87 @@ class GCPServiceConnector(ServiceConnector):
|
|
1722
1854
|
raise AuthorizationException(msg) from e
|
1723
1855
|
|
1724
1856
|
if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1725
|
-
|
1857
|
+
# Get a GAR client
|
1858
|
+
gar_client = artifactregistry_v1.ArtifactRegistryClient(
|
1859
|
+
credentials=credentials
|
1860
|
+
)
|
1726
1861
|
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1862
|
+
if resource_id:
|
1863
|
+
registry_id, registry_name = self._parse_gar_resource_id(
|
1864
|
+
resource_id
|
1865
|
+
)
|
1866
|
+
|
1867
|
+
if registry_name is None:
|
1868
|
+
# This is a legacy GCR repository URI. We can't verify
|
1869
|
+
# the repository access without attempting to connect to it
|
1870
|
+
# via Docker/OCI, so just return the resource ID.
|
1871
|
+
return [registry_id]
|
1872
|
+
|
1873
|
+
# Check if the specified GAR registry exists
|
1874
|
+
try:
|
1875
|
+
repository = gar_client.get_repository(
|
1876
|
+
name=registry_name,
|
1877
|
+
)
|
1878
|
+
if repository.format_.name != "DOCKER":
|
1879
|
+
raise AuthorizationException(
|
1880
|
+
f"Google Artifact Registry '{resource_id}' is not a "
|
1881
|
+
"Docker registry."
|
1882
|
+
)
|
1883
|
+
return [registry_id]
|
1884
|
+
except google.api_core.exceptions.GoogleAPIError as e:
|
1885
|
+
msg = f"Failed to fetch Google Artifact Registry '{registry_id}': {e}"
|
1886
|
+
logger.error(msg)
|
1887
|
+
raise AuthorizationException(msg) from e
|
1888
|
+
|
1889
|
+
# For backwards compatibility, we initialize the list of resource
|
1890
|
+
# IDs with all GCR supported registries for the configured GCP
|
1891
|
+
# project
|
1892
|
+
resource_ids: List[str] = [
|
1893
|
+
f"{location}gcr.io/{self.config.gcp_project_id}"
|
1894
|
+
for location in ["", "us.", "eu.", "asia."]
|
1895
|
+
]
|
1896
|
+
|
1897
|
+
# List all Google Artifact Registries
|
1898
|
+
try:
|
1899
|
+
# First, we need to fetch all the Artifact Registry supported
|
1900
|
+
# locations
|
1901
|
+
locations = gar_client.list_locations(
|
1902
|
+
request=locations_pb2.ListLocationsRequest(
|
1903
|
+
name=f"projects/{self.config.gcp_project_id}"
|
1904
|
+
)
|
1905
|
+
)
|
1906
|
+
location_names = [
|
1907
|
+
locations.locations[i].location_id
|
1908
|
+
for i in range(len(locations.locations))
|
1909
|
+
]
|
1910
|
+
|
1911
|
+
# Then, we need to fetch all the repositories in each location
|
1912
|
+
repository_names: List[str] = []
|
1913
|
+
for location in location_names:
|
1914
|
+
repositories = gar_client.list_repositories(
|
1915
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/{location}"
|
1916
|
+
)
|
1917
|
+
repository_names.extend(
|
1918
|
+
[
|
1919
|
+
repository.name
|
1920
|
+
for repository in repositories
|
1921
|
+
if repository.format_.name == "DOCKER"
|
1922
|
+
]
|
1923
|
+
)
|
1924
|
+
|
1925
|
+
for repository_name in repository_names:
|
1926
|
+
# Convert the repository name to a canonical GAR URL
|
1927
|
+
resource_ids.append(
|
1928
|
+
self._parse_gar_resource_id(repository_name)[0]
|
1929
|
+
)
|
1930
|
+
|
1931
|
+
except google.api_core.exceptions.GoogleAPIError as e:
|
1932
|
+
msg = f"Failed to list Google Artifact Registries: {e}"
|
1933
|
+
logger.error(msg)
|
1934
|
+
# TODO: enable when GCR is no longer supported:
|
1935
|
+
# raise AuthorizationException(msg) from e
|
1936
|
+
|
1937
|
+
return resource_ids
|
1730
1938
|
|
1731
1939
|
if resource_type == KUBERNETES_CLUSTER_RESOURCE_TYPE:
|
1732
1940
|
gke_client = container_v1.ClusterManagerClient(
|
@@ -1736,7 +1944,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1736
1944
|
# List all GKE clusters
|
1737
1945
|
try:
|
1738
1946
|
clusters = gke_client.list_clusters(
|
1739
|
-
parent=f"projects/{self.config.
|
1947
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/-"
|
1740
1948
|
)
|
1741
1949
|
cluster_names = [cluster.name for cluster in clusters.clusters]
|
1742
1950
|
except google.api_core.exceptions.GoogleAPIError as e:
|
@@ -1810,7 +2018,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1810
2018
|
# object
|
1811
2019
|
auth_method: str = GCPAuthenticationMethods.OAUTH2_TOKEN
|
1812
2020
|
config: GCPBaseConfig = GCPOAuth2TokenConfig(
|
1813
|
-
project_id=self.config.
|
2021
|
+
project_id=self.config.gcp_project_id,
|
1814
2022
|
token=credentials.token,
|
1815
2023
|
service_account_email=credentials.signer_email
|
1816
2024
|
if hasattr(credentials, "signer_email")
|
@@ -1855,7 +2063,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1855
2063
|
if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1856
2064
|
assert resource_id is not None
|
1857
2065
|
|
1858
|
-
registry_id = self.
|
2066
|
+
registry_id, _ = self._parse_gar_resource_id(resource_id)
|
1859
2067
|
|
1860
2068
|
# Create a client-side Docker connector instance with the temporary
|
1861
2069
|
# Docker credentials
|
@@ -1884,7 +2092,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1884
2092
|
# List all GKE clusters
|
1885
2093
|
try:
|
1886
2094
|
clusters = gke_client.list_clusters(
|
1887
|
-
parent=f"projects/{self.config.
|
2095
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/-"
|
1888
2096
|
)
|
1889
2097
|
cluster_map = {
|
1890
2098
|
cluster.name: cluster for cluster in clusters.clusters
|
@@ -1928,7 +2136,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1928
2136
|
auth_method=KubernetesAuthenticationMethods.TOKEN,
|
1929
2137
|
resource_type=resource_type,
|
1930
2138
|
config=KubernetesTokenConfig(
|
1931
|
-
cluster_name=f"gke_{self.config.
|
2139
|
+
cluster_name=f"gke_{self.config.gcp_project_id}_{cluster_name}",
|
1932
2140
|
certificate_authority=cluster_ca_cert,
|
1933
2141
|
server=f"https://{cluster_server}",
|
1934
2142
|
token=bearer_token,
|
@@ -60,6 +60,7 @@ class Integration(metaclass=IntegrationMeta):
|
|
60
60
|
|
61
61
|
REQUIREMENTS: List[str] = []
|
62
62
|
APT_PACKAGES: List[str] = []
|
63
|
+
REQUIREMENTS_IGNORED_ON_UNINSTALL: List[str] = []
|
63
64
|
|
64
65
|
@classmethod
|
65
66
|
def check_installation(cls) -> bool:
|
@@ -143,6 +144,29 @@ class Integration(metaclass=IntegrationMeta):
|
|
143
144
|
"""
|
144
145
|
return cls.REQUIREMENTS
|
145
146
|
|
147
|
+
@classmethod
|
148
|
+
def get_uninstall_requirements(
|
149
|
+
cls, target_os: Optional[str] = None
|
150
|
+
) -> List[str]:
|
151
|
+
"""Method to get the uninstall requirements for the integration.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
target_os: The target operating system to get the requirements for.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
A list of requirements.
|
158
|
+
"""
|
159
|
+
ret = []
|
160
|
+
for each in cls.get_requirements(target_os=target_os):
|
161
|
+
is_ignored = False
|
162
|
+
for ignored in cls.REQUIREMENTS_IGNORED_ON_UNINSTALL:
|
163
|
+
if each.startswith(ignored):
|
164
|
+
is_ignored = True
|
165
|
+
break
|
166
|
+
if not is_ignored:
|
167
|
+
ret.append(each)
|
168
|
+
return ret
|
169
|
+
|
146
170
|
@classmethod
|
147
171
|
def activate(cls) -> None:
|
148
172
|
"""Abstract method to activate the integration."""
|
@@ -31,6 +31,9 @@ class KubeflowIntegration(Integration):
|
|
31
31
|
|
32
32
|
NAME = KUBEFLOW
|
33
33
|
REQUIREMENTS = ["kfp>=2.6.0", "kfp-kubernetes>=1.1.0"] # Only 1.x version that supports pyyaml 6
|
34
|
+
REQUIREMENTS_IGNORED_ON_UNINSTALL = [
|
35
|
+
"kfp", # it is used by GCP as well
|
36
|
+
]
|
34
37
|
|
35
38
|
@classmethod
|
36
39
|
def flavors(cls) -> List[Type[Flavor]]:
|
@@ -149,7 +149,7 @@ class KubeflowOrchestratorConfig(
|
|
149
149
|
|
150
150
|
kubeflow_hostname: Optional[str] = None
|
151
151
|
kubeflow_namespace: str = "kubeflow"
|
152
|
-
kubernetes_context: Optional[str] # TODO: Potential setting
|
152
|
+
kubernetes_context: Optional[str] = None # TODO: Potential setting
|
153
153
|
|
154
154
|
@model_validator(mode="before")
|
155
155
|
@classmethod
|
@@ -31,7 +31,9 @@ class KubernetesIntegration(Integration):
|
|
31
31
|
|
32
32
|
NAME = KUBERNETES
|
33
33
|
REQUIREMENTS = ["kubernetes>=21.7,<26"]
|
34
|
-
|
34
|
+
REQUIREMENTS_IGNORED_ON_UNINSTALL = [
|
35
|
+
"kfp", # it is used by many others
|
36
|
+
]
|
35
37
|
@classmethod
|
36
38
|
def flavors(cls) -> List[Type[Flavor]]:
|
37
39
|
"""Declare the stack component flavors for the Kubernetes integration.
|
@@ -225,8 +225,11 @@ def wait_pod(
|
|
225
225
|
response = core_api.read_namespaced_pod_log(
|
226
226
|
name=pod_name,
|
227
227
|
namespace=namespace,
|
228
|
+
_preload_content=False,
|
228
229
|
)
|
229
|
-
|
230
|
+
raw_data = response.data
|
231
|
+
decoded_log = raw_data.decode("utf-8", errors="replace")
|
232
|
+
logs = decoded_log.splitlines()
|
230
233
|
if len(logs) > logged_lines:
|
231
234
|
for line in logs[logged_lines:]:
|
232
235
|
logger.info(line)
|
@@ -45,9 +45,11 @@ class MlflowIntegration(Integration):
|
|
45
45
|
# This downgrades pydantic to v1 even though mlflow does not have
|
46
46
|
# any issues with v2. This is why we have to pin it here so a downgrade
|
47
47
|
# will not happen.
|
48
|
-
"pydantic>=2.7.0,<2.8.0"
|
48
|
+
"pydantic>=2.7.0,<2.8.0",
|
49
49
|
]
|
50
50
|
|
51
|
+
REQUIREMENTS_IGNORED_ON_UNINSTALL = ["python-rapidjson", "pydantic"]
|
52
|
+
|
51
53
|
@classmethod
|
52
54
|
def activate(cls) -> None:
|
53
55
|
"""Activate the MLflow integration."""
|