44This module provides validation functions for agent configurations,
55with clear error messages and best practices enforcement.
66"""
7+
78from __future__ import annotations
89
910from typing import Any , Dict , List , Optional
1718
1819class ConfigValidationError (Exception ):
1920 """Exception raised when configuration validation fails."""
20-
21+
2122 def __init__ (self , message : str , file_path : Optional [str ] = None ):
2223 self .file_path = file_path
2324 super ().__init__ (message )
2425
2526
2627class EnvironmentsValidationError (ConfigValidationError ):
2728 """Exception raised when environments.yaml validation fails."""
29+
2830 pass
2931
3032
3133def validate_environments_config (
32- environments_config : AgentEnvironmentsConfig ,
33- required_environments : Optional [List [str ]] = None
34+ environments_config : AgentEnvironmentsConfig , required_environments : Optional [List [str ]] = None
3435) -> None :
3536 """
3637 Validate environments configuration with comprehensive checks.
37-
38+
3839 Args:
3940 environments_config: The loaded environments configuration
4041 required_environments: List of environment names that must be present
41-
42+
4243 Raises:
4344 EnvironmentsValidationError: If validation fails
4445 """
@@ -48,65 +49,69 @@ def validate_environments_config(
4849 for env_name in required_environments :
4950 if env_name not in environments_config .environments :
5051 missing_envs .append (env_name )
51-
52+
5253 if missing_envs :
5354 available_envs = list (environments_config .environments .keys ())
5455 raise EnvironmentsValidationError (
5556 f"Missing required environments: { ', ' .join (missing_envs )} . "
5657 f"Available environments: { ', ' .join (available_envs )} "
5758 )
58-
59+
60+ # if environment mappings are set, you cannot have a top-level env_name that maps to an `environment: value`
61+ # and another environment that has the mapping i.e.
62+ # enviorments:
63+ # dev:
64+ # ....
65+ # dev1:
66+ # environment: dev
67+ # this is invalid because its unclear if "dev" refers to just that top-level environment or the mapping
68+ #
5969 # Validate each environment configuration
6070 for env_name , env_config in environments_config .environments .items ():
6171 try :
6272 _validate_single_environment_config (env_name , env_config )
6373 except Exception as e :
64- raise EnvironmentsValidationError (
65- f"Environment '{ env_name } ' configuration error: { str (e )} "
66- ) from e
74+ raise EnvironmentsValidationError (f"Environment '{ env_name } ' configuration error: { str (e )} " ) from e
6775
6876
6977def _validate_single_environment_config (env_name : str , env_config : AgentEnvironmentConfig ) -> None :
7078 """
7179 Validate a single environment configuration.
72-
80+
7381 Args:
7482 env_name: Name of the environment
7583 env_config: AgentEnvironmentConfig instance
76-
84+
7785 Raises:
7886 ValueError: If validation fails
7987 """
8088 # Validate namespace naming conventions if kubernetes config exists
8189 if env_config .kubernetes and env_config .kubernetes .namespace :
8290 namespace = env_config .kubernetes .namespace
83-
91+
8492 # Check for common namespace naming issues
8593 if namespace != namespace .lower ():
8694 logger .warning (
87- f"Namespace '{ namespace } ' contains uppercase letters. "
88- "Kubernetes namespaces should be lowercase."
95+ f"Namespace '{ namespace } ' contains uppercase letters. Kubernetes namespaces should be lowercase."
8996 )
90-
91- if namespace .startswith ('-' ) or namespace .endswith ('-' ):
92- raise ValueError (
93- f"Namespace '{ namespace } ' cannot start or end with hyphens"
94- )
95-
97+
98+ if namespace .startswith ("-" ) or namespace .endswith ("-" ):
99+ raise ValueError (f"Namespace '{ namespace } ' cannot start or end with hyphens" )
100+
96101 # Validate auth principal
97102 principal = env_config .auth .principal
98- if not principal .get (' user_id' ):
103+ if not principal .get (" user_id" ):
99104 raise ValueError ("Auth principal must contain non-empty 'user_id'" )
100-
105+
101106 # Check for environment-specific user_id patterns
102- user_id = principal [' user_id' ]
107+ user_id = principal [" user_id" ]
103108 if isinstance (user_id , str ):
104- if not any (env_name .lower () in user_id .lower () for env_name in [' dev' , ' prod' , ' staging' , env_name ]):
109+ if not any (env_name .lower () in user_id .lower () for env_name in [" dev" , " prod" , " staging" , env_name ]):
105110 logger .warning (
106111 f"User ID '{ user_id } ' doesn't contain environment indicator. "
107112 f"Consider including '{ env_name } ' in the user_id for clarity."
108113 )
109-
114+
110115 # Validate helm overrides if present
111116 if env_config .helm_overrides :
112117 _validate_helm_overrides (env_config .helm_overrides )
@@ -115,26 +120,26 @@ def _validate_single_environment_config(env_name: str, env_config: AgentEnvironm
115120def _validate_helm_overrides (helm_overrides : Dict [str , Any ]) -> None :
116121 """
117122 Validate helm override configuration.
118-
123+
119124 Args:
120125 helm_overrides: Dictionary of helm overrides
121-
126+
122127 Raises:
123128 ValueError: If validation fails
124129 """
125130 # Check for common helm override issues
126- if ' resources' in helm_overrides :
127- resources = helm_overrides [' resources' ]
131+ if " resources" in helm_overrides :
132+ resources = helm_overrides [" resources" ]
128133 if isinstance (resources , dict ):
129134 # Validate resource format
130- if ' requests' in resources or ' limits' in resources :
131- for resource_type in [' requests' , ' limits' ]:
135+ if " requests" in resources or " limits" in resources :
136+ for resource_type in [" requests" , " limits" ]:
132137 if resource_type in resources :
133138 resource_config : Any = resources [resource_type ]
134139 if isinstance (resource_config , dict ):
135140 # Check for valid resource specifications
136141 for key , value in resource_config .items ():
137- if key in [' cpu' , ' memory' ] and not isinstance (value , str ):
142+ if key in [" cpu" , " memory" ] and not isinstance (value , str ):
138143 logger .warning (
139144 f"Resource { key } should be a string (e.g., '500m', '1Gi'), "
140145 f"got { type (value ).__name__ } : { value } "
@@ -144,13 +149,13 @@ def _validate_helm_overrides(helm_overrides: Dict[str, Any]) -> None:
144149def validate_environments_yaml_file (file_path : str ) -> AgentEnvironmentsConfig :
145150 """
146151 Load and validate environments.yaml file.
147-
152+
148153 Args:
149154 file_path: Path to environments.yaml file
150-
155+
151156 Returns:
152157 Validated AgentEnvironmentsConfig
153-
158+
154159 Raises:
155160 EnvironmentsValidationError: If file is invalid
156161 """
@@ -164,66 +169,59 @@ def validate_environments_yaml_file(file_path: str) -> AgentEnvironmentsConfig:
164169 "📋 Why required:\n "
165170 " Environment-specific settings (auth, namespace, resources)\n "
166171 " must be separated from global manifest for proper isolation." ,
167- file_path = file_path
172+ file_path = file_path ,
168173 ) from None
169174 except Exception as e :
170- raise EnvironmentsValidationError (
171- f"Invalid environments.yaml file: { str (e )} " ,
172- file_path = file_path
173- ) from e
175+ raise EnvironmentsValidationError (f"Invalid environments.yaml file: { str (e )} " , file_path = file_path ) from e
174176
175177
176178def validate_manifest_and_environments (
177- manifest_path : str ,
178- required_environment : Optional [str ] = None
179+ manifest_path : str , required_environment : Optional [str ] = None
179180) -> tuple [str , AgentEnvironmentsConfig ]:
180181 """
181182 Validate both manifest.yaml and environments.yaml files together.
182-
183+
183184 Args:
184185 manifest_path: Path to manifest.yaml file
185186 required_environment: Specific environment that must be present
186-
187+
187188 Returns:
188189 Tuple of (manifest_path, environments_config)
189-
190+
190191 Raises:
191192 ConfigValidationError: If validation fails
192193 """
193194 manifest_file = Path (manifest_path )
194195 if not manifest_file .exists ():
195196 raise ConfigValidationError (f"Manifest file not found: { manifest_path } " )
196-
197+
197198 # Look for environments.yaml in same directory
198199 environments_file = manifest_file .parent / "environments.yaml"
199200 environments_config = validate_environments_yaml_file (str (environments_file ))
200-
201+
201202 # Validate specific environment if requested
202203 if required_environment :
203- validate_environments_config (
204- environments_config ,
205- required_environments = [required_environment ]
206- )
207-
204+ validate_environments_config (environments_config , required_environments = [required_environment ])
205+
208206 return manifest_path , environments_config
209207
210208
211209def generate_helpful_error_message (error : Exception , context : str = "" ) -> str :
212210 """
213211 Generate helpful error message with troubleshooting tips.
214-
212+
215213 Args:
216214 error: The original exception
217215 context: Additional context about where the error occurred
218-
216+
219217 Returns:
220218 Formatted error message with troubleshooting tips
221219 """
222220 base_msg = str (error )
223-
221+
224222 if context :
225223 base_msg = f"{ context } : { base_msg } "
226-
224+
227225 # Add troubleshooting tips based on error type
228226 if isinstance (error , FileNotFoundError ):
229227 if "environments.yaml" in base_msg :
@@ -246,5 +244,5 @@ def generate_helpful_error_message(error: Exception, context: str = "") -> str:
246244 "- Include team and environment (e.g., 'team-dev-agent')\n "
247245 "- Keep under 63 characters"
248246 )
249-
247+
250248 return base_msg
0 commit comments