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.

Files changed (66) hide show
  1. cdk_factory/configurations/base_config.py +23 -24
  2. cdk_factory/configurations/cdk_config.py +1 -1
  3. cdk_factory/configurations/deployment.py +12 -0
  4. cdk_factory/configurations/devops.py +1 -1
  5. cdk_factory/configurations/resources/acm.py +9 -2
  6. cdk_factory/configurations/resources/auto_scaling.py +7 -5
  7. cdk_factory/configurations/resources/cloudfront.py +7 -2
  8. cdk_factory/configurations/resources/ecr.py +1 -1
  9. cdk_factory/configurations/resources/ecs_cluster.py +12 -5
  10. cdk_factory/configurations/resources/ecs_service.py +30 -3
  11. cdk_factory/configurations/resources/lambda_edge.py +18 -4
  12. cdk_factory/configurations/resources/load_balancer.py +8 -9
  13. cdk_factory/configurations/resources/monitoring.py +8 -3
  14. cdk_factory/configurations/resources/rds.py +8 -9
  15. cdk_factory/configurations/resources/route53.py +5 -0
  16. cdk_factory/configurations/resources/rum.py +7 -2
  17. cdk_factory/configurations/resources/s3.py +10 -2
  18. cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
  19. cdk_factory/configurations/resources/vpc.py +19 -0
  20. cdk_factory/configurations/workload.py +32 -2
  21. cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
  22. cdk_factory/constructs/ecr/ecr_construct.py +9 -2
  23. cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
  24. cdk_factory/interfaces/istack.py +4 -4
  25. cdk_factory/interfaces/networked_stack_mixin.py +6 -6
  26. cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
  27. cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
  28. cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
  29. cdk_factory/pipeline/pipeline_factory.py +3 -3
  30. cdk_factory/stack_library/__init__.py +3 -2
  31. cdk_factory/stack_library/acm/acm_stack.py +7 -17
  32. cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
  33. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
  34. cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
  35. cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
  36. cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
  37. cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
  38. cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
  39. cdk_factory/stack_library/ecs/__init__.py +1 -3
  40. cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
  41. cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
  42. cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
  43. cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
  44. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +240 -83
  45. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
  46. cdk_factory/stack_library/rds/rds_stack.py +74 -98
  47. cdk_factory/stack_library/route53/route53_stack.py +246 -40
  48. cdk_factory/stack_library/rum/rum_stack.py +108 -91
  49. cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
  50. cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
  51. cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
  52. cdk_factory/stack_library/stack_base.py +5 -0
  53. cdk_factory/stack_library/vpc/vpc_stack.py +171 -130
  54. cdk_factory/stack_library/websites/static_website_stack.py +7 -3
  55. cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
  56. cdk_factory/utilities/environment_services.py +5 -5
  57. cdk_factory/utilities/json_loading_utility.py +1 -1
  58. cdk_factory/validation/config_validator.py +483 -0
  59. cdk_factory/version.py +1 -1
  60. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
  61. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
  62. cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
  63. cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
  64. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
  65. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
  66. {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.16.15"
1
+ __version__ = "0.20.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.16.15
3
+ Version: 0.20.0
4
4
  Summary: CDK Factory. A QuickStarter and best practices setup for CDK projects
5
5
  Author-email: Eric Wilson <eric.wilson@geekcafe.com>
6
6
  License: MIT License