Skip to content

Commit f2e47c5

Browse files
committed
adding the multiple env mapping feature
1 parent b1d05a6 commit f2e47c5

File tree

5 files changed

+404
-77
lines changed

5 files changed

+404
-77
lines changed

src/agentex/lib/cli/handlers/deploy_handlers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,8 @@ def deploy_agent(
292292
check_and_switch_cluster_context(cluster_name)
293293

294294
manifest = AgentManifest.from_yaml(file_path=manifest_path)
295-
296-
# Load agent environment configuration
297295
agent_env_config = None
296+
# Load agent environment configuration
298297
if environment_name:
299298
manifest_dir = Path(manifest_path).parent
300299
environments_config = manifest.load_environments_config(manifest_dir)

src/agentex/lib/sdk/config/environment_config.py

Lines changed: 108 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from __future__ import annotations
99

10-
from typing import Any, Dict, override
10+
from typing import Any, Dict, override, List
1111
from pathlib import Path
1212

1313
import yaml
@@ -18,13 +18,12 @@
1818

1919
class AgentAuthConfig(BaseModel):
2020
"""Authentication configuration for an agent in a specific environment."""
21-
21+
2222
principal: Dict[str, Any] = Field(
23-
...,
24-
description="Principal configuration for agent authorization and registration"
23+
..., description="Principal configuration for agent authorization and registration"
2524
)
26-
27-
@field_validator('principal')
25+
26+
@field_validator("principal")
2827
@classmethod
2928
def validate_principal_required_fields(cls, v: Any) -> Dict[str, Any]:
3029
"""Ensure principal has required fields for agent registration."""
@@ -35,142 +34,177 @@ def validate_principal_required_fields(cls, v: Any) -> Dict[str, Any]:
3534

3635
class AgentKubernetesConfig(BaseModel):
3736
"""Kubernetes configuration for an agent in a specific environment."""
38-
39-
namespace: str = Field(
40-
...,
41-
description="Kubernetes namespace where the agent will be deployed"
42-
)
43-
44-
@field_validator('namespace')
37+
38+
namespace: str = Field(..., description="Kubernetes namespace where the agent will be deployed")
39+
40+
@field_validator("namespace")
4541
@classmethod
4642
def validate_namespace_format(cls, v: str) -> str:
4743
"""Ensure namespace follows Kubernetes naming conventions."""
4844
if not v or not v.strip():
4945
raise ValueError("Namespace cannot be empty")
50-
46+
5147
# Basic Kubernetes namespace validation
5248
namespace = v.strip().lower()
53-
if not namespace.replace('-', '').replace('.', '').isalnum():
54-
raise ValueError(
55-
f"Namespace '{v}' must contain only lowercase letters, numbers, "
56-
"hyphens, and periods"
57-
)
58-
49+
if not namespace.replace("-", "").replace(".", "").isalnum():
50+
raise ValueError(f"Namespace '{v}' must contain only lowercase letters, numbers, hyphens, and periods")
51+
5952
if len(namespace) > 63:
6053
raise ValueError(f"Namespace '{v}' cannot exceed 63 characters")
61-
54+
6255
return namespace
6356

6457

6558
class AgentEnvironmentConfig(BaseModel):
6659
"""Complete configuration for an agent in a specific environment."""
67-
68-
kubernetes: AgentKubernetesConfig | None = Field(
69-
default=None,
70-
description="Kubernetes deployment configuration"
71-
)
72-
auth: AgentAuthConfig = Field(
73-
...,
74-
description="Authentication and authorization configuration"
75-
)
76-
helm_repository_name: str = Field(
77-
default="scale-egp",
78-
description="Helm repository name for the environment"
60+
61+
kubernetes: AgentKubernetesConfig | None = Field(default=None, description="Kubernetes deployment configuration")
62+
environment: str | None = Field(
63+
default=None,
64+
description="The environment keyword that this specific environment maps to: either dev, staging, prod",
7965
)
66+
auth: AgentAuthConfig = Field(..., description="Authentication and authorization configuration")
67+
helm_repository_name: str = Field(default="scale-egp", description="Helm repository name for the environment")
8068
helm_repository_url: str = Field(
81-
default="https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts",
82-
description="Helm repository url for the environment"
69+
default="https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts",
70+
description="Helm repository url for the environment",
8371
)
8472
helm_overrides: Dict[str, Any] = Field(
85-
default_factory=dict,
86-
description="Helm chart value overrides for environment-specific tuning"
73+
default_factory=dict, description="Helm chart value overrides for environment-specific tuning"
8774
)
8875

8976

9077
class AgentEnvironmentsConfig(UtilsBaseModel):
9178
"""All environment configurations for an agent."""
92-
93-
schema_version: str = Field(
94-
default="v1",
95-
description="Schema version for validation and compatibility"
96-
)
79+
80+
schema_version: str = Field(default="v1", description="Schema version for validation and compatibility")
9781
environments: Dict[str, AgentEnvironmentConfig] = Field(
98-
...,
99-
description="Environment-specific configurations (dev, prod, etc.)"
82+
..., description="Environment-specific configurations (dev, prod, etc.)"
10083
)
101-
102-
@field_validator('schema_version')
84+
85+
@field_validator("schema_version")
10386
@classmethod
10487
def validate_schema_version(cls, v: str) -> str:
10588
"""Ensure schema version is supported."""
106-
supported_versions = ['v1']
89+
supported_versions = ["v1"]
10790
if v not in supported_versions:
108-
raise ValueError(
109-
f"Schema version '{v}' not supported. "
110-
f"Supported versions: {', '.join(supported_versions)}"
111-
)
91+
raise ValueError(f"Schema version '{v}' not supported. Supported versions: {', '.join(supported_versions)}")
11292
return v
113-
114-
@field_validator('environments')
93+
94+
@field_validator("environments")
11595
@classmethod
11696
def validate_environments_not_empty(cls, v: Dict[str, AgentEnvironmentConfig]) -> Dict[str, AgentEnvironmentConfig]:
11797
"""Ensure at least one environment is defined."""
11898
if not v:
11999
raise ValueError("At least one environment must be defined")
120100
return v
121-
101+
122102
def get_config_for_env(self, env_name: str) -> AgentEnvironmentConfig:
123103
"""Get configuration for a specific environment.
124-
104+
125105
Args:
126106
env_name: Name of the environment (e.g., 'dev', 'prod')
127-
107+
128108
Returns:
129109
AgentEnvironmentConfig for the specified environment
130-
110+
131111
Raises:
132112
ValueError: If environment is not found
133113
"""
134114
if env_name not in self.environments:
135-
available_envs = ', '.join(self.environments.keys())
115+
available_envs = ", ".join(self.environments.keys())
136116
raise ValueError(
137-
f"Environment '{env_name}' not found in environments.yaml. "
138-
f"Available environments: {available_envs}"
117+
f"Environment '{env_name}' not found in environments.yaml. Available environments: {available_envs}"
139118
)
140119
return self.environments[env_name]
141-
120+
121+
def get_configs_for_env(self, env: str) -> List[AgentEnvironmentConfig]:
122+
"""Get configuration for a specific environment based on the expected mapping.
123+
The environment is either:
124+
1. explicitly specified like so using a key-map in the environments conifg:
125+
environments:
126+
dev-aws:
127+
environment: "dev"
128+
kubernetes:
129+
namespace: "sgp-000-hello-acp"
130+
auth:
131+
principal:
132+
user_id: 73d0c8bd-4726-434c-9686-eb627d89f078
133+
account_id: 6887f093600ecd59bbbd3095
134+
helm_overrides:
135+
136+
or: it it can be defined at the top level:
137+
dev:
138+
kubernetes:
139+
namespace: "sgp-000-hello-acp"
140+
auth:
141+
principal:
142+
user_id: 73d0c8bd-4726-434c-9686-eb627d89f078
143+
account_id: 6887f093600ecd59bbbd3095
144+
helm_overrides:
145+
146+
if its not explicitly defined, we assumed the level key (environment name) is the environment
147+
Args:
148+
env_name: Name of the environment (e.g., 'dev', 'prod')
149+
150+
Returns:
151+
AgentEnvironmentConfig for the specified environment
152+
153+
Raises:
154+
ValueError: If environment is not found
155+
"""
156+
envs_to_deploy = []
157+
if env in self.environments:
158+
# this supports if the top-level key is just "dev, staging, etc" and matches
159+
# the environment name exactly without any explicit mapping
160+
envs_to_deploy.append(self.environments[env])
161+
162+
for _, config in self.environments.items():
163+
if config.environment == env:
164+
envs_to_deploy.append(config)
165+
166+
if len(envs_to_deploy) == 0:
167+
available_envs = [env.environment for _, env in self.environments.items()] + [
168+
env_name for env_name in self.environments
169+
]
170+
raise ValueError(
171+
f"Environment '{envs_to_deploy}' not found in environments.yaml. Available environments: {available_envs}"
172+
)
173+
174+
return envs_to_deploy
175+
142176
def list_environments(self) -> list[str]:
143177
"""Get list of all configured environment names."""
144178
return list(self.environments.keys())
145-
179+
146180
@classmethod
147181
@override
148182
def from_yaml(cls, file_path: str) -> "AgentEnvironmentsConfig":
149183
"""Load configuration from environments.yaml file.
150-
184+
151185
Args:
152186
file_path: Path to environments.yaml file
153-
187+
154188
Returns:
155189
Parsed and validated AgentEnvironmentsConfig
156-
190+
157191
Raises:
158192
FileNotFoundError: If file doesn't exist
159193
ValueError: If file is invalid or doesn't validate
160194
"""
161195
path = Path(file_path)
162196
if not path.exists():
163197
raise FileNotFoundError(f"environments.yaml not found: {file_path}")
164-
198+
165199
try:
166-
with open(path, 'r') as f:
200+
with open(path, "r") as f:
167201
data = yaml.safe_load(f)
168-
202+
169203
if not data:
170204
raise ValueError("environments.yaml file is empty")
171-
205+
172206
return cls.model_validate(data)
173-
207+
174208
except yaml.YAMLError as e:
175209
raise ValueError(f"Invalid YAML format in {file_path}: {e}") from e
176210
except Exception as e:
@@ -179,18 +213,18 @@ def from_yaml(cls, file_path: str) -> "AgentEnvironmentsConfig":
179213

180214
def load_environments_config_from_manifest_dir(manifest_dir: Path) -> AgentEnvironmentsConfig | None:
181215
"""Helper function to load environments.yaml from same directory as manifest.yaml.
182-
216+
183217
Args:
184218
manifest_dir: Directory containing manifest.yaml
185-
219+
186220
Returns:
187221
AgentEnvironmentsConfig if environments.yaml exists, None otherwise
188-
222+
189223
Raises:
190224
ValueError: If environments.yaml exists but is invalid
191225
"""
192226
environments_file = manifest_dir / "environments.yaml"
193227
if not environments_file.exists():
194228
return None
195-
229+
196230
return AgentEnvironmentsConfig.from_yaml(str(environments_file))

tests/lib/cli/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)