cdk-factory 0.16.15__py3-none-any.whl → 0.20.0__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.
Potentially problematic release.
This version of cdk-factory might be problematic. Click here for more details.
- cdk_factory/configurations/base_config.py +23 -24
- cdk_factory/configurations/cdk_config.py +1 -1
- cdk_factory/configurations/deployment.py +12 -0
- cdk_factory/configurations/devops.py +1 -1
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/cloudfront.py +7 -2
- cdk_factory/configurations/resources/ecr.py +1 -1
- cdk_factory/configurations/resources/ecs_cluster.py +12 -5
- cdk_factory/configurations/resources/ecs_service.py +30 -3
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/load_balancer.py +8 -9
- cdk_factory/configurations/resources/monitoring.py +8 -3
- cdk_factory/configurations/resources/rds.py +8 -9
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/rum.py +7 -2
- cdk_factory/configurations/resources/s3.py +10 -2
- cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
- cdk_factory/configurations/resources/vpc.py +19 -0
- cdk_factory/configurations/workload.py +32 -2
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/ecr/ecr_construct.py +9 -2
- cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
- cdk_factory/interfaces/istack.py +4 -4
- cdk_factory/interfaces/networked_stack_mixin.py +6 -6
- cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
- cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
- cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
- cdk_factory/pipeline/pipeline_factory.py +3 -3
- cdk_factory/stack_library/__init__.py +3 -2
- cdk_factory/stack_library/acm/acm_stack.py +7 -17
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
- cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
- cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +1 -3
- cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
- cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
- cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
- cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +240 -83
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
- cdk_factory/stack_library/rds/rds_stack.py +74 -98
- cdk_factory/stack_library/route53/route53_stack.py +246 -40
- cdk_factory/stack_library/rum/rum_stack.py +108 -91
- cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
- cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
- cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
- cdk_factory/stack_library/stack_base.py +5 -0
- cdk_factory/stack_library/vpc/vpc_stack.py +171 -130
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
- cdk_factory/utilities/environment_services.py +5 -5
- cdk_factory/utilities/json_loading_utility.py +1 -1
- cdk_factory/validation/config_validator.py +483 -0
- cdk_factory/version.py +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
- cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
- cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Configuration Validator for CDK Factory
|
|
9
|
+
|
|
10
|
+
Provides comprehensive validation for all CDK Factory configurations
|
|
11
|
+
including SSM integration, dependencies, and module-specific requirements.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import jsonschema
|
|
16
|
+
from typing import Dict, Any, List, Optional
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from aws_lambda_powertools import Logger
|
|
19
|
+
|
|
20
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import SsmStandardValidator, ValidationResult
|
|
21
|
+
|
|
22
|
+
logger = Logger(service="ConfigValidator")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConfigValidator:
|
|
26
|
+
"""
|
|
27
|
+
Comprehensive configuration validator for CDK Factory.
|
|
28
|
+
|
|
29
|
+
Validates:
|
|
30
|
+
- Module configuration against JSON schemas
|
|
31
|
+
- SSM configuration structure and paths
|
|
32
|
+
- Dependency graph consistency
|
|
33
|
+
- Template variable usage
|
|
34
|
+
- Required parameters and formats
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, schemas_dir: Optional[str] = None):
|
|
38
|
+
"""
|
|
39
|
+
Initialize configuration validator.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
schemas_dir: Directory containing JSON schema files
|
|
43
|
+
"""
|
|
44
|
+
self.schemas = self._load_schemas(schemas_dir)
|
|
45
|
+
self.ssm_validator = SsmStandardValidator()
|
|
46
|
+
|
|
47
|
+
def _load_schemas(self, schemas_dir: Optional[str]) -> Dict[str, Dict[str, Any]]:
|
|
48
|
+
"""
|
|
49
|
+
Load JSON schemas for module validation.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
schemas_dir: Directory containing schema files
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Dictionary mapping module names to schemas
|
|
56
|
+
"""
|
|
57
|
+
schemas = {}
|
|
58
|
+
|
|
59
|
+
# Default schemas directory
|
|
60
|
+
if not schemas_dir:
|
|
61
|
+
schemas_dir = Path(__file__).parent.parent / "schemas"
|
|
62
|
+
|
|
63
|
+
schemas_path = Path(schemas_dir)
|
|
64
|
+
if not schemas_path.exists():
|
|
65
|
+
logger.warning(f"Schemas directory not found: {schemas_path}")
|
|
66
|
+
return schemas
|
|
67
|
+
|
|
68
|
+
# Load all JSON schema files
|
|
69
|
+
for schema_file in schemas_path.glob("*.json"):
|
|
70
|
+
try:
|
|
71
|
+
with open(schema_file, 'r') as f:
|
|
72
|
+
schema = json.load(f)
|
|
73
|
+
module_name = schema_file.stem
|
|
74
|
+
schemas[module_name] = schema
|
|
75
|
+
logger.info(f"Loaded schema for module: {module_name}")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Failed to load schema {schema_file}: {e}")
|
|
78
|
+
|
|
79
|
+
return schemas
|
|
80
|
+
|
|
81
|
+
def validate_module_config(self, module_name: str, config: Dict[str, Any]) -> ValidationResult:
|
|
82
|
+
"""
|
|
83
|
+
Validate module configuration against its JSON schema.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
module_name: Name of the module
|
|
87
|
+
config: Configuration dictionary to validate
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
ValidationResult with validation status and errors
|
|
91
|
+
"""
|
|
92
|
+
errors = []
|
|
93
|
+
|
|
94
|
+
# Check if schema exists for module
|
|
95
|
+
if module_name not in self.schemas:
|
|
96
|
+
# Use generic schema if module-specific schema not found
|
|
97
|
+
schema = self._get_generic_schema()
|
|
98
|
+
logger.info(f"Using generic schema for module: {module_name}")
|
|
99
|
+
else:
|
|
100
|
+
schema = self.schemas[module_name]
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
jsonschema.validate(config, schema)
|
|
104
|
+
logger.info(f"Module configuration validation passed: {module_name}")
|
|
105
|
+
except jsonschema.ValidationError as e:
|
|
106
|
+
error_msg = f"Configuration validation failed: {e.message}"
|
|
107
|
+
if e.absolute_path:
|
|
108
|
+
error_msg += f" at {'.'.join(str(p) for p in e.absolute_path)}"
|
|
109
|
+
errors.append(error_msg)
|
|
110
|
+
logger.error(error_msg)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
errors.append(f"Schema validation error: {str(e)}")
|
|
113
|
+
logger.error(f"Schema validation error for {module_name}: {e}")
|
|
114
|
+
|
|
115
|
+
# Additional module-specific validations
|
|
116
|
+
module_errors = self._validate_module_specific(module_name, config)
|
|
117
|
+
errors.extend(module_errors)
|
|
118
|
+
|
|
119
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors)
|
|
120
|
+
|
|
121
|
+
def validate_ssm_configuration(self, config: Dict[str, Any]) -> ValidationResult:
|
|
122
|
+
"""
|
|
123
|
+
Validate SSM configuration using standardized validator.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
config: Configuration dictionary containing SSM settings
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
ValidationResult with validation status and errors
|
|
130
|
+
"""
|
|
131
|
+
return self.ssm_validator.validate_configuration(config)
|
|
132
|
+
|
|
133
|
+
def validate_dependencies(self, config: Dict[str, Any], available_stacks: List[str]) -> ValidationResult:
|
|
134
|
+
"""
|
|
135
|
+
Validate dependency configuration.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
config: Configuration dictionary with dependencies
|
|
139
|
+
available_stacks: List of available stack names
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
ValidationResult with validation status and errors
|
|
143
|
+
"""
|
|
144
|
+
errors = []
|
|
145
|
+
|
|
146
|
+
dependencies = config.get("dependencies", [])
|
|
147
|
+
if not isinstance(dependencies, list):
|
|
148
|
+
errors.append("dependencies must be a list")
|
|
149
|
+
return ValidationResult(valid=False, errors=errors)
|
|
150
|
+
|
|
151
|
+
# Check that all dependencies exist
|
|
152
|
+
for dep in dependencies:
|
|
153
|
+
if dep not in available_stacks:
|
|
154
|
+
errors.append(f"Dependency not found: {dep}")
|
|
155
|
+
|
|
156
|
+
# Check for circular dependencies
|
|
157
|
+
if self._has_circular_dependency(config.get("name", ""), dependencies, available_stacks):
|
|
158
|
+
errors.append("Circular dependency detected")
|
|
159
|
+
|
|
160
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors)
|
|
161
|
+
|
|
162
|
+
def validate_template_variables(self, config: Dict[str, Any]) -> ValidationResult:
|
|
163
|
+
"""
|
|
164
|
+
Validate template variable usage in configuration.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
config: Configuration dictionary to validate
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
ValidationResult with validation status and errors
|
|
171
|
+
"""
|
|
172
|
+
errors = []
|
|
173
|
+
|
|
174
|
+
def find_template_variables(obj, path=""):
|
|
175
|
+
if isinstance(obj, str):
|
|
176
|
+
import re
|
|
177
|
+
variables = re.findall(r'\{\{([^}]+)\}\}', obj)
|
|
178
|
+
for var in variables:
|
|
179
|
+
# Check for valid template variables
|
|
180
|
+
valid_vars = ["ENVIRONMENT", "WORKLOAD_NAME", "AWS_REGION", "RESOURCE_NAME"]
|
|
181
|
+
if var not in valid_vars:
|
|
182
|
+
errors.append(f"Invalid template variable '{var}' at {path}")
|
|
183
|
+
elif isinstance(obj, dict):
|
|
184
|
+
for key, value in obj.items():
|
|
185
|
+
current_path = f"{path}.{key}" if path else key
|
|
186
|
+
find_template_variables(value, current_path)
|
|
187
|
+
elif isinstance(obj, list):
|
|
188
|
+
for i, item in enumerate(obj):
|
|
189
|
+
find_template_variables(item, f"{path}[{i}]")
|
|
190
|
+
|
|
191
|
+
find_template_variables(config)
|
|
192
|
+
|
|
193
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors)
|
|
194
|
+
|
|
195
|
+
def validate_complete_configuration(self, config: Dict[str, Any], available_stacks: List[str] = None) -> ValidationResult:
|
|
196
|
+
"""
|
|
197
|
+
Perform comprehensive validation of configuration.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
config: Complete configuration dictionary
|
|
201
|
+
available_stacks: List of available stack names for dependency validation
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
ValidationResult with comprehensive validation status
|
|
205
|
+
"""
|
|
206
|
+
all_errors = []
|
|
207
|
+
|
|
208
|
+
# Validate required fields
|
|
209
|
+
required_fields = ["name", "module"]
|
|
210
|
+
for field in required_fields:
|
|
211
|
+
if field not in config:
|
|
212
|
+
all_errors.append(f"Missing required field: {field}")
|
|
213
|
+
|
|
214
|
+
# Validate module configuration
|
|
215
|
+
module_name = config.get("module")
|
|
216
|
+
if module_name:
|
|
217
|
+
module_validation = self.validate_module_config(module_name, config)
|
|
218
|
+
all_errors.extend(module_validation.errors)
|
|
219
|
+
|
|
220
|
+
# Validate SSM configuration
|
|
221
|
+
ssm_validation = self.validate_ssm_configuration(config)
|
|
222
|
+
all_errors.extend(ssm_validation.errors)
|
|
223
|
+
|
|
224
|
+
# Validate dependencies
|
|
225
|
+
if available_stacks:
|
|
226
|
+
dep_validation = self.validate_dependencies(config, available_stacks)
|
|
227
|
+
all_errors.extend(dep_validation.errors)
|
|
228
|
+
|
|
229
|
+
# Validate template variables
|
|
230
|
+
template_validation = self.validate_template_variables(config)
|
|
231
|
+
all_errors.extend(template_validation.errors)
|
|
232
|
+
|
|
233
|
+
return ValidationResult(valid=len(all_errors) == 0, errors=all_errors)
|
|
234
|
+
|
|
235
|
+
def _validate_module_specific(self, module_name: str, config: Dict[str, Any]) -> List[str]:
|
|
236
|
+
"""
|
|
237
|
+
Perform module-specific validations.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
module_name: Name of the module
|
|
241
|
+
config: Configuration dictionary
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of validation errors
|
|
245
|
+
"""
|
|
246
|
+
errors = []
|
|
247
|
+
|
|
248
|
+
if module_name == "vpc_library_module":
|
|
249
|
+
errors.extend(self._validate_vpc_config(config))
|
|
250
|
+
elif module_name == "auto_scaling_library_module":
|
|
251
|
+
errors.extend(self._validate_auto_scaling_config(config))
|
|
252
|
+
elif module_name == "ecs_cluster_stack":
|
|
253
|
+
errors.extend(self._validate_ecs_config(config))
|
|
254
|
+
|
|
255
|
+
return errors
|
|
256
|
+
|
|
257
|
+
def _validate_vpc_config(self, config: Dict[str, Any]) -> List[str]:
|
|
258
|
+
"""Validate VPC-specific configuration."""
|
|
259
|
+
errors = []
|
|
260
|
+
vpc_config = config.get("vpc", {})
|
|
261
|
+
|
|
262
|
+
# Validate CIDR format
|
|
263
|
+
cidr = vpc_config.get("cidr")
|
|
264
|
+
if cidr:
|
|
265
|
+
import ipaddress
|
|
266
|
+
try:
|
|
267
|
+
ipaddress.IPv4Network(cidr)
|
|
268
|
+
except ValueError:
|
|
269
|
+
errors.append(f"Invalid CIDR format: {cidr}")
|
|
270
|
+
|
|
271
|
+
# Validate max AZs
|
|
272
|
+
max_azs = vpc_config.get("max_azs")
|
|
273
|
+
if max_azs and (not isinstance(max_azs, int) or max_azs < 1 or max_azs > 6):
|
|
274
|
+
errors.append(f"max_azs must be an integer between 1 and 6: {max_azs}")
|
|
275
|
+
|
|
276
|
+
return errors
|
|
277
|
+
|
|
278
|
+
def _validate_auto_scaling_config(self, config: Dict[str, Any]) -> List[str]:
|
|
279
|
+
"""Validate Auto Scaling-specific configuration."""
|
|
280
|
+
errors = []
|
|
281
|
+
asg_config = config.get("auto_scaling", {})
|
|
282
|
+
|
|
283
|
+
# Validate capacity settings
|
|
284
|
+
min_capacity = asg_config.get("min_capacity")
|
|
285
|
+
max_capacity = asg_config.get("max_capacity")
|
|
286
|
+
desired_capacity = asg_config.get("desired_capacity")
|
|
287
|
+
|
|
288
|
+
if all([min_capacity, max_capacity, desired_capacity]):
|
|
289
|
+
if not (min_capacity <= desired_capacity <= max_capacity):
|
|
290
|
+
errors.append("desired_capacity must be between min_capacity and max_capacity")
|
|
291
|
+
|
|
292
|
+
# Validate instance type
|
|
293
|
+
instance_type = asg_config.get("instance_type")
|
|
294
|
+
if instance_type:
|
|
295
|
+
valid_types = ["t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large",
|
|
296
|
+
"t3.nano", "t3.micro", "t3.small", "t3.medium", "t3.large",
|
|
297
|
+
"t3a.nano", "t3a.micro", "t3a.small", "t3a.medium", "t3a.large"]
|
|
298
|
+
if instance_type not in valid_types:
|
|
299
|
+
errors.append(f"Invalid instance type: {instance_type}")
|
|
300
|
+
|
|
301
|
+
return errors
|
|
302
|
+
|
|
303
|
+
def _validate_ecs_config(self, config: Dict[str, Any]) -> List[str]:
|
|
304
|
+
"""Validate ECS-specific configuration."""
|
|
305
|
+
errors = []
|
|
306
|
+
ecs_config = config.get("ecs", {})
|
|
307
|
+
|
|
308
|
+
# Validate capacity providers
|
|
309
|
+
capacity_providers = ecs_config.get("capacity_providers", [])
|
|
310
|
+
valid_providers = ["FARGATE", "FARGATE_SPOT", "EC2", "EC2_SPOT"]
|
|
311
|
+
|
|
312
|
+
for provider in capacity_providers:
|
|
313
|
+
if provider not in valid_providers:
|
|
314
|
+
errors.append(f"Invalid capacity provider: {provider}")
|
|
315
|
+
|
|
316
|
+
return errors
|
|
317
|
+
|
|
318
|
+
def _get_generic_schema(self) -> Dict[str, Any]:
|
|
319
|
+
"""Get generic schema for modules without specific schemas."""
|
|
320
|
+
return {
|
|
321
|
+
"type": "object",
|
|
322
|
+
"required": ["name", "module"],
|
|
323
|
+
"properties": {
|
|
324
|
+
"name": {
|
|
325
|
+
"type": "string",
|
|
326
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]*$"
|
|
327
|
+
},
|
|
328
|
+
"module": {
|
|
329
|
+
"type": "string"
|
|
330
|
+
},
|
|
331
|
+
"enabled": {
|
|
332
|
+
"type": "boolean",
|
|
333
|
+
"default": True
|
|
334
|
+
},
|
|
335
|
+
"dependencies": {
|
|
336
|
+
"type": "array",
|
|
337
|
+
"items": {
|
|
338
|
+
"type": "string"
|
|
339
|
+
},
|
|
340
|
+
"default": []
|
|
341
|
+
},
|
|
342
|
+
"ssm": {
|
|
343
|
+
"type": "object",
|
|
344
|
+
"properties": {
|
|
345
|
+
"imports": {
|
|
346
|
+
"type": "object",
|
|
347
|
+
"patternProperties": {
|
|
348
|
+
"^[a-zA-Z][a-zA-Z0-9_]*$": {
|
|
349
|
+
"oneOf": [
|
|
350
|
+
{"type": "string"},
|
|
351
|
+
{
|
|
352
|
+
"type": "array",
|
|
353
|
+
"items": {"type": "string"}
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
"exports": {
|
|
360
|
+
"type": "object",
|
|
361
|
+
"patternProperties": {
|
|
362
|
+
"^[a-zA-Z][a-zA-Z0-9_]*$": {"type": "string"}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"additionalProperties": False
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
"additionalProperties": True
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
def _has_circular_dependency(self, stack_name: str, dependencies: List[str], available_stacks: List[str]) -> bool:
|
|
373
|
+
"""
|
|
374
|
+
Check for circular dependencies using simple detection.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
stack_name: Name of the current stack
|
|
378
|
+
dependencies: List of dependencies for current stack
|
|
379
|
+
available_stacks: List of all available stacks
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
True if circular dependency detected
|
|
383
|
+
"""
|
|
384
|
+
# Simple check: if stack appears in its own dependencies
|
|
385
|
+
if stack_name in dependencies:
|
|
386
|
+
return True
|
|
387
|
+
|
|
388
|
+
# For more complex circular dependency detection,
|
|
389
|
+
# we would need to build the full dependency graph
|
|
390
|
+
# This is a simplified implementation
|
|
391
|
+
return False
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class SchemaGenerator:
|
|
395
|
+
"""Generate JSON schemas for module configurations."""
|
|
396
|
+
|
|
397
|
+
def generate_schema_from_module(self, module_class) -> Dict[str, Any]:
|
|
398
|
+
"""
|
|
399
|
+
Generate JSON schema from module class documentation.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
module_class: Module class to generate schema for
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
JSON schema dictionary
|
|
406
|
+
"""
|
|
407
|
+
# This would parse module class documentation and config classes
|
|
408
|
+
# to generate appropriate JSON schemas
|
|
409
|
+
# For now, return a basic schema
|
|
410
|
+
return {
|
|
411
|
+
"type": "object",
|
|
412
|
+
"required": ["name", "module"],
|
|
413
|
+
"properties": {
|
|
414
|
+
"name": {"type": "string"},
|
|
415
|
+
"module": {"type": "string"}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
def save_schema(self, module_name: str, schema: Dict[str, Any], output_dir: str):
|
|
420
|
+
"""
|
|
421
|
+
Save JSON schema to file.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
module_name: Name of the module
|
|
425
|
+
schema: JSON schema to save
|
|
426
|
+
output_dir: Directory to save schema in
|
|
427
|
+
"""
|
|
428
|
+
output_path = Path(output_dir) / f"{module_name}.json"
|
|
429
|
+
|
|
430
|
+
with open(output_path, 'w') as f:
|
|
431
|
+
json.dump(schema, f, indent=2)
|
|
432
|
+
|
|
433
|
+
logger.info(f"Saved schema for {module_name} to {output_path}")
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# Utility functions for validation
|
|
437
|
+
|
|
438
|
+
def validate_configuration_file(file_path: str, validator: ConfigValidator = None) -> ValidationResult:
|
|
439
|
+
"""
|
|
440
|
+
Validate a configuration file.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
file_path: Path to configuration file
|
|
444
|
+
validator: ConfigValidator instance (creates default if None)
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
ValidationResult with validation status
|
|
448
|
+
"""
|
|
449
|
+
if validator is None:
|
|
450
|
+
validator = ConfigValidator()
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
with open(file_path, 'r') as f:
|
|
454
|
+
config = json.load(f)
|
|
455
|
+
|
|
456
|
+
return validator.validate_complete_configuration(config)
|
|
457
|
+
|
|
458
|
+
except Exception as e:
|
|
459
|
+
return ValidationResult(valid=False, errors=[f"Failed to load configuration file: {str(e)}"])
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def validate_configurations_directory(directory: str, validator: ConfigValidator = None) -> Dict[str, ValidationResult]:
|
|
463
|
+
"""
|
|
464
|
+
Validate all configuration files in a directory.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
directory: Directory containing configuration files
|
|
468
|
+
validator: ConfigValidator instance (creates default if None)
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Dictionary mapping file names to validation results
|
|
472
|
+
"""
|
|
473
|
+
if validator is None:
|
|
474
|
+
validator = ConfigValidator()
|
|
475
|
+
|
|
476
|
+
results = {}
|
|
477
|
+
config_dir = Path(directory)
|
|
478
|
+
|
|
479
|
+
for config_file in config_dir.glob("*.json"):
|
|
480
|
+
result = validate_configuration_file(str(config_file), validator)
|
|
481
|
+
results[config_file.name] = result
|
|
482
|
+
|
|
483
|
+
return results
|
cdk_factory/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.20.0"
|