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.
Files changed (249) hide show
  1. README.md +30 -9
  2. RELEASE_NOTES.md +240 -0
  3. zenml/VERSION +1 -1
  4. zenml/actions/base_action.py +177 -174
  5. zenml/actions/pipeline_run/pipeline_run_action.py +28 -23
  6. zenml/analytics/enums.py +3 -0
  7. zenml/artifact_stores/base_artifact_store.py +7 -1
  8. zenml/artifacts/utils.py +13 -10
  9. zenml/cli/__init__.py +28 -0
  10. zenml/cli/artifact.py +1 -2
  11. zenml/cli/integration.py +9 -8
  12. zenml/cli/server.py +6 -0
  13. zenml/cli/service_connectors.py +1 -0
  14. zenml/cli/stack.py +946 -39
  15. zenml/cli/stack_components.py +7 -0
  16. zenml/cli/text_utils.py +35 -1
  17. zenml/cli/utils.py +127 -10
  18. zenml/client.py +257 -72
  19. zenml/config/compiler.py +10 -9
  20. zenml/config/docker_settings.py +33 -14
  21. zenml/constants.py +11 -2
  22. zenml/container_registries/base_container_registry.py +1 -0
  23. zenml/enums.py +7 -0
  24. zenml/event_hub/base_event_hub.py +5 -5
  25. zenml/event_hub/event_hub.py +20 -14
  26. zenml/event_sources/base_event.py +0 -11
  27. zenml/event_sources/base_event_source.py +7 -0
  28. zenml/event_sources/webhooks/base_webhook_event_source.py +1 -4
  29. zenml/exceptions.py +4 -0
  30. zenml/hooks/hook_validators.py +2 -3
  31. zenml/integrations/aws/__init__.py +1 -0
  32. zenml/integrations/azure/__init__.py +1 -0
  33. zenml/integrations/bitbucket/plugins/event_sources/bitbucket_webhook_event_source.py +3 -3
  34. zenml/integrations/deepchecks/__init__.py +1 -0
  35. zenml/integrations/discord/__init__.py +1 -0
  36. zenml/integrations/evidently/__init__.py +1 -0
  37. zenml/integrations/facets/__init__.py +1 -0
  38. zenml/integrations/feast/__init__.py +1 -0
  39. zenml/integrations/gcp/__init__.py +3 -1
  40. zenml/integrations/gcp/google_credentials_mixin.py +1 -1
  41. zenml/integrations/gcp/service_connectors/gcp_service_connector.py +320 -64
  42. zenml/integrations/huggingface/__init__.py +1 -0
  43. zenml/integrations/integration.py +24 -0
  44. zenml/integrations/kubeflow/__init__.py +3 -0
  45. zenml/integrations/kubeflow/flavors/kubeflow_orchestrator_flavor.py +1 -1
  46. zenml/integrations/kubeflow/orchestrators/kubeflow_orchestrator.py +0 -1
  47. zenml/integrations/kubernetes/__init__.py +3 -1
  48. zenml/integrations/kubernetes/orchestrators/kube_utils.py +4 -1
  49. zenml/integrations/label_studio/annotators/label_studio_annotator.py +1 -0
  50. zenml/integrations/langchain/__init__.py +1 -0
  51. zenml/integrations/mlflow/__init__.py +4 -2
  52. zenml/integrations/neural_prophet/__init__.py +1 -0
  53. zenml/integrations/polars/__init__.py +1 -0
  54. zenml/integrations/prodigy/__init__.py +1 -0
  55. zenml/integrations/pycaret/__init__.py +6 -0
  56. zenml/integrations/registry.py +37 -0
  57. zenml/integrations/s3/artifact_stores/s3_artifact_store.py +93 -9
  58. zenml/integrations/seldon/__init__.py +1 -0
  59. zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -0
  60. zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +2 -2
  61. zenml/integrations/skypilot/orchestrators/skypilot_base_vm_orchestrator.py +1 -1
  62. zenml/integrations/skypilot/orchestrators/skypilot_orchestrator_entrypoint.py +2 -2
  63. zenml/integrations/skypilot_aws/__init__.py +2 -1
  64. zenml/integrations/skypilot_azure/__init__.py +1 -1
  65. zenml/integrations/skypilot_gcp/__init__.py +1 -1
  66. zenml/integrations/skypilot_lambda/__init__.py +1 -1
  67. zenml/integrations/skypilot_lambda/flavors/skypilot_orchestrator_lambda_vm_flavor.py +1 -1
  68. zenml/integrations/slack/__init__.py +1 -0
  69. zenml/integrations/tekton/__init__.py +1 -0
  70. zenml/integrations/tensorboard/__init__.py +0 -1
  71. zenml/integrations/tensorflow/__init__.py +18 -6
  72. zenml/integrations/wandb/__init__.py +1 -0
  73. zenml/logging/step_logging.py +54 -51
  74. zenml/models/__init__.py +28 -0
  75. zenml/models/v2/core/action.py +276 -0
  76. zenml/models/v2/core/component.py +18 -0
  77. zenml/models/v2/core/model.py +1 -2
  78. zenml/models/v2/core/service_connector.py +17 -0
  79. zenml/models/v2/core/stack.py +31 -0
  80. zenml/models/v2/core/trigger.py +182 -141
  81. zenml/models/v2/misc/full_stack.py +97 -0
  82. zenml/models/v2/misc/stack_deployment.py +86 -0
  83. zenml/new/pipelines/pipeline.py +14 -4
  84. zenml/new/pipelines/pipeline_decorator.py +1 -2
  85. zenml/new/pipelines/run_utils.py +1 -12
  86. zenml/new/steps/step_decorator.py +2 -3
  87. zenml/orchestrators/input_utils.py +3 -6
  88. zenml/pipelines/base_pipeline.py +0 -2
  89. zenml/pipelines/pipeline_decorator.py +1 -2
  90. zenml/stack/stack.py +3 -6
  91. zenml/stack/stack_component.py +4 -0
  92. zenml/stack_deployments/__init__.py +14 -0
  93. zenml/stack_deployments/aws_stack_deployment.py +254 -0
  94. zenml/stack_deployments/gcp_stack_deployment.py +260 -0
  95. zenml/stack_deployments/stack_deployment.py +208 -0
  96. zenml/stack_deployments/utils.py +44 -0
  97. zenml/steps/base_step.py +1 -2
  98. zenml/steps/step_decorator.py +1 -2
  99. zenml/types.py +10 -1
  100. zenml/utils/function_utils.py +1 -1
  101. zenml/utils/pagination_utils.py +7 -5
  102. zenml/utils/pipeline_docker_image_builder.py +117 -73
  103. zenml/utils/pydantic_utils.py +6 -5
  104. zenml/zen_server/cloud_utils.py +18 -3
  105. zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-DpJaNHKF.js} +1 -1
  106. zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +85 -0
  107. zenml/zen_server/dashboard/assets/{@react-router-DYovave8.js → @react-router-CO-OsFwI.js} +2 -2
  108. zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-DJfzkHO1.js} +2 -2
  109. zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
  110. zenml/zen_server/dashboard/assets/AwarenessChannel-BYDLT2xC.js +1 -0
  111. zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-BkOuRmyq.js} +2 -2
  112. zenml/zen_server/dashboard/assets/Commands-ZvWR1BRs.js +1 -0
  113. zenml/zen_server/dashboard/assets/CopyButton-DVwLkafa.js +2 -0
  114. zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-C2IiqX4I.js} +7 -7
  115. zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +1 -0
  116. zenml/zen_server/dashboard/assets/EmptyState-BMLnFVlB.js +1 -0
  117. zenml/zen_server/dashboard/assets/Error-CqX0VqW_.js +1 -0
  118. zenml/zen_server/dashboard/assets/ExecutionStatus-BoLUXR9t.js +1 -0
  119. zenml/zen_server/dashboard/assets/Helpbox-LFydyVwh.js +1 -0
  120. zenml/zen_server/dashboard/assets/Infobox-DnENC0sh.js +1 -0
  121. zenml/zen_server/dashboard/assets/InlineAvatar-CbJtYr0t.js +1 -0
  122. zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
  123. zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +1 -0
  124. zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
  125. zenml/zen_server/dashboard/assets/SetPassword-BYBdbQDo.js +1 -0
  126. zenml/zen_server/dashboard/assets/SuccessStep-Nx743hll.js +1 -0
  127. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DF9gSzE0.js} +1 -1
  128. zenml/zen_server/dashboard/assets/{aws-t0gKCj_R.js → aws-BgKTfTfx.js} +1 -1
  129. zenml/zen_server/dashboard/assets/{check-circle-BVvhm5dy.js → check-circle-i56092KI.js} +1 -1
  130. zenml/zen_server/dashboard/assets/{chevron-down-zcvCWmyP.js → chevron-down-D_ZlKMqH.js} +1 -1
  131. zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-BiEMg7rd.js} +1 -1
  132. zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.js +1 -0
  133. zenml/zen_server/dashboard/assets/{copy-BRhQz3j-.js → copy-BXNk6BjL.js} +1 -1
  134. zenml/zen_server/dashboard/assets/{database-CRRnyFWh.js → database-1xWSgZfO.js} +1 -1
  135. zenml/zen_server/dashboard/assets/{docker-BAonhm6G.js → docker-CQMVm_4d.js} +1 -1
  136. zenml/zen_server/dashboard/assets/{file-text-CbVERUON.js → file-text-CqD_iu6l.js} +1 -1
  137. zenml/zen_server/dashboard/assets/{help-B8rqCvqn.js → help-bu_DgLKI.js} +1 -1
  138. zenml/zen_server/dashboard/assets/index-C_CrU4vI.js +1 -0
  139. zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +55 -0
  140. zenml/zen_server/dashboard/assets/index-inApY3KQ.css +1 -0
  141. zenml/zen_server/dashboard/assets/index-rK_Wuy2W.js +1 -0
  142. zenml/zen_server/dashboard/assets/index.esm-Corw4lXQ.js +1 -0
  143. zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-BUnVASxp.js} +1 -1
  144. zenml/zen_server/dashboard/assets/not-found-B4VnX8gK.js +1 -0
  145. zenml/zen_server/dashboard/assets/package-CsUhPmou.js +1 -0
  146. zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-3efNCDeb.js} +2 -2
  147. zenml/zen_server/dashboard/assets/page-7zTHbhhI.js +1 -0
  148. zenml/zen_server/dashboard/assets/page-BEs6jK71.js +1 -0
  149. zenml/zen_server/dashboard/assets/page-BpSqIf4B.js +1 -0
  150. zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-Bx6o0ARS.js} +1 -1
  151. zenml/zen_server/dashboard/assets/page-C43QGHTt.js +9 -0
  152. zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +1 -0
  153. zenml/zen_server/dashboard/assets/page-CRTJ0UuR.js +1 -0
  154. zenml/zen_server/dashboard/assets/page-CUZIGO-3.js +1 -0
  155. zenml/zen_server/dashboard/assets/page-CaopxiU1.js +1 -0
  156. zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-Cx67M0QT.js} +1 -1
  157. zenml/zen_server/dashboard/assets/page-D7Z399xy.js +1 -0
  158. zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +1 -0
  159. zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-DKlIdAe5.js} +1 -1
  160. zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-DMOYZppS.js} +1 -1
  161. zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +2 -0
  162. zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-Dc_7KMQE.js} +1 -1
  163. zenml/zen_server/dashboard/assets/page-DvCvroOM.js +1 -0
  164. zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +1 -0
  165. zenml/zen_server/dashboard/assets/page-JyfeDUfu.js +1 -0
  166. zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-Sxn82W-5.js} +1 -1
  167. zenml/zen_server/dashboard/assets/page-TKXERe16.js +1 -0
  168. zenml/zen_server/dashboard/assets/page-Xu8JEjSU.js +1 -0
  169. zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
  170. zenml/zen_server/dashboard/assets/plus-DOeLmm7C.js +1 -0
  171. zenml/zen_server/dashboard/assets/{terminal-B2ovgWuz.js → terminal-By9cErXc.js} +1 -1
  172. zenml/zen_server/dashboard/assets/{update-server-settings-mutation-0Wgz8pUE.js → update-server-settings-mutation-CR8e3Sir.js} +1 -1
  173. zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-DuQMeqYA.js} +1 -1
  174. zenml/zen_server/dashboard/assets/{zod-DrZvVLjd.js → zod-BhoGpZ63.js} +1 -1
  175. zenml/zen_server/dashboard/index.html +7 -7
  176. zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
  177. zenml/zen_server/dashboard_legacy/index.html +1 -1
  178. zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.c8c57fb0d2132b1d3c2119e776b7dfb3.js} +4 -4
  179. zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
  180. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js → main.382439a7.chunk.js} +2 -2
  181. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.382439a7.chunk.js.map} +1 -1
  182. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  183. zenml/zen_server/deploy/helm/README.md +2 -2
  184. zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +11 -5
  185. zenml/zen_server/pipeline_deployment/utils.py +57 -44
  186. zenml/zen_server/rbac/models.py +1 -0
  187. zenml/zen_server/rbac/utils.py +22 -1
  188. zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
  189. zenml/zen_server/routers/actions_endpoints.py +324 -0
  190. zenml/zen_server/routers/stack_deployment_endpoints.py +158 -0
  191. zenml/zen_server/routers/triggers_endpoints.py +30 -158
  192. zenml/zen_server/routers/workspaces_endpoints.py +64 -0
  193. zenml/zen_server/zen_server_api.py +4 -0
  194. zenml/zen_stores/migrations/utils.py +1 -1
  195. zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
  196. zenml/zen_stores/migrations/versions/0.61.0_release.py +23 -0
  197. zenml/zen_stores/migrations/versions/0d707865f404_adding_labels_to_stacks.py +30 -0
  198. zenml/zen_stores/migrations/versions/25155145c545_separate_actions_and_triggers.py +228 -0
  199. zenml/zen_stores/rest_zen_store.py +248 -8
  200. zenml/zen_stores/schemas/__init__.py +2 -0
  201. zenml/zen_stores/schemas/action_schemas.py +192 -0
  202. zenml/zen_stores/schemas/stack_schemas.py +10 -0
  203. zenml/zen_stores/schemas/step_run_schemas.py +27 -11
  204. zenml/zen_stores/schemas/trigger_schemas.py +43 -50
  205. zenml/zen_stores/schemas/user_schemas.py +10 -2
  206. zenml/zen_stores/schemas/workspace_schemas.py +5 -0
  207. zenml/zen_stores/sql_zen_store.py +540 -36
  208. zenml/zen_stores/zen_store_interface.py +165 -0
  209. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/METADATA +33 -11
  210. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/RECORD +213 -193
  211. zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js +0 -77
  212. zenml/zen_server/dashboard/assets/@tanstack-CEbkxrhX.js +0 -30
  213. zenml/zen_server/dashboard/assets/AwarenessChannel-nXGpmj_f.js +0 -1
  214. zenml/zen_server/dashboard/assets/Cards-nwsvQLVS.js +0 -1
  215. zenml/zen_server/dashboard/assets/Commands-DuIWKg_Q.js +0 -1
  216. zenml/zen_server/dashboard/assets/CopyButton-B_YSm-Ds.js +0 -2
  217. zenml/zen_server/dashboard/assets/DisplayDate-BdguISQF.js +0 -1
  218. zenml/zen_server/dashboard/assets/EmptyState-BkooiGtL.js +0 -1
  219. zenml/zen_server/dashboard/assets/Error-B6M0dPph.js +0 -1
  220. zenml/zen_server/dashboard/assets/Helpbox-BQoqCm04.js +0 -1
  221. zenml/zen_server/dashboard/assets/Infobox-Ce9mefqU.js +0 -1
  222. zenml/zen_server/dashboard/assets/InlineAvatar-DGf3dVhV.js +0 -1
  223. zenml/zen_server/dashboard/assets/PageHeader-DGaemzjc.js +0 -1
  224. zenml/zen_server/dashboard/assets/Pagination-DVYfBCCc.js +0 -1
  225. zenml/zen_server/dashboard/assets/PasswordChecker-DSLBp7Vl.js +0 -1
  226. zenml/zen_server/dashboard/assets/SetPassword-B5s7DJug.js +0 -1
  227. zenml/zen_server/dashboard/assets/SuccessStep-ZzczaM7g.js +0 -1
  228. zenml/zen_server/dashboard/assets/cloud-only-Ba_ShBR5.js +0 -1
  229. zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
  230. zenml/zen_server/dashboard/assets/index-QORVVTMN.js +0 -55
  231. zenml/zen_server/dashboard/assets/index.esm-F7nqy9zY.js +0 -1
  232. zenml/zen_server/dashboard/assets/not-found-Dh2la7kh.js +0 -1
  233. zenml/zen_server/dashboard/assets/page-B-5jAKoO.js +0 -1
  234. zenml/zen_server/dashboard/assets/page-B-vWk8a6.js +0 -1
  235. zenml/zen_server/dashboard/assets/page-B0BrqfS8.js +0 -1
  236. zenml/zen_server/dashboard/assets/page-BQxVFlUl.js +0 -1
  237. zenml/zen_server/dashboard/assets/page-ByrHy6Ss.js +0 -1
  238. zenml/zen_server/dashboard/assets/page-CPtY4Kv_.js +0 -1
  239. zenml/zen_server/dashboard/assets/page-CmmukLsl.js +0 -1
  240. zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
  241. zenml/zen_server/dashboard/assets/page-DAQQyLxT.js +0 -1
  242. zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
  243. zenml/zen_server/dashboard/assets/page-DZCbwOEs.js +0 -2
  244. zenml/zen_server/dashboard/assets/page-DdaIt20-.js +0 -1
  245. zenml/zen_server/dashboard/assets/page-LqLs24Ot.js +0 -1
  246. zenml/zen_server/dashboard/assets/page-lebv0c7C.js +0 -1
  247. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/LICENSE +0 -0
  248. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.61.0.dev20240712.dist-info}/WHEEL +0 -0
  249. {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
- if isinstance(data.get("user_account_json"), dict):
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
- if isinstance(data.get("service_account_json"), dict):
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
- if isinstance(data.get("external_account_json"), dict):
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(GCPBaseConfig, GCPUserAccountCredentials):
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
- class GCPExternalAccountConfig(GCPBaseConfig, GCPExternalAccountCredentials):
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(GCPBaseConfig, GCPOAuth2Token):
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=GCPBaseConfig,
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 GCR container registry",
886
+ name="GCP GAR container registry",
790
887
  resource_type=DOCKER_REGISTRY_RESOURCE_TYPE,
791
888
  description="""
792
- Allows Stack Components to access a GCR registry as a standard
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/permissions-reference):
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
- The resource name associated with this resource type identifies the GCR
811
- container registry associated with the GCP project (the repository name is
812
- optional):
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
- - GCR repository URI: `[https://]gcr.io/{project-id}[/{repository-name}]
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
- # Does not support instances, given that the connector
818
- # provides access to the entire GCR container registry
819
- # for the configured GCP project.
820
- supports_instances=False,
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 _parse_gcr_resource_id(
1237
+ def _parse_gar_resource_id(
1097
1238
  self,
1098
1239
  resource_id: str,
1099
- ) -> str:
1100
- """Validate and convert an GCR resource ID to an GCR registry ID.
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 GCR registry ID.
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 GCR
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
- # - an GCR repository URI
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.project_id
1262
+ config_project_id = self.config.gcp_project_id
1119
1263
  project_id: Optional[str] = None
1120
- # A GCR repository URI uses one of several hostnames (gcr.io, us.gcr.io,
1121
- # eu.gcr.io, asia.gcr.io etc.) and the project ID is the first part of
1122
- # the URL path
1123
- if re.match(
1124
- r"^(https://)?([a-z]+.)*gcr.io/[a-z0-9-]+(/.+)*$",
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 GCR repository URI
1128
- if resource_id.startswith("https://"):
1129
- project_id = resource_id.split("/")[3]
1130
- else:
1131
- project_id = resource_id.split("/")[1]
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 GCR registry: {resource_id}. "
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 an GCR repository URI that specifies a different project,
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} GCR repository "
1145
- f"'{project_id}' does not match the project configured in "
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 f"gcr.io/{project_id}"
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._parse_gcr_resource_id(
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.project_id
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.project_id, credentials=credentials
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.project_id,
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 = GCPBaseConfig(
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.project_id, credentials=credentials
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
- assert resource_id is not None
1905
+ # Get a GAR client
1906
+ gar_client = artifactregistry_v1.ArtifactRegistryClient(
1907
+ credentials=credentials
1908
+ )
1726
1909
 
1727
- # No way to verify a GCR registry without attempting to
1728
- # connect to it via Docker/OCI, so just return the resource ID.
1729
- return [resource_id]
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.project_id}/locations/-"
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.project_id,
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._parse_gcr_resource_id(resource_id)
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.project_id}/locations/-"
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.project_id}_{cluster_name}",
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,
@@ -39,6 +39,7 @@ class HuggingfaceIntegration(Integration):
39
39
  # TODO try relaxing it back going forward
40
40
  "fsspec<=2023.12.0",
41
41
  ]
42
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = ["fsspec"]
42
43
 
43
44
  @classmethod
44
45
  def activate(cls) -> None:
@@ -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."""