2323console = Console ()
2424
2525TEMPORAL_WORKER_KEY = "temporal-worker"
26- AGENTEX_AGENTS_HELM_CHART_VERSION = "0.1.9"
26+ DEFAULT_HELM_CHART_VERSION = "0.1.9"
2727
2828
2929class InputDeployOverrides (BaseModel ):
@@ -42,7 +42,7 @@ def check_helm_installed() -> bool:
4242
4343
4444def add_helm_repo (helm_repository_name : str , helm_repository_url : str ) -> None :
45- """Add the agentex helm repository if not already added"""
45+ """Add the agentex helm repository if not already added (classic mode) """
4646 try :
4747 # Check if repo already exists
4848 result = subprocess .run (["helm" , "repo" , "list" ], capture_output = True , text = True , check = True )
@@ -69,6 +69,157 @@ def add_helm_repo(helm_repository_name: str, helm_repository_url: str) -> None:
6969 raise HelmError (f"Failed to add helm repository: { e } " ) from e
7070
7171
72+ def login_to_gar_registry (oci_registry : str ) -> None :
73+ """Auto-login to Google Artifact Registry using gcloud credentials.
74+
75+ Args:
76+ oci_registry: The GAR registry URL (e.g., 'us-west1-docker.pkg.dev/project-id/repo-name')
77+ """
78+ try :
79+ # Extract the registry host (e.g., 'us-west1-docker.pkg.dev')
80+ registry_host = oci_registry .split ("/" )[0 ]
81+
82+ # Get access token from gcloud
83+ console .print (f"[blue]ℹ[/blue] Authenticating with Google Artifact Registry: { registry_host } " )
84+ result = subprocess .run (
85+ ["gcloud" , "auth" , "print-access-token" ],
86+ capture_output = True ,
87+ text = True ,
88+ check = True ,
89+ )
90+ access_token = result .stdout .strip ()
91+
92+ # Login to helm registry using the access token
93+ subprocess .run (
94+ [
95+ "helm" ,
96+ "registry" ,
97+ "login" ,
98+ registry_host ,
99+ "--username" ,
100+ "oauth2accesstoken" ,
101+ "--password-stdin" ,
102+ ],
103+ input = access_token ,
104+ text = True ,
105+ check = True ,
106+ )
107+ console .print (f"[green]✓[/green] Authenticated with GAR: { registry_host } " )
108+
109+ except subprocess .CalledProcessError as e :
110+ raise HelmError (
111+ f"Failed to authenticate with Google Artifact Registry: { e } \n "
112+ "Ensure you are logged in with 'gcloud auth login' and have access to the registry."
113+ ) from e
114+ except FileNotFoundError :
115+ raise HelmError (
116+ "gcloud CLI not found. Please install the Google Cloud SDK: "
117+ "https://cloud.google.com/sdk/docs/install"
118+ ) from None
119+
120+
121+ def get_latest_gar_chart_version (oci_registry : str , chart_name : str = "agentex-agent" ) -> str :
122+ """Fetch the latest version of a Helm chart from Google Artifact Registry.
123+
124+ GAR stores Helm chart versions as tags (e.g., '0.1.9'), not as versions (which are SHA digests).
125+ This function lists tags sorted by creation time and returns the most recent one.
126+
127+ Args:
128+ oci_registry: The GAR registry URL (e.g., 'us-west1-docker.pkg.dev/project-id/repo-name')
129+ chart_name: Name of the Helm chart
130+
131+ Returns:
132+ The latest version string (e.g., '0.2.0')
133+ """
134+ try :
135+ # Parse the OCI registry URL to extract components
136+ # Format: REGION-docker.pkg.dev/PROJECT/REPOSITORY
137+ parts = oci_registry .split ("/" )
138+ if len (parts ) < 3 :
139+ raise HelmError (
140+ f"Invalid OCI registry format: { oci_registry } . "
141+ "Expected format: REGION-docker.pkg.dev/PROJECT/REPOSITORY"
142+ )
143+
144+ location = parts [0 ].replace ("-docker.pkg.dev" , "" )
145+ project = parts [1 ]
146+ repository = parts [2 ]
147+
148+ console .print (f"[blue]ℹ[/blue] Fetching latest chart version from GAR..." )
149+
150+ # Use gcloud to list tags (not versions - versions are SHA digests)
151+ # Tags contain the semantic versions like '0.1.9'
152+ result = subprocess .run (
153+ [
154+ "gcloud" ,
155+ "artifacts" ,
156+ "tags" ,
157+ "list" ,
158+ f"--repository={ repository } " ,
159+ f"--location={ location } " ,
160+ f"--project={ project } " ,
161+ f"--package={ chart_name } " ,
162+ "--sort-by=~createTime" ,
163+ "--limit=1" ,
164+ "--format=value(tag)" ,
165+ ],
166+ capture_output = True ,
167+ text = True ,
168+ check = True ,
169+ )
170+
171+ output = result .stdout .strip ()
172+ if not output :
173+ raise HelmError (
174+ f"No tags found for chart '{ chart_name } ' in { oci_registry } "
175+ )
176+
177+ # The output is the tag name (semantic version)
178+ version = output
179+ console .print (f"[green]✓[/green] Latest chart version: { version } " )
180+ return version
181+
182+ except subprocess .CalledProcessError as e :
183+ raise HelmError (
184+ f"Failed to fetch chart tags from GAR: { e .stderr } \n "
185+ "Ensure you have access to the Artifact Registry."
186+ ) from e
187+ except FileNotFoundError :
188+ raise HelmError (
189+ "gcloud CLI not found. Please install the Google Cloud SDK: "
190+ "https://cloud.google.com/sdk/docs/install"
191+ ) from None
192+
193+
194+ def get_chart_reference (
195+ use_oci : bool ,
196+ helm_repository_name : str | None = None ,
197+ oci_registry : str | None = None ,
198+ chart_name : str = "agentex-agent" ,
199+ ) -> str :
200+ """Get the chart reference based on the deployment mode.
201+
202+ Args:
203+ use_oci: Whether to use OCI registry mode
204+ helm_repository_name: Name of the classic helm repo (required if use_oci=False)
205+ oci_registry: OCI registry URL (required if use_oci=True)
206+ chart_name: Name of the helm chart
207+
208+ Returns:
209+ Chart reference string for helm install/upgrade commands
210+ """
211+ if use_oci :
212+ if not oci_registry :
213+ raise HelmError ("OCI registry URL is required for OCI mode" )
214+ # OCI format: oci://registry/path/chart-name
215+ return f"oci://{ oci_registry } /{ chart_name } "
216+ else :
217+ if not helm_repository_name :
218+ raise HelmError ("Helm repository name is required for classic mode" )
219+ # Classic format: repo-name/chart-name
220+ return f"{ helm_repository_name } /{ chart_name } "
221+
222+
72223def convert_env_vars_dict_to_list (env_vars : dict [str , str ]) -> list [dict [str , str ]]:
73224 """Convert a dictionary of environment variables to a list of dictionaries"""
74225 return [{"name" : key , "value" : value } for key , value in env_vars .items ()]
@@ -281,8 +432,18 @@ def deploy_agent(
281432 namespace : str ,
282433 deploy_overrides : InputDeployOverrides ,
283434 environment_name : str | None = None ,
435+ use_latest_chart : bool = False ,
284436) -> None :
285- """Deploy an agent using helm"""
437+ """Deploy an agent using helm
438+
439+ Args:
440+ manifest_path: Path to the agent manifest file
441+ cluster_name: Target Kubernetes cluster name
442+ namespace: Kubernetes namespace to deploy to
443+ deploy_overrides: Image repository/tag overrides
444+ environment_name: Environment name from environments.yaml
445+ use_latest_chart: If True, fetch and use the latest chart version from OCI registry (OCI mode only)
446+ """
286447
287448 # Validate prerequisites
288449 if not check_helm_installed ():
@@ -304,14 +465,46 @@ def deploy_agent(
304465 else :
305466 console .print (f"[yellow]⚠[/yellow] No environments.yaml found, skipping environment-specific config" )
306467
307- if agent_env_config :
308- helm_repository_name = agent_env_config .helm_repository_name
309- helm_repository_url = agent_env_config .helm_repository_url
468+ # Determine if using OCI or classic helm repo mode
469+ use_oci = agent_env_config .uses_oci_registry () if agent_env_config else False
470+ helm_repository_name : str | None = None
471+ oci_registry : str | None = None
472+
473+ if use_oci :
474+ oci_registry = agent_env_config .helm_oci_registry # type: ignore[union-attr]
475+ console .print (f"[blue]ℹ[/blue] Using OCI Helm registry: { oci_registry } " )
476+ login_to_gar_registry (oci_registry ) # type: ignore[arg-type]
477+ else :
478+ if agent_env_config :
479+ helm_repository_name = agent_env_config .helm_repository_name
480+ helm_repository_url = agent_env_config .helm_repository_url
481+ else :
482+ helm_repository_name = "scale-egp"
483+ helm_repository_url = "https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts"
484+ # Add helm repository/update (classic mode only)
485+ add_helm_repo (helm_repository_name , helm_repository_url )
486+
487+ # Get the chart reference based on deployment mode
488+ chart_reference = get_chart_reference (
489+ use_oci = use_oci ,
490+ helm_repository_name = helm_repository_name ,
491+ oci_registry = oci_registry ,
492+ )
493+
494+ # Determine chart version
495+ # Priority: --use-latest-chart > env config > default
496+ if use_latest_chart :
497+ if not use_oci :
498+ console .print ("[yellow]⚠[/yellow] --use-latest-chart only works with OCI registries, using default version" )
499+ chart_version = DEFAULT_HELM_CHART_VERSION
500+ else :
501+ chart_version = get_latest_gar_chart_version (oci_registry ) # type: ignore[arg-type]
502+ elif agent_env_config and agent_env_config .helm_chart_version :
503+ chart_version = agent_env_config .helm_chart_version
310504 else :
311- helm_repository_name = "scale-egp"
312- helm_repository_url = "https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts"
313- # Add helm repository/update
314- add_helm_repo (helm_repository_name , helm_repository_url )
505+ chart_version = DEFAULT_HELM_CHART_VERSION
506+
507+ console .print (f"[blue]ℹ[/blue] Using Helm chart version: { chart_version } " )
315508
316509 # Merge configurations
317510 helm_values = merge_deployment_configs (manifest , agent_env_config , deploy_overrides , manifest_path )
@@ -341,9 +534,9 @@ def deploy_agent(
341534 "helm" ,
342535 "upgrade" ,
343536 release_name ,
344- f" { helm_repository_name } /agentex-agent" ,
537+ chart_reference ,
345538 "--version" ,
346- AGENTEX_AGENTS_HELM_CHART_VERSION ,
539+ chart_version ,
347540 "-f" ,
348541 values_file ,
349542 "-n" ,
@@ -363,9 +556,9 @@ def deploy_agent(
363556 "helm" ,
364557 "install" ,
365558 release_name ,
366- f" { helm_repository_name } /agentex-agent" ,
559+ chart_reference ,
367560 "--version" ,
368- AGENTEX_AGENTS_HELM_CHART_VERSION ,
561+ chart_version ,
369562 "-f" ,
370563 values_file ,
371564 "-n" ,
0 commit comments