zenml-nightly 0.58.2.dev20240623__py3-none-any.whl → 0.61.0.dev20240712__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +30 -9
- RELEASE_NOTES.md +240 -0
- zenml/VERSION +1 -1
- zenml/actions/base_action.py +177 -174
- zenml/actions/pipeline_run/pipeline_run_action.py +28 -23
- zenml/analytics/enums.py +3 -0
- zenml/artifact_stores/base_artifact_store.py +7 -1
- zenml/artifacts/utils.py +13 -10
- 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/service_connectors.py +1 -0
- zenml/cli/stack.py +946 -39
- zenml/cli/stack_components.py +7 -0
- zenml/cli/text_utils.py +35 -1
- zenml/cli/utils.py +127 -10
- zenml/client.py +257 -72
- zenml/config/compiler.py +10 -9
- zenml/config/docker_settings.py +33 -14
- zenml/constants.py +11 -2
- zenml/container_registries/base_container_registry.py +1 -0
- zenml/enums.py +7 -0
- zenml/event_hub/base_event_hub.py +5 -5
- zenml/event_hub/event_hub.py +20 -14
- zenml/event_sources/base_event.py +0 -11
- zenml/event_sources/base_event_source.py +7 -0
- zenml/event_sources/webhooks/base_webhook_event_source.py +1 -4
- zenml/exceptions.py +4 -0
- zenml/hooks/hook_validators.py +2 -3
- zenml/integrations/aws/__init__.py +1 -0
- zenml/integrations/azure/__init__.py +1 -0
- zenml/integrations/bitbucket/plugins/event_sources/bitbucket_webhook_event_source.py +3 -3
- 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 +320 -64
- 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 +4 -2
- 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 +93 -9
- 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/logging/step_logging.py +54 -51
- zenml/models/__init__.py +28 -0
- zenml/models/v2/core/action.py +276 -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/core/trigger.py +182 -141
- zenml/models/v2/misc/full_stack.py +97 -0
- zenml/models/v2/misc/stack_deployment.py +86 -0
- zenml/new/pipelines/pipeline.py +14 -4
- zenml/new/pipelines/pipeline_decorator.py +1 -2
- zenml/new/pipelines/run_utils.py +1 -12
- zenml/new/steps/step_decorator.py +2 -3
- zenml/orchestrators/input_utils.py +3 -6
- zenml/pipelines/base_pipeline.py +0 -2
- zenml/pipelines/pipeline_decorator.py +1 -2
- zenml/stack/stack.py +3 -6
- zenml/stack/stack_component.py +4 -0
- zenml/stack_deployments/__init__.py +14 -0
- zenml/stack_deployments/aws_stack_deployment.py +254 -0
- zenml/stack_deployments/gcp_stack_deployment.py +260 -0
- zenml/stack_deployments/stack_deployment.py +208 -0
- zenml/stack_deployments/utils.py +44 -0
- zenml/steps/base_step.py +1 -2
- zenml/steps/step_decorator.py +1 -2
- zenml/types.py +10 -1
- zenml/utils/function_utils.py +1 -1
- zenml/utils/pagination_utils.py +7 -5
- zenml/utils/pipeline_docker_image_builder.py +117 -73
- 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/models.py +1 -0
- zenml/zen_server/rbac/utils.py +22 -1
- zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
- zenml/zen_server/routers/actions_endpoints.py +324 -0
- zenml/zen_server/routers/stack_deployment_endpoints.py +158 -0
- zenml/zen_server/routers/triggers_endpoints.py +30 -158
- zenml/zen_server/routers/workspaces_endpoints.py +64 -0
- zenml/zen_server/zen_server_api.py +4 -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/migrations/versions/25155145c545_separate_actions_and_triggers.py +228 -0
- zenml/zen_stores/rest_zen_store.py +248 -8
- zenml/zen_stores/schemas/__init__.py +2 -0
- zenml/zen_stores/schemas/action_schemas.py +192 -0
- zenml/zen_stores/schemas/stack_schemas.py +10 -0
- zenml/zen_stores/schemas/step_run_schemas.py +27 -11
- zenml/zen_stores/schemas/trigger_schemas.py +43 -50
- zenml/zen_stores/schemas/user_schemas.py +10 -2
- zenml/zen_stores/schemas/workspace_schemas.py +5 -0
- zenml/zen_stores/sql_zen_store.py +540 -36
- zenml/zen_stores/zen_store_interface.py +165 -0
- {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/METADATA +33 -11
- {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/RECORD +213 -193
- 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.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/entry_points.txt +0 -0
@@ -20,6 +20,7 @@ services:
|
|
20
20
|
|
21
21
|
"""
|
22
22
|
|
23
|
+
import base64
|
23
24
|
import datetime
|
24
25
|
import json
|
25
26
|
import os
|
@@ -43,7 +44,8 @@ from google.auth._default import (
|
|
43
44
|
_get_external_account_credentials,
|
44
45
|
)
|
45
46
|
from google.auth.transport.requests import Request
|
46
|
-
from google.cloud import container_v1, storage
|
47
|
+
from google.cloud import artifactregistry_v1, container_v1, storage
|
48
|
+
from google.cloud.location import locations_pb2
|
47
49
|
from google.oauth2 import credentials as gcp_credentials
|
48
50
|
from google.oauth2 import service_account as gcp_service_account
|
49
51
|
from pydantic import Field, field_validator, model_validator
|
@@ -88,7 +90,7 @@ class GCPUserAccountCredentials(AuthenticationConfig):
|
|
88
90
|
"""GCP user account credentials."""
|
89
91
|
|
90
92
|
user_account_json: PlainSerializedSecretStr = Field(
|
91
|
-
title="GCP User Account Credentials JSON",
|
93
|
+
title="GCP User Account Credentials JSON optionally base64 encoded.",
|
92
94
|
)
|
93
95
|
|
94
96
|
generate_temporary_tokens: bool = Field(
|
@@ -112,9 +114,24 @@ class GCPUserAccountCredentials(AuthenticationConfig):
|
|
112
114
|
|
113
115
|
Returns:
|
114
116
|
The validated configuration values.
|
117
|
+
|
118
|
+
Raises:
|
119
|
+
ValueError: If the user account credentials JSON is invalid.
|
115
120
|
"""
|
116
|
-
|
121
|
+
user_account_json = data.get("user_account_json")
|
122
|
+
if isinstance(user_account_json, dict):
|
117
123
|
data["user_account_json"] = json.dumps(data["user_account_json"])
|
124
|
+
elif isinstance(user_account_json, str):
|
125
|
+
# Check if the user account JSON is base64 encoded and decode it
|
126
|
+
if re.match(r"^[A-Za-z0-9+/=]+$", user_account_json):
|
127
|
+
try:
|
128
|
+
data["user_account_json"] = base64.b64decode(
|
129
|
+
user_account_json
|
130
|
+
).decode("utf-8")
|
131
|
+
except Exception as e:
|
132
|
+
raise ValueError(
|
133
|
+
f"Failed to decode base64 encoded user account JSON: {e}"
|
134
|
+
)
|
118
135
|
return data
|
119
136
|
|
120
137
|
@field_validator("user_account_json")
|
@@ -169,7 +186,7 @@ class GCPServiceAccountCredentials(AuthenticationConfig):
|
|
169
186
|
"""GCP service account credentials."""
|
170
187
|
|
171
188
|
service_account_json: PlainSerializedSecretStr = Field(
|
172
|
-
title="GCP Service Account Key JSON",
|
189
|
+
title="GCP Service Account Key JSON optionally base64 encoded.",
|
173
190
|
)
|
174
191
|
|
175
192
|
generate_temporary_tokens: bool = Field(
|
@@ -193,11 +210,27 @@ class GCPServiceAccountCredentials(AuthenticationConfig):
|
|
193
210
|
|
194
211
|
Returns:
|
195
212
|
The validated configuration values.
|
213
|
+
|
214
|
+
Raises:
|
215
|
+
ValueError: If the service account credentials JSON is invalid.
|
196
216
|
"""
|
197
|
-
|
217
|
+
service_account_json = data.get("service_account_json")
|
218
|
+
if isinstance(service_account_json, dict):
|
198
219
|
data["service_account_json"] = json.dumps(
|
199
220
|
data["service_account_json"]
|
200
221
|
)
|
222
|
+
elif isinstance(service_account_json, str):
|
223
|
+
# Check if the service account JSON is base64 encoded and decode it
|
224
|
+
if re.match(r"^[A-Za-z0-9+/=]+$", service_account_json):
|
225
|
+
try:
|
226
|
+
data["service_account_json"] = base64.b64decode(
|
227
|
+
service_account_json
|
228
|
+
).decode("utf-8")
|
229
|
+
except Exception as e:
|
230
|
+
raise ValueError(
|
231
|
+
f"Failed to decode base64 encoded service account JSON: {e}"
|
232
|
+
)
|
233
|
+
|
201
234
|
return data
|
202
235
|
|
203
236
|
@field_validator("service_account_json")
|
@@ -260,7 +293,7 @@ class GCPExternalAccountCredentials(AuthenticationConfig):
|
|
260
293
|
"""GCP external account credentials."""
|
261
294
|
|
262
295
|
external_account_json: PlainSerializedSecretStr = Field(
|
263
|
-
title="GCP External Account JSON",
|
296
|
+
title="GCP External Account JSON optionally base64 encoded.",
|
264
297
|
)
|
265
298
|
|
266
299
|
generate_temporary_tokens: bool = Field(
|
@@ -284,11 +317,27 @@ class GCPExternalAccountCredentials(AuthenticationConfig):
|
|
284
317
|
|
285
318
|
Returns:
|
286
319
|
The validated configuration values.
|
320
|
+
|
321
|
+
Raises:
|
322
|
+
ValueError: If the external account credentials JSON is invalid.
|
287
323
|
"""
|
288
|
-
|
324
|
+
external_account_json = data.get("external_account_json")
|
325
|
+
if isinstance(external_account_json, dict):
|
289
326
|
data["external_account_json"] = json.dumps(
|
290
327
|
data["external_account_json"]
|
291
328
|
)
|
329
|
+
elif isinstance(external_account_json, str):
|
330
|
+
# Check if the external account JSON is base64 encoded and decode it
|
331
|
+
if re.match(r"^[A-Za-z0-9+/=]+$", external_account_json):
|
332
|
+
try:
|
333
|
+
data["external_account_json"] = base64.b64decode(
|
334
|
+
external_account_json
|
335
|
+
).decode("utf-8")
|
336
|
+
except Exception as e:
|
337
|
+
raise ValueError(
|
338
|
+
f"Failed to decode base64 encoded external account JSON: {e}"
|
339
|
+
)
|
340
|
+
|
292
341
|
return data
|
293
342
|
|
294
343
|
@field_validator("external_account_json")
|
@@ -351,24 +400,72 @@ class GCPOAuth2Token(AuthenticationConfig):
|
|
351
400
|
class GCPBaseConfig(AuthenticationConfig):
|
352
401
|
"""GCP base configuration."""
|
353
402
|
|
403
|
+
@property
|
404
|
+
def gcp_project_id(self) -> str:
|
405
|
+
"""Get the GCP project ID.
|
406
|
+
|
407
|
+
This method must be implemented by subclasses to ensure that the GCP
|
408
|
+
project ID is always available.
|
409
|
+
|
410
|
+
Raises:
|
411
|
+
NotImplementedError: If the method is not implemented.
|
412
|
+
"""
|
413
|
+
raise NotImplementedError
|
414
|
+
|
415
|
+
|
416
|
+
class GCPBaseProjectIDConfig(GCPBaseConfig):
|
417
|
+
"""GCP base configuration with included project ID."""
|
418
|
+
|
354
419
|
project_id: str = Field(
|
355
420
|
title="GCP Project ID where the target resource is located.",
|
356
421
|
)
|
357
422
|
|
423
|
+
@property
|
424
|
+
def gcp_project_id(self) -> str:
|
425
|
+
"""Get the GCP project ID.
|
426
|
+
|
427
|
+
Returns:
|
428
|
+
The GCP project ID.
|
429
|
+
"""
|
430
|
+
return self.project_id
|
431
|
+
|
358
432
|
|
359
|
-
class GCPUserAccountConfig(
|
433
|
+
class GCPUserAccountConfig(GCPBaseProjectIDConfig, GCPUserAccountCredentials):
|
360
434
|
"""GCP user account configuration."""
|
361
435
|
|
362
436
|
|
363
437
|
class GCPServiceAccountConfig(GCPBaseConfig, GCPServiceAccountCredentials):
|
364
438
|
"""GCP service account configuration."""
|
365
439
|
|
440
|
+
_project_id: Optional[str] = None
|
441
|
+
|
442
|
+
@property
|
443
|
+
def gcp_project_id(self) -> str:
|
444
|
+
"""Get the GCP project ID.
|
445
|
+
|
446
|
+
When a service account JSON is provided, the project ID can be extracted
|
447
|
+
from it instead of being provided explicitly.
|
366
448
|
|
367
|
-
|
449
|
+
Returns:
|
450
|
+
The GCP project ID.
|
451
|
+
"""
|
452
|
+
if self._project_id is None:
|
453
|
+
self._project_id = json.loads(
|
454
|
+
self.service_account_json.get_secret_value()
|
455
|
+
)["project_id"]
|
456
|
+
# Guaranteed by the field validator
|
457
|
+
assert self._project_id is not None
|
458
|
+
|
459
|
+
return self._project_id
|
460
|
+
|
461
|
+
|
462
|
+
class GCPExternalAccountConfig(
|
463
|
+
GCPBaseProjectIDConfig, GCPExternalAccountCredentials
|
464
|
+
):
|
368
465
|
"""GCP external account configuration."""
|
369
466
|
|
370
467
|
|
371
|
-
class GCPOAuth2TokenConfig(
|
468
|
+
class GCPOAuth2TokenConfig(GCPBaseProjectIDConfig, GCPOAuth2Token):
|
372
469
|
"""GCP OAuth 2.0 configuration."""
|
373
470
|
|
374
471
|
service_account_email: Optional[str] = Field(
|
@@ -540,7 +637,7 @@ resources in the specified project. When used remotely in a GCP workload, the
|
|
540
637
|
configured project has to be the same as the project of the attached service
|
541
638
|
account.
|
542
639
|
""",
|
543
|
-
config_class=
|
640
|
+
config_class=GCPBaseProjectIDConfig,
|
544
641
|
),
|
545
642
|
AuthenticationMethodModel(
|
546
643
|
name="GCP User Account",
|
@@ -786,14 +883,53 @@ GKE clusters in the GCP project that it is configured to use.
|
|
786
883
|
emoji=":cyclone:",
|
787
884
|
),
|
788
885
|
ResourceTypeModel(
|
789
|
-
name="GCP
|
886
|
+
name="GCP GAR container registry",
|
790
887
|
resource_type=DOCKER_REGISTRY_RESOURCE_TYPE,
|
791
888
|
description="""
|
792
|
-
Allows Stack Components to access a
|
889
|
+
Allows Stack Components to access a Google Artifact Registry as a standard
|
793
890
|
Docker registry resource. When used by Stack Components, they are provided a
|
794
891
|
pre-authenticated Python Docker client instance.
|
795
892
|
|
796
|
-
The configured credentials must have at least the following [GCP permissions](https://cloud.google.com/iam/docs/
|
893
|
+
The configured credentials must have at least the following [GCP permissions](https://cloud.google.com/iam/docs/understanding-roles#artifact-registry-roles):
|
894
|
+
|
895
|
+
- `artifactregistry.repositories.createOnPush`
|
896
|
+
- `artifactregistry.repositories.downloadArtifacts`
|
897
|
+
- `artifactregistry.repositories.get`
|
898
|
+
- `artifactregistry.repositories.list`
|
899
|
+
- `artifactregistry.repositories.readViaVirtualRepository`
|
900
|
+
- `artifactregistry.repositories.uploadArtifacts`
|
901
|
+
- `artifactregistry.locations.list`
|
902
|
+
|
903
|
+
The Artifact Registry Create-on-Push Writer role includes all of the above
|
904
|
+
permissions.
|
905
|
+
|
906
|
+
This resource type also includes legacy GCR container registry support.
|
907
|
+
|
908
|
+
**Important Notice: Google Container Registry** [**is being replaced by Artifact Registry**](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr).
|
909
|
+
Please start using Artifact Registry for your containers. As per Google's
|
910
|
+
documentation, *"after May 15, 2024, Artifact Registry will host images for the
|
911
|
+
gcr.io domain in Google Cloud projects without previous Container Registry
|
912
|
+
usage. After March 18, 2025, Container Registry will be shut down."*.
|
913
|
+
|
914
|
+
Support for legacy GCR registries is still included in the GCP service
|
915
|
+
connector. Users that already have GCP service connectors configured to access
|
916
|
+
GCR registries may continue to use them without taking any action. However, it
|
917
|
+
is recommended to transition to Google Artifact Registries as soon as possible
|
918
|
+
by following [the GCP guide on this subject](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)
|
919
|
+
and making the following updates to ZenML GCP Service Connectors that are used
|
920
|
+
to access GCR resources:
|
921
|
+
|
922
|
+
* add the IAM permissions documented here to the GCP Service Connector
|
923
|
+
credentials to enable them to access the Artifact Registries.
|
924
|
+
* users may keep the gcr.io GCR URLs already configured in the GCP Service
|
925
|
+
Connectors as well as those used in linked Container Registry stack components
|
926
|
+
given that these domains are redirected by Google to GAR as covered in the GCR
|
927
|
+
transition guide. Alternatively, users may update the GCP Service Connector
|
928
|
+
configuration and/or the Container Registry stack components to use the
|
929
|
+
replacement Artifact Registry URLs.
|
930
|
+
|
931
|
+
When used with GCR registries, the configured credentials must have at least the
|
932
|
+
following [GCP permissions](https://cloud.google.com/iam/docs/understanding-roles#cloud-storage-roles):
|
797
933
|
|
798
934
|
- `storage.buckets.get`
|
799
935
|
- `storage.multipartUploads.abort`
|
@@ -807,17 +943,21 @@ The configured credentials must have at least the following [GCP permissions](ht
|
|
807
943
|
The Storage Legacy Bucket Writer role includes all of the above permissions
|
808
944
|
while at the same time restricting access to only the GCR buckets.
|
809
945
|
|
810
|
-
|
811
|
-
|
812
|
-
|
946
|
+
If set, the resource name must identify a GAR or GCR registry using one of the
|
947
|
+
following formats:
|
948
|
+
|
949
|
+
- Google Artifact Registry repository URI: `[https://]<region>-docker.pkg.dev/<project-id>/<registry-id>[/<repository-name>]`
|
950
|
+
- Google Artifact Registry name: `projects/<project-id>/locations/<location>/repositories/<repository-id>`
|
951
|
+
- (legacy) GCR repository URI: `[https://][us.|eu.|asia.]gcr.io/<project-id>[/<repository-name>]`
|
813
952
|
|
814
|
-
|
953
|
+
The connector can only be used to access GAR and GCR registries in the GCP
|
954
|
+
project that it is configured to use.
|
815
955
|
""",
|
816
956
|
auth_methods=GCPAuthenticationMethods.values(),
|
817
|
-
#
|
818
|
-
#
|
819
|
-
#
|
820
|
-
supports_instances=
|
957
|
+
# The connector provides access to the entire GCR container registry
|
958
|
+
# for the configured GCP project as well as any number of artifact
|
959
|
+
# registry repositories.
|
960
|
+
supports_instances=True,
|
821
961
|
logo_url="https://public-flavor-logos.s3.eu-central-1.amazonaws.com/container_registry/docker.png",
|
822
962
|
emoji=":whale:",
|
823
963
|
),
|
@@ -1006,6 +1146,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1006
1146
|
# service account authentication)
|
1007
1147
|
|
1008
1148
|
assert isinstance(cfg, GCPServiceAccountConfig)
|
1149
|
+
|
1009
1150
|
credentials = (
|
1010
1151
|
gcp_service_account.Credentials.from_service_account_info(
|
1011
1152
|
json.loads(
|
@@ -1093,60 +1234,101 @@ class GCPServiceConnector(ServiceConnector):
|
|
1093
1234
|
|
1094
1235
|
return bucket_name
|
1095
1236
|
|
1096
|
-
def
|
1237
|
+
def _parse_gar_resource_id(
|
1097
1238
|
self,
|
1098
1239
|
resource_id: str,
|
1099
|
-
) -> str:
|
1100
|
-
"""Validate and convert
|
1240
|
+
) -> Tuple[str, Optional[str]]:
|
1241
|
+
"""Validate and convert a GAR resource ID to a Google Artifact Registry ID and name.
|
1101
1242
|
|
1102
1243
|
Args:
|
1103
1244
|
resource_id: The resource ID to convert.
|
1104
1245
|
|
1105
1246
|
Returns:
|
1106
|
-
The
|
1247
|
+
The Google Artifact Registry ID and name. The name is omitted if the
|
1248
|
+
resource ID is a GCR repository URI.
|
1107
1249
|
|
1108
1250
|
Raises:
|
1109
|
-
ValueError: If the provided resource ID is not a valid
|
1110
|
-
repository URI.
|
1251
|
+
ValueError: If the provided resource ID is not a valid GAR
|
1252
|
+
or GCR repository URI.
|
1111
1253
|
"""
|
1112
1254
|
# The resource ID could mean different things:
|
1113
1255
|
#
|
1114
|
-
# -
|
1256
|
+
# - a GAR repository URI
|
1257
|
+
# - a GAR repository name
|
1258
|
+
# - a GCR repository URI (backwards-compatibility)
|
1115
1259
|
#
|
1116
1260
|
# We need to extract the project ID and registry ID from
|
1117
1261
|
# the provided resource ID
|
1118
|
-
config_project_id = self.config.
|
1262
|
+
config_project_id = self.config.gcp_project_id
|
1119
1263
|
project_id: Optional[str] = None
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1264
|
+
canonical_url: str
|
1265
|
+
registry_name: Optional[str] = None
|
1266
|
+
|
1267
|
+
# A Google Artifact Registry URI uses the <location>-docker-pkg.dev
|
1268
|
+
# domain format with the project ID as the first part of the URL path
|
1269
|
+
# and the registry name as the second part of the URL path
|
1270
|
+
if match := re.match(
|
1271
|
+
r"^(https://)?(([a-z0-9-]+)-docker\.pkg\.dev/([a-z0-9-]+)/([a-z0-9-.]+))(/.+)*$",
|
1125
1272
|
resource_id,
|
1126
1273
|
):
|
1127
|
-
# The resource ID is a
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1274
|
+
# The resource ID is a Google Artifact Registry URI
|
1275
|
+
project_id = match[4]
|
1276
|
+
location = match[3]
|
1277
|
+
repository = match[5]
|
1278
|
+
|
1279
|
+
# Return the GAR URL without the image name and without the protocol
|
1280
|
+
canonical_url = match[2]
|
1281
|
+
registry_name = f"projects/{project_id}/locations/{location}/repositories/{repository}"
|
1282
|
+
|
1283
|
+
# Alternatively, the Google Artifact Registry name uses the
|
1284
|
+
# projects/<project-id>/locations/<location>/repositories/<repository-id>
|
1285
|
+
# format
|
1286
|
+
elif match := re.match(
|
1287
|
+
r"^projects/([a-z0-9-]+)/locations/([a-z0-9-]+)/repositories/([a-z0-9-.]+)$",
|
1288
|
+
resource_id,
|
1289
|
+
):
|
1290
|
+
# The resource ID is a Google Artifact Registry name
|
1291
|
+
project_id = match[1]
|
1292
|
+
location = match[2]
|
1293
|
+
repository = match[3]
|
1294
|
+
|
1295
|
+
# Return the GAR URL
|
1296
|
+
canonical_url = (
|
1297
|
+
f"{location}-docker.pkg.dev/{project_id}/{repository}"
|
1298
|
+
)
|
1299
|
+
registry_name = resource_id
|
1300
|
+
|
1301
|
+
# A legacy GCR repository URI uses one of several hostnames (gcr.io,
|
1302
|
+
# us.gcr.io, eu.gcr.io, asia.gcr.io) and the project ID is the
|
1303
|
+
# first part of the URL path
|
1304
|
+
elif match := re.match(
|
1305
|
+
r"^(https://)?(((us|eu|asia)\.)?gcr\.io/[a-z0-9-]+)(/.+)*$",
|
1306
|
+
resource_id,
|
1307
|
+
):
|
1308
|
+
# The resource ID is a legacy GCR repository URI.
|
1309
|
+
# Return the GAR URL without the image name and without the protocol
|
1310
|
+
canonical_url = match[2]
|
1311
|
+
|
1132
1312
|
else:
|
1133
1313
|
raise ValueError(
|
1134
|
-
f"Invalid resource ID for a
|
1135
|
-
f"Supported formats are:\n"
|
1314
|
+
f"Invalid resource ID for a Google Artifact Registry: "
|
1315
|
+
f"{resource_id}. Supported formats are:\n"
|
1316
|
+
f"Google Artifact Registry URI: [https://]<region>-docker.pkg.dev/<project-id>/<registry-id>[/<repository-name>]\n"
|
1317
|
+
f"Google Artifact Registry name: projects/<project-id>/locations/<location>/repositories/<repository-id>\n"
|
1136
1318
|
f"GCR repository URI: [https://][us.|eu.|asia.]gcr.io/<project-id>[/<repository-name>]"
|
1137
1319
|
)
|
1138
1320
|
|
1139
1321
|
# If the connector is configured with a project and the resource ID
|
1140
|
-
# is
|
1322
|
+
# is a GAR repository URI that specifies a different project,
|
1141
1323
|
# we raise an error
|
1142
1324
|
if project_id and project_id != config_project_id:
|
1143
1325
|
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}'."
|
1326
|
+
f"The GCP project for the {resource_id} Google Artifact "
|
1327
|
+
f"Registry '{project_id}' does not match the project "
|
1328
|
+
f"configured in the connector: '{config_project_id}'."
|
1147
1329
|
)
|
1148
1330
|
|
1149
|
-
return
|
1331
|
+
return canonical_url, registry_name
|
1150
1332
|
|
1151
1333
|
def _parse_gke_resource_id(self, resource_id: str) -> str:
|
1152
1334
|
"""Validate and convert an GKE resource ID to a GKE cluster name.
|
@@ -1195,7 +1377,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1195
1377
|
cluster_name = self._parse_gke_resource_id(resource_id)
|
1196
1378
|
return cluster_name
|
1197
1379
|
elif resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1198
|
-
registry_id = self.
|
1380
|
+
registry_id, _ = self._parse_gar_resource_id(
|
1199
1381
|
resource_id,
|
1200
1382
|
)
|
1201
1383
|
return registry_id
|
@@ -1219,9 +1401,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1219
1401
|
authorized.
|
1220
1402
|
"""
|
1221
1403
|
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}"
|
1404
|
+
return self.config.gcp_project_id
|
1225
1405
|
|
1226
1406
|
raise RuntimeError(
|
1227
1407
|
f"Default resource ID not supported for '{resource_type}' resource "
|
@@ -1278,7 +1458,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1278
1458
|
|
1279
1459
|
# Create an GCS client for the bucket
|
1280
1460
|
client = storage.Client(
|
1281
|
-
project=self.config.
|
1461
|
+
project=self.config.gcp_project_id, credentials=credentials
|
1282
1462
|
)
|
1283
1463
|
return client
|
1284
1464
|
|
@@ -1384,7 +1564,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1384
1564
|
"config",
|
1385
1565
|
"set",
|
1386
1566
|
"project",
|
1387
|
-
self.config.
|
1567
|
+
self.config.gcp_project_id,
|
1388
1568
|
],
|
1389
1569
|
check=True,
|
1390
1570
|
stderr=subprocess.STDOUT,
|
@@ -1488,7 +1668,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1488
1668
|
)
|
1489
1669
|
|
1490
1670
|
if auth_method == GCPAuthenticationMethods.IMPLICIT:
|
1491
|
-
auth_config =
|
1671
|
+
auth_config = GCPBaseProjectIDConfig(
|
1492
1672
|
project_id=project_id,
|
1493
1673
|
)
|
1494
1674
|
elif auth_method == GCPAuthenticationMethods.OAUTH2_TOKEN:
|
@@ -1697,7 +1877,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1697
1877
|
|
1698
1878
|
if resource_type == GCS_RESOURCE_TYPE:
|
1699
1879
|
gcs_client = storage.Client(
|
1700
|
-
project=self.config.
|
1880
|
+
project=self.config.gcp_project_id, credentials=credentials
|
1701
1881
|
)
|
1702
1882
|
if not resource_id:
|
1703
1883
|
# List all GCS buckets
|
@@ -1722,11 +1902,87 @@ class GCPServiceConnector(ServiceConnector):
|
|
1722
1902
|
raise AuthorizationException(msg) from e
|
1723
1903
|
|
1724
1904
|
if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1725
|
-
|
1905
|
+
# Get a GAR client
|
1906
|
+
gar_client = artifactregistry_v1.ArtifactRegistryClient(
|
1907
|
+
credentials=credentials
|
1908
|
+
)
|
1726
1909
|
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1910
|
+
if resource_id:
|
1911
|
+
registry_id, registry_name = self._parse_gar_resource_id(
|
1912
|
+
resource_id
|
1913
|
+
)
|
1914
|
+
|
1915
|
+
if registry_name is None:
|
1916
|
+
# This is a legacy GCR repository URI. We can't verify
|
1917
|
+
# the repository access without attempting to connect to it
|
1918
|
+
# via Docker/OCI, so just return the resource ID.
|
1919
|
+
return [registry_id]
|
1920
|
+
|
1921
|
+
# Check if the specified GAR registry exists
|
1922
|
+
try:
|
1923
|
+
repository = gar_client.get_repository(
|
1924
|
+
name=registry_name,
|
1925
|
+
)
|
1926
|
+
if repository.format_.name != "DOCKER":
|
1927
|
+
raise AuthorizationException(
|
1928
|
+
f"Google Artifact Registry '{resource_id}' is not a "
|
1929
|
+
"Docker registry."
|
1930
|
+
)
|
1931
|
+
return [registry_id]
|
1932
|
+
except google.api_core.exceptions.GoogleAPIError as e:
|
1933
|
+
msg = f"Failed to fetch Google Artifact Registry '{registry_id}': {e}"
|
1934
|
+
logger.error(msg)
|
1935
|
+
raise AuthorizationException(msg) from e
|
1936
|
+
|
1937
|
+
# For backwards compatibility, we initialize the list of resource
|
1938
|
+
# IDs with all GCR supported registries for the configured GCP
|
1939
|
+
# project
|
1940
|
+
resource_ids: List[str] = [
|
1941
|
+
f"{location}gcr.io/{self.config.gcp_project_id}"
|
1942
|
+
for location in ["", "us.", "eu.", "asia."]
|
1943
|
+
]
|
1944
|
+
|
1945
|
+
# List all Google Artifact Registries
|
1946
|
+
try:
|
1947
|
+
# First, we need to fetch all the Artifact Registry supported
|
1948
|
+
# locations
|
1949
|
+
locations = gar_client.list_locations(
|
1950
|
+
request=locations_pb2.ListLocationsRequest(
|
1951
|
+
name=f"projects/{self.config.gcp_project_id}"
|
1952
|
+
)
|
1953
|
+
)
|
1954
|
+
location_names = [
|
1955
|
+
locations.locations[i].location_id
|
1956
|
+
for i in range(len(locations.locations))
|
1957
|
+
]
|
1958
|
+
|
1959
|
+
# Then, we need to fetch all the repositories in each location
|
1960
|
+
repository_names: List[str] = []
|
1961
|
+
for location in location_names:
|
1962
|
+
repositories = gar_client.list_repositories(
|
1963
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/{location}"
|
1964
|
+
)
|
1965
|
+
repository_names.extend(
|
1966
|
+
[
|
1967
|
+
repository.name
|
1968
|
+
for repository in repositories
|
1969
|
+
if repository.format_.name == "DOCKER"
|
1970
|
+
]
|
1971
|
+
)
|
1972
|
+
|
1973
|
+
for repository_name in repository_names:
|
1974
|
+
# Convert the repository name to a canonical GAR URL
|
1975
|
+
resource_ids.append(
|
1976
|
+
self._parse_gar_resource_id(repository_name)[0]
|
1977
|
+
)
|
1978
|
+
|
1979
|
+
except google.api_core.exceptions.GoogleAPIError as e:
|
1980
|
+
msg = f"Failed to list Google Artifact Registries: {e}"
|
1981
|
+
logger.error(msg)
|
1982
|
+
# TODO: enable when GCR is no longer supported:
|
1983
|
+
# raise AuthorizationException(msg) from e
|
1984
|
+
|
1985
|
+
return resource_ids
|
1730
1986
|
|
1731
1987
|
if resource_type == KUBERNETES_CLUSTER_RESOURCE_TYPE:
|
1732
1988
|
gke_client = container_v1.ClusterManagerClient(
|
@@ -1736,7 +1992,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1736
1992
|
# List all GKE clusters
|
1737
1993
|
try:
|
1738
1994
|
clusters = gke_client.list_clusters(
|
1739
|
-
parent=f"projects/{self.config.
|
1995
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/-"
|
1740
1996
|
)
|
1741
1997
|
cluster_names = [cluster.name for cluster in clusters.clusters]
|
1742
1998
|
except google.api_core.exceptions.GoogleAPIError as e:
|
@@ -1810,7 +2066,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1810
2066
|
# object
|
1811
2067
|
auth_method: str = GCPAuthenticationMethods.OAUTH2_TOKEN
|
1812
2068
|
config: GCPBaseConfig = GCPOAuth2TokenConfig(
|
1813
|
-
project_id=self.config.
|
2069
|
+
project_id=self.config.gcp_project_id,
|
1814
2070
|
token=credentials.token,
|
1815
2071
|
service_account_email=credentials.signer_email
|
1816
2072
|
if hasattr(credentials, "signer_email")
|
@@ -1855,7 +2111,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1855
2111
|
if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1856
2112
|
assert resource_id is not None
|
1857
2113
|
|
1858
|
-
registry_id = self.
|
2114
|
+
registry_id, _ = self._parse_gar_resource_id(resource_id)
|
1859
2115
|
|
1860
2116
|
# Create a client-side Docker connector instance with the temporary
|
1861
2117
|
# Docker credentials
|
@@ -1884,7 +2140,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1884
2140
|
# List all GKE clusters
|
1885
2141
|
try:
|
1886
2142
|
clusters = gke_client.list_clusters(
|
1887
|
-
parent=f"projects/{self.config.
|
2143
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/-"
|
1888
2144
|
)
|
1889
2145
|
cluster_map = {
|
1890
2146
|
cluster.name: cluster for cluster in clusters.clusters
|
@@ -1928,7 +2184,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1928
2184
|
auth_method=KubernetesAuthenticationMethods.TOKEN,
|
1929
2185
|
resource_type=resource_type,
|
1930
2186
|
config=KubernetesTokenConfig(
|
1931
|
-
cluster_name=f"gke_{self.config.
|
2187
|
+
cluster_name=f"gke_{self.config.gcp_project_id}_{cluster_name}",
|
1932
2188
|
certificate_authority=cluster_ca_cert,
|
1933
2189
|
server=f"https://{cluster_server}",
|
1934
2190
|
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."""
|