1717from agentex .lib .cli .utils .kubectl_utils import check_and_switch_cluster_context
1818from agentex .lib .sdk .config .agent_config import AgentConfig
1919from agentex .lib .sdk .config .agent_manifest import AgentManifest
20- from agentex .lib .sdk .config .environment_config import AgentEnvironmentConfig
20+ from agentex .lib .sdk .config .environment_config import OciRegistryConfig , AgentEnvironmentConfig
2121
2222logger = make_logger (__name__ )
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,166 @@ 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: https://cloud.google.com/sdk/docs/install"
117+ ) from None
118+
119+
120+ def get_latest_gar_chart_version (oci_registry : str , chart_name : str = "agentex-agent" ) -> str :
121+ """Fetch the latest version of a Helm chart from Google Artifact Registry.
122+
123+ GAR stores Helm chart versions as tags (e.g., '0.1.9'), not as versions (which are SHA digests).
124+ This function lists tags sorted by creation time and returns the most recent one.
125+
126+ Args:
127+ oci_registry: The GAR registry URL (e.g., 'us-west1-docker.pkg.dev/project-id/repo-name')
128+ chart_name: Name of the Helm chart
129+
130+ Returns:
131+ The latest version string (e.g., '0.2.0')
132+ """
133+ try :
134+ # Parse the OCI registry URL to extract components
135+ # Format: REGION-docker.pkg.dev/PROJECT/REPOSITORY
136+ parts = oci_registry .split ("/" )
137+ if len (parts ) < 3 :
138+ raise HelmError (
139+ f"Invalid OCI registry format: { oci_registry } . "
140+ "Expected format: REGION-docker.pkg.dev/PROJECT/REPOSITORY"
141+ )
142+
143+ location = parts [0 ].replace ("-docker.pkg.dev" , "" )
144+ project = parts [1 ]
145+ repository = parts [2 ]
146+
147+ console .print (f"[blue]ℹ[/blue] Fetching latest chart version from GAR..." )
148+
149+ # Use gcloud to list tags (not versions - versions are SHA digests)
150+ # Tags contain the semantic versions like '0.1.9'
151+ result = subprocess .run (
152+ [
153+ "gcloud" ,
154+ "artifacts" ,
155+ "tags" ,
156+ "list" ,
157+ f"--repository={ repository } " ,
158+ f"--location={ location } " ,
159+ f"--project={ project } " ,
160+ f"--package={ chart_name } " ,
161+ "--sort-by=~createTime" ,
162+ "--limit=1" ,
163+ "--format=value(tag)" ,
164+ ],
165+ capture_output = True ,
166+ text = True ,
167+ check = True ,
168+ )
169+
170+ output = result .stdout .strip ()
171+ if not output :
172+ raise HelmError (f"No tags found for chart '{ chart_name } ' in { oci_registry } " )
173+
174+ # The output is the tag name (semantic version)
175+ version = output
176+ console .print (f"[green]✓[/green] Latest chart version: { version } " )
177+ return version
178+
179+ except subprocess .CalledProcessError as e :
180+ raise HelmError (
181+ f"Failed to fetch chart tags from GAR: { e .stderr } \n Ensure you have access to the Artifact Registry."
182+ ) from e
183+ except FileNotFoundError :
184+ raise HelmError (
185+ "gcloud CLI not found. Please install the Google Cloud SDK: https://cloud.google.com/sdk/docs/install"
186+ ) from None
187+
188+
189+ def resolve_chart (
190+ oci_registry : OciRegistryConfig | None ,
191+ helm_repository_name : str | None ,
192+ use_latest_chart : bool ,
193+ chart_name : str = "agentex-agent" ,
194+ ) -> tuple [str , str ]:
195+ """Resolve the chart reference and version based on the deployment mode.
196+
197+ For OCI mode, builds an oci:// reference and resolves version from:
198+ --use-latest-chart (GAR only) > oci_registry.chart_version > default.
199+ For classic mode, builds a repo/chart reference and uses default version.
200+
201+ Returns:
202+ (chart_reference, chart_version)
203+ """
204+ if oci_registry :
205+ chart_reference = f"oci://{ oci_registry .url } /{ chart_name } "
206+
207+ if use_latest_chart :
208+ if oci_registry .provider != "gar" :
209+ console .print (
210+ "[yellow]⚠[/yellow] --use-latest-chart only works with GAR provider (provider: gar), using default version"
211+ )
212+ chart_version = DEFAULT_HELM_CHART_VERSION
213+ else :
214+ chart_version = get_latest_gar_chart_version (oci_registry .url )
215+ elif oci_registry .chart_version :
216+ chart_version = oci_registry .chart_version
217+ else :
218+ chart_version = DEFAULT_HELM_CHART_VERSION
219+ else :
220+ if not helm_repository_name :
221+ raise HelmError ("Helm repository name is required for classic mode" )
222+ chart_reference = f"{ helm_repository_name } /{ chart_name } "
223+
224+ if use_latest_chart :
225+ console .print ("[yellow]⚠[/yellow] --use-latest-chart only works with OCI registries, using default version" )
226+ chart_version = DEFAULT_HELM_CHART_VERSION
227+
228+ console .print (f"[blue]ℹ[/blue] Using Helm chart version: { chart_version } " )
229+ return chart_reference , chart_version
230+
231+
72232def convert_env_vars_dict_to_list (env_vars : dict [str , str ]) -> list [dict [str , str ]]:
73233 """Convert a dictionary of environment variables to a list of dictionaries"""
74234 return [{"name" : key , "value" : value } for key , value in env_vars .items ()]
@@ -281,8 +441,18 @@ def deploy_agent(
281441 namespace : str ,
282442 deploy_overrides : InputDeployOverrides ,
283443 environment_name : str | None = None ,
444+ use_latest_chart : bool = False ,
284445) -> None :
285- """Deploy an agent using helm"""
446+ """Deploy an agent using helm
447+
448+ Args:
449+ manifest_path: Path to the agent manifest file
450+ cluster_name: Target Kubernetes cluster name
451+ namespace: Kubernetes namespace to deploy to
452+ deploy_overrides: Image repository/tag overrides
453+ environment_name: Environment name from environments.yaml
454+ use_latest_chart: If True, fetch and use the latest chart version from OCI registry (OCI mode only)
455+ """
286456
287457 # Validate prerequisites
288458 if not check_helm_installed ():
@@ -304,14 +474,36 @@ def deploy_agent(
304474 else :
305475 console .print (f"[yellow]⚠[/yellow] No environments.yaml found, skipping environment-specific config" )
306476
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
477+ # Determine deployment mode: OCI registry or classic helm repo
478+ oci_registry = agent_env_config .oci_registry if agent_env_config else None
479+ helm_repository_name : str | None = None
480+
481+ if oci_registry :
482+ console .print (f"[blue]ℹ[/blue] Using OCI Helm registry: { oci_registry .url } " )
483+
484+ # Only auto-authenticate for GAR provider
485+ if oci_registry .provider == "gar" :
486+ login_to_gar_registry (oci_registry .url )
487+ else :
488+ console .print (
489+ "[blue]ℹ[/blue] Skipping auto-authentication (no provider specified, assuming already authenticated)"
490+ )
310491 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 )
492+ if agent_env_config :
493+ helm_repository_name = agent_env_config .helm_repository_name
494+ helm_repository_url = agent_env_config .helm_repository_url
495+ else :
496+ helm_repository_name = "scale-egp"
497+ helm_repository_url = "https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts"
498+ # Add helm repository/update (classic mode only)
499+ add_helm_repo (helm_repository_name , helm_repository_url )
500+
501+ # Resolve chart reference and version in one step
502+ chart_reference , chart_version = resolve_chart (
503+ oci_registry = oci_registry ,
504+ helm_repository_name = helm_repository_name ,
505+ use_latest_chart = use_latest_chart ,
506+ )
315507
316508 # Merge configurations
317509 helm_values = merge_deployment_configs (manifest , agent_env_config , deploy_overrides , manifest_path )
@@ -341,9 +533,9 @@ def deploy_agent(
341533 "helm" ,
342534 "upgrade" ,
343535 release_name ,
344- f" { helm_repository_name } /agentex-agent" ,
536+ chart_reference ,
345537 "--version" ,
346- AGENTEX_AGENTS_HELM_CHART_VERSION ,
538+ chart_version ,
347539 "-f" ,
348540 values_file ,
349541 "-n" ,
@@ -363,9 +555,9 @@ def deploy_agent(
363555 "helm" ,
364556 "install" ,
365557 release_name ,
366- f" { helm_repository_name } /agentex-agent" ,
558+ chart_reference ,
367559 "--version" ,
368- AGENTEX_AGENTS_HELM_CHART_VERSION ,
560+ chart_version ,
369561 "-f" ,
370562 values_file ,
371563 "-n" ,
0 commit comments