cdk-factory 0.15.12__tar.gz → 0.15.14__tar.gz
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-0.15.12 → cdk_factory-0.15.14}/PKG-INFO +1 -1
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/pyproject.toml +1 -1
- cdk_factory-0.15.14/src/cdk_factory/configurations/resources/rds.py +411 -0
- cdk_factory-0.15.14/src/cdk_factory/version.py +1 -0
- cdk_factory-0.15.12/src/cdk_factory/configurations/resources/rds.py +0 -240
- cdk_factory-0.15.12/src/cdk_factory/version.py +0 -1
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/.gitignore +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/.windsurfrules +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/LICENSE +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/archive/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/archive/migrate_to_enhanced_ssm.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/examples/json-imports/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/examples/separate-api-gateway/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/examples/separate-api-gateway/api-gateway-stack.json +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/examples/separate-api-gateway/config.json +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/examples/separate-api-gateway/lambda-stack.json +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/mypy.ini +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/publish_to_pypi.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/publish_to_pypi.sh +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/pysetup.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/pysetup.sh +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/requirements.dev.txt +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/requirements.tests.txt +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/requirements.txt +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/run-checks.sh +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/run-tests-clean-venv.sh +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/run-tests.sh +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/scripts/cloudfront-cleanup.sh +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/app.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/builds/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/cdk.json +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/cli.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/commands/command_loader.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/base_config.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/cdk_config.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/deployment.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/deployment_wave.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/devops.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/enhanced_base_config.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/enhanced_ssm_config.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/management.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/pipeline.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/pipeline_stage.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/_resources.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/api_gateway.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/apigateway_route_config.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/auto_scaling.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/cloudfront.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/cloudwatch_widget.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/code_artifact.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/code_artifact_login.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/code_repository.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/cognito.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/docker.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/dynamodb.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/ecr.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/ecs_service.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/exisiting.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/lambda_edge.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/lambda_function.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/lambda_layers.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/lambda_triggers.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/load_balancer.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/monitoring.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/resource_mapping.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/resource_naming.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/resource_types.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/route53.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/route53_hosted_zone.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/rum.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/s3.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/security_group.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/security_group_full_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/sqs.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/resources/vpc.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/configurations/workload.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/ecr/ecr_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/lambdas/lambda_function_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/lambdas/lambda_function_docker_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/lambdas/lambda_function_role_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/lambdas/policies/policy_docs.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/lambdas/policies/policy_statements.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/s3_buckets/s3_bucket_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_destination_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_source_construct.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/constructs/sqs/policies/sqs_policies.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/interfaces/istack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/interfaces/live_ssm_resolver.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/interfaces/ssm_parameter_mixin.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/lambdas/edge/ip_gate/handler.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/lambdas/health_handler.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/pipeline/path_utils.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/pipeline/pipeline_factory.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/pipeline/security/policies.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/pipeline/security/roles.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/pipeline/stage.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack/istack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack/stack_factory.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack/stack_module_loader.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack/stack_module_registry.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack/stack_modules.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/api_gateway/api_gateway_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/auto_scaling/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/aws_lambdas/lambda_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/buckets/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/buckets/bucket_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/cloudfront/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/cloudfront/cloudfront_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/code_artifact/code_artifact_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/cognito/cognito_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/dynamodb/dynamodb_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/ecr/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/ecr/ecr_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/ecs/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/ecs/ecs_service_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/lambda_edge/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/load_balancer/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/load_balancer/load_balancer_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/monitoring/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/monitoring/monitoring_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/rds/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/rds/rds_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/route53/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/route53/route53_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/rum/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/rum/rum_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/security_group/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/security_group/security_group_full_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/security_group/security_group_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/simple_queue_service/sqs_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/stack_base.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/vpc/__init__.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/vpc/vpc_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stack_library/websites/static_website_stack.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/stages/websites/static_website_stage.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/templates/README.md +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/templates/app.py.template +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/templates/cdk.json.template +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/api_gateway_integration_utility.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/commandline_args.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/configuration_loader.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/docker_utilities.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/environment_services.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/file_operations.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/git_utilities.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/json_loading_utility.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/lambda_function_utilities.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utilities/os_execute.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/utils/api_gateway_utilities.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/cdk_factory/workload/workload_factory.py +0 -0
- {cdk_factory-0.15.12 → cdk_factory-0.15.14}/src/handlers/test/handler.py +0 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RdsConfig - supports RDS database settings for AWS CDK.
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, Literal
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
from cdk_factory.configurations.enhanced_base_config import EnhancedBaseConfig
|
|
11
|
+
|
|
12
|
+
logger = Logger(service="RdsConfig")
|
|
13
|
+
|
|
14
|
+
# Supported RDS engines
|
|
15
|
+
Engine = Literal["mysql", "mariadb", "postgres", "aurora-mysql", "aurora-postgres", "sqlserver", "oracle"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RdsConfig(EnhancedBaseConfig):
|
|
19
|
+
"""
|
|
20
|
+
RDS Configuration - supports RDS database settings.
|
|
21
|
+
Each property reads from the config dict and provides a sensible default if not set.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: dict, deployment) -> None:
|
|
25
|
+
super().__init__(config or {}, resource_type="rds", resource_name=config.get("name", "rds") if config else "rds")
|
|
26
|
+
self.__config = config or {}
|
|
27
|
+
self.__deployment = deployment
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def name(self) -> str:
|
|
31
|
+
"""RDS instance name"""
|
|
32
|
+
return self.__config.get("name", "database")
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def identifier(self) -> str:
|
|
36
|
+
"""RDS DB instance identifier (sanitized)"""
|
|
37
|
+
raw_id = self.__config.get("identifier", self.name)
|
|
38
|
+
return self._sanitize_instance_identifier(raw_id)
|
|
39
|
+
|
|
40
|
+
def _sanitize_instance_identifier(self, identifier: str) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Sanitize DB instance identifier to meet RDS requirements:
|
|
43
|
+
- 1-63 chars, lowercase letters/digits/hyphen
|
|
44
|
+
- Must start with letter, can't end with hyphen, no consecutive hyphens
|
|
45
|
+
"""
|
|
46
|
+
if not identifier:
|
|
47
|
+
raise ValueError("Instance identifier cannot be empty")
|
|
48
|
+
|
|
49
|
+
sanitized, notes = self._sanitize_instance_identifier_impl(identifier)
|
|
50
|
+
|
|
51
|
+
if notes:
|
|
52
|
+
logger.info(f"Sanitized instance identifier from '{identifier}' to '{sanitized}': {', '.join(notes)}")
|
|
53
|
+
|
|
54
|
+
return sanitized
|
|
55
|
+
|
|
56
|
+
def _sanitize_instance_identifier_impl(self, identifier: str) -> Tuple[str, List[str]]:
|
|
57
|
+
"""
|
|
58
|
+
DB instance identifier rules (all engines):
|
|
59
|
+
- 1-63 chars, lowercase letters/digits/hyphen
|
|
60
|
+
- Must start with letter
|
|
61
|
+
- Can't end with hyphen
|
|
62
|
+
- No consecutive hyphens (--)
|
|
63
|
+
"""
|
|
64
|
+
notes: List[str] = []
|
|
65
|
+
s = identifier.lower()
|
|
66
|
+
|
|
67
|
+
# Keep only lowercase letters, digits, hyphen
|
|
68
|
+
s_clean = re.sub(r"[^a-z0-9-]", "", s)
|
|
69
|
+
if s_clean != s:
|
|
70
|
+
notes.append("removed invalid characters (only a-z, 0-9, '-' allowed)")
|
|
71
|
+
s = s_clean
|
|
72
|
+
|
|
73
|
+
if not s:
|
|
74
|
+
raise ValueError(f"Instance identifier '{identifier}' contains no valid characters")
|
|
75
|
+
|
|
76
|
+
# Must start with letter
|
|
77
|
+
if not re.match(r"^[a-z]", s):
|
|
78
|
+
s = f"db{s}"
|
|
79
|
+
notes.append("prefixed with 'db' to start with a letter")
|
|
80
|
+
|
|
81
|
+
# Collapse consecutive hyphens
|
|
82
|
+
s_collapsed = re.sub(r"-{2,}", "-", s)
|
|
83
|
+
if s_collapsed != s:
|
|
84
|
+
s = s_collapsed
|
|
85
|
+
notes.append("collapsed consecutive hyphens")
|
|
86
|
+
|
|
87
|
+
# Can't end with hyphen
|
|
88
|
+
if s.endswith("-"):
|
|
89
|
+
s = s.rstrip("-")
|
|
90
|
+
notes.append("removed trailing hyphen")
|
|
91
|
+
|
|
92
|
+
# Truncate to 63 characters
|
|
93
|
+
if len(s) > 63:
|
|
94
|
+
s = s[:63]
|
|
95
|
+
# Make sure we didn't truncate to a trailing hyphen
|
|
96
|
+
if s.endswith("-"):
|
|
97
|
+
s = s.rstrip("-")
|
|
98
|
+
notes.append("truncated to 63 characters")
|
|
99
|
+
|
|
100
|
+
return s, notes
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def engine(self) -> str:
|
|
104
|
+
"""Database engine"""
|
|
105
|
+
return self.__config.get("engine", "postgres")
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def engine_version(self) -> str:
|
|
109
|
+
"""Database engine version"""
|
|
110
|
+
engine_version = self.__config.get("engine_version")
|
|
111
|
+
if not engine_version:
|
|
112
|
+
raise ValueError("No engine version found")
|
|
113
|
+
return engine_version
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def instance_class(self) -> str:
|
|
117
|
+
"""Database instance class"""
|
|
118
|
+
return self.__config.get("instance_class", "t3.micro")
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def database_name(self) -> str:
|
|
122
|
+
"""Name of the database to create (sanitized for RDS requirements)"""
|
|
123
|
+
raw_name = self.__config.get("database_name", "appdb")
|
|
124
|
+
return self._sanitize_database_name(raw_name)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def username(self) -> str:
|
|
128
|
+
"""Master username for the database (sanitized for RDS requirements)"""
|
|
129
|
+
raw_username = self.__config.get("username", "appuser")
|
|
130
|
+
return self._sanitize_username(raw_username)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def secret_name(self) -> str:
|
|
134
|
+
"""Name of the secret to store credentials"""
|
|
135
|
+
env_name = self.__deployment.environment if self.__deployment else None
|
|
136
|
+
if not env_name:
|
|
137
|
+
raise ValueError("No environment found for RDS secret name. Please add an environment to the deployment.")
|
|
138
|
+
return self.__config.get("secret_name", f"/{env_name}/db/creds")
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def allocated_storage(self) -> int:
|
|
142
|
+
"""Allocated storage in GB"""
|
|
143
|
+
# Ensure we return an integer
|
|
144
|
+
return int(self.__config.get("allocated_storage", 20))
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def storage_encrypted(self) -> bool:
|
|
148
|
+
"""Whether storage is encrypted"""
|
|
149
|
+
return self.__config.get("storage_encrypted", True)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def multi_az(self) -> bool:
|
|
153
|
+
"""Whether to enable Multi-AZ deployment"""
|
|
154
|
+
return self.__config.get("multi_az", False)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def backup_retention(self) -> int:
|
|
158
|
+
"""Backup retention period in days"""
|
|
159
|
+
return self.__config.get("backup_retention", 7)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def deletion_protection(self) -> bool:
|
|
163
|
+
"""Whether deletion protection is enabled"""
|
|
164
|
+
return self.__config.get("deletion_protection", False)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def enable_performance_insights(self) -> bool:
|
|
168
|
+
"""Whether to enable Performance Insights"""
|
|
169
|
+
return self.__config.get("enable_performance_insights", True)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def subnet_group_name(self) -> str:
|
|
173
|
+
"""Subnet group name for database placement"""
|
|
174
|
+
return self.__config.get("subnet_group_name", "db")
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def security_group_ids(self) -> List[str]:
|
|
178
|
+
"""Security group IDs for the database"""
|
|
179
|
+
return self.__config.get("security_group_ids", [])
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def cloudwatch_logs_exports(self) -> List[str]:
|
|
183
|
+
"""
|
|
184
|
+
Log types to export to CloudWatch (engine-specific).
|
|
185
|
+
Returns configured log types or engine-specific defaults.
|
|
186
|
+
"""
|
|
187
|
+
# If explicitly configured, use that
|
|
188
|
+
if "cloudwatch_logs_exports" in self.__config:
|
|
189
|
+
return self.__config["cloudwatch_logs_exports"]
|
|
190
|
+
|
|
191
|
+
# Otherwise, return engine-specific defaults
|
|
192
|
+
engine = self.engine.lower()
|
|
193
|
+
|
|
194
|
+
# MySQL / MariaDB
|
|
195
|
+
if engine in ("mysql", "mariadb", "aurora-mysql"):
|
|
196
|
+
return ["error", "general", "slowquery"]
|
|
197
|
+
|
|
198
|
+
# PostgreSQL
|
|
199
|
+
elif engine in ("postgres", "postgresql", "aurora-postgres", "aurora-postgresql"):
|
|
200
|
+
return ["postgresql"]
|
|
201
|
+
|
|
202
|
+
# SQL Server
|
|
203
|
+
elif engine in ("sqlserver", "sqlserver-ee", "sqlserver-se", "sqlserver-ex", "sqlserver-web"):
|
|
204
|
+
return ["error", "agent"]
|
|
205
|
+
|
|
206
|
+
# Oracle
|
|
207
|
+
elif engine in ("oracle", "oracle-ee", "oracle-se2", "oracle-se1"):
|
|
208
|
+
return ["alert", "audit", "trace"]
|
|
209
|
+
|
|
210
|
+
# Default to empty list for unknown engines (safer than guessing)
|
|
211
|
+
else:
|
|
212
|
+
logger.warning(f"Unknown engine '{engine}', disabling CloudWatch logs exports by default")
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def removal_policy(self) -> str:
|
|
217
|
+
"""Removal policy for the database"""
|
|
218
|
+
return self.__config.get("removal_policy", "retain")
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def existing_instance_id(self) -> Optional[str]:
|
|
222
|
+
"""Existing RDS instance ID to import (if using existing)"""
|
|
223
|
+
return self.__config.get("existing_instance_id")
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def tags(self) -> Dict[str, str]:
|
|
227
|
+
"""Tags to apply to the RDS instance"""
|
|
228
|
+
return self.__config.get("tags", {})
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def vpc_id(self) -> str | None:
|
|
232
|
+
"""Returns the VPC ID for the Security Group"""
|
|
233
|
+
return self.__config.get("vpc_id")
|
|
234
|
+
|
|
235
|
+
@vpc_id.setter
|
|
236
|
+
def vpc_id(self, value: str):
|
|
237
|
+
"""Sets the VPC ID for the Security Group"""
|
|
238
|
+
self.__config["vpc_id"] = value
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def ssm_imports(self) -> Dict[str, str]:
|
|
242
|
+
"""SSM parameter imports for the RDS instance"""
|
|
243
|
+
# Check both nested and flat structures for backwards compatibility
|
|
244
|
+
if "ssm" in self.__config and "imports" in self.__config["ssm"]:
|
|
245
|
+
return self.__config["ssm"]["imports"]
|
|
246
|
+
return self.__config.get("ssm_imports", {})
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def ssm_exports(self) -> Dict[str, str]:
|
|
250
|
+
"""SSM parameter exports for the RDS instance"""
|
|
251
|
+
# Check both nested and flat structures for backwards compatibility
|
|
252
|
+
if "ssm" in self.__config and "exports" in self.__config["ssm"]:
|
|
253
|
+
return self.__config["ssm"]["exports"]
|
|
254
|
+
return self.__config.get("ssm_exports", {})
|
|
255
|
+
|
|
256
|
+
def _sanitize_database_name(self, name: str) -> str:
|
|
257
|
+
"""
|
|
258
|
+
Sanitize database name to meet RDS requirements (engine-specific).
|
|
259
|
+
Implements rules from RDS documentation for each engine type.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
name: Raw database name from config
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Sanitized database name
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
ValueError: If name cannot be sanitized to meet requirements
|
|
269
|
+
"""
|
|
270
|
+
if not name:
|
|
271
|
+
raise ValueError("Database name cannot be empty")
|
|
272
|
+
|
|
273
|
+
engine = self.engine.lower()
|
|
274
|
+
sanitized, notes = self._sanitize_db_name_impl(engine, name)
|
|
275
|
+
|
|
276
|
+
if notes:
|
|
277
|
+
logger.info(f"Sanitized database name from '{name}' to '{sanitized}': {', '.join(notes)}")
|
|
278
|
+
|
|
279
|
+
return sanitized
|
|
280
|
+
|
|
281
|
+
def _sanitize_db_name_impl(self, engine: str, name: str) -> Tuple[str, List[str]]:
|
|
282
|
+
"""
|
|
283
|
+
Engine-specific database name sanitization.
|
|
284
|
+
Based on AWS RDS naming requirements:
|
|
285
|
+
- MySQL/MariaDB: 1-64 chars, start with letter, letters/digits/underscore
|
|
286
|
+
- PostgreSQL: 1-63 chars, start with letter, letters/digits/underscore
|
|
287
|
+
- SQL Server: 1-128 chars, start with letter, letters/digits/underscore
|
|
288
|
+
- Oracle: 1-8 chars (SID), alphanumeric only, start with letter
|
|
289
|
+
"""
|
|
290
|
+
notes: List[str] = []
|
|
291
|
+
|
|
292
|
+
# Determine engine-specific limits
|
|
293
|
+
if engine in ("mysql", "mariadb", "aurora-mysql"):
|
|
294
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
295
|
+
max_len = 64
|
|
296
|
+
elif engine in ("postgres", "postgresql", "aurora-postgres", "aurora-postgresql"):
|
|
297
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
298
|
+
max_len = 63
|
|
299
|
+
elif engine in ("sqlserver", "sqlserver-ee", "sqlserver-se", "sqlserver-ex", "sqlserver-web"):
|
|
300
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
301
|
+
max_len = 128
|
|
302
|
+
elif engine in ("oracle", "oracle-ee", "oracle-se2", "oracle-se1"):
|
|
303
|
+
allowed_chars = r"A-Za-z0-9" # No underscore for Oracle SID
|
|
304
|
+
max_len = 8
|
|
305
|
+
else:
|
|
306
|
+
# Default to conservative rules
|
|
307
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
308
|
+
max_len = 64
|
|
309
|
+
notes.append(f"unknown engine '{engine}', using default MySQL rules")
|
|
310
|
+
|
|
311
|
+
# Replace hyphens with underscores (except Oracle which doesn't allow underscores)
|
|
312
|
+
s = name
|
|
313
|
+
if "oracle" not in engine:
|
|
314
|
+
s = s.replace("-", "_")
|
|
315
|
+
if "_" in name and "-" in name:
|
|
316
|
+
notes.append("replaced hyphens with underscores")
|
|
317
|
+
|
|
318
|
+
# Strip disallowed characters
|
|
319
|
+
s_clean = re.sub(f"[^{allowed_chars}]", "", s)
|
|
320
|
+
if s_clean != s:
|
|
321
|
+
notes.append("removed invalid characters")
|
|
322
|
+
s = s_clean
|
|
323
|
+
|
|
324
|
+
if not s:
|
|
325
|
+
raise ValueError(f"Database name '{name}' contains no valid characters after sanitization")
|
|
326
|
+
|
|
327
|
+
# Must start with a letter
|
|
328
|
+
if not re.match(r"^[A-Za-z]", s):
|
|
329
|
+
s = f"db{s}"
|
|
330
|
+
notes.append("prefixed with 'db' to start with a letter")
|
|
331
|
+
|
|
332
|
+
# Truncate to max length
|
|
333
|
+
if len(s) > max_len:
|
|
334
|
+
s = s[:max_len]
|
|
335
|
+
notes.append(f"truncated to {max_len} characters")
|
|
336
|
+
|
|
337
|
+
# SQL Server: can't start with 'rdsadmin'
|
|
338
|
+
if "sqlserver" in engine and s.lower().startswith("rdsadmin"):
|
|
339
|
+
s = f"db_{s}"
|
|
340
|
+
notes.append("prefixed to avoid 'rdsadmin' (SQL Server restriction)")
|
|
341
|
+
|
|
342
|
+
return s, notes
|
|
343
|
+
|
|
344
|
+
def _sanitize_username(self, username: str) -> str:
|
|
345
|
+
"""
|
|
346
|
+
Sanitize master username to meet RDS requirements:
|
|
347
|
+
- Must begin with a letter (a-z, A-Z)
|
|
348
|
+
- Can contain alphanumeric characters and underscores
|
|
349
|
+
- Max 16 characters (AWS RDS master username limit)
|
|
350
|
+
- Cannot be a reserved word
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
username: Raw username from config
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Sanitized username
|
|
357
|
+
|
|
358
|
+
Raises:
|
|
359
|
+
ValueError: If username is invalid
|
|
360
|
+
"""
|
|
361
|
+
if not username:
|
|
362
|
+
raise ValueError("Username cannot be empty")
|
|
363
|
+
|
|
364
|
+
sanitized, notes = self._sanitize_master_username_impl(username)
|
|
365
|
+
|
|
366
|
+
if notes:
|
|
367
|
+
logger.info(f"Sanitized username from '{username}' to '{sanitized}': {', '.join(notes)}")
|
|
368
|
+
|
|
369
|
+
return sanitized
|
|
370
|
+
|
|
371
|
+
def _sanitize_master_username_impl(self, username: str) -> Tuple[str, List[str]]:
|
|
372
|
+
"""
|
|
373
|
+
Sanitize master username according to AWS RDS rules:
|
|
374
|
+
- 1-16 characters
|
|
375
|
+
- Start with a letter
|
|
376
|
+
- Letters, digits, underscore only
|
|
377
|
+
- Not a reserved word
|
|
378
|
+
"""
|
|
379
|
+
notes: List[str] = []
|
|
380
|
+
s = username
|
|
381
|
+
|
|
382
|
+
# Replace hyphens with underscores, remove other invalid chars
|
|
383
|
+
s = s.replace("-", "_")
|
|
384
|
+
s_clean = re.sub(r"[^A-Za-z0-9_]", "", s)
|
|
385
|
+
if s_clean != s:
|
|
386
|
+
notes.append("removed invalid characters")
|
|
387
|
+
s = s_clean
|
|
388
|
+
|
|
389
|
+
if not s:
|
|
390
|
+
raise ValueError(f"Username '{username}' contains no valid characters after sanitization")
|
|
391
|
+
|
|
392
|
+
# Must start with a letter
|
|
393
|
+
if not re.match(r"^[A-Za-z]", s):
|
|
394
|
+
s = f"user{s}"
|
|
395
|
+
notes.append("prefixed with 'user' to start with a letter")
|
|
396
|
+
|
|
397
|
+
# Truncate to 16 characters
|
|
398
|
+
if len(s) > 16:
|
|
399
|
+
s = s[:16]
|
|
400
|
+
notes.append("truncated to 16 characters")
|
|
401
|
+
|
|
402
|
+
# Check against common reserved words
|
|
403
|
+
reserved = {"postgres", "mysql", "root", "admin", "rdsadmin", "system", "sa", "user"}
|
|
404
|
+
if s.lower() in reserved:
|
|
405
|
+
s = f"{s}_usr"
|
|
406
|
+
# Re-truncate if needed after adding suffix
|
|
407
|
+
if len(s) > 16:
|
|
408
|
+
s = s[:16]
|
|
409
|
+
notes.append("appended '_usr' to avoid reserved username")
|
|
410
|
+
|
|
411
|
+
return s, notes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.15.14"
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
RdsConfig - supports RDS database settings for AWS CDK.
|
|
3
|
-
Maintainers: Eric Wilson
|
|
4
|
-
MIT License. See Project Root for license information.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import re
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
9
|
-
from aws_lambda_powertools import Logger
|
|
10
|
-
from cdk_factory.configurations.enhanced_base_config import EnhancedBaseConfig
|
|
11
|
-
|
|
12
|
-
logger = Logger(service="RdsConfig")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class RdsConfig(EnhancedBaseConfig):
|
|
16
|
-
"""
|
|
17
|
-
RDS Configuration - supports RDS database settings.
|
|
18
|
-
Each property reads from the config dict and provides a sensible default if not set.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, config: dict, deployment) -> None:
|
|
22
|
-
super().__init__(config or {}, resource_type="rds", resource_name=config.get("name", "rds") if config else "rds")
|
|
23
|
-
self.__config = config or {}
|
|
24
|
-
self.__deployment = deployment
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def name(self) -> str:
|
|
28
|
-
"""RDS instance name"""
|
|
29
|
-
return self.__config.get("name", "database")
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def engine(self) -> str:
|
|
33
|
-
"""Database engine"""
|
|
34
|
-
return self.__config.get("engine", "postgres")
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def engine_version(self) -> str:
|
|
38
|
-
"""Database engine version"""
|
|
39
|
-
engine_version = self.__config.get("engine_version")
|
|
40
|
-
if not engine_version:
|
|
41
|
-
raise ValueError("No engine version found")
|
|
42
|
-
return engine_version
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def instance_class(self) -> str:
|
|
46
|
-
"""Database instance class"""
|
|
47
|
-
return self.__config.get("instance_class", "t3.micro")
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def database_name(self) -> str:
|
|
51
|
-
"""Name of the database to create (sanitized for RDS requirements)"""
|
|
52
|
-
raw_name = self.__config.get("database_name", "appdb")
|
|
53
|
-
return self._sanitize_database_name(raw_name)
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def username(self) -> str:
|
|
57
|
-
"""Master username for the database (sanitized for RDS requirements)"""
|
|
58
|
-
raw_username = self.__config.get("username", "appuser")
|
|
59
|
-
return self._sanitize_username(raw_username)
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def secret_name(self) -> str:
|
|
63
|
-
"""Name of the secret to store credentials"""
|
|
64
|
-
env_name = self.__deployment.environment if self.__deployment else None
|
|
65
|
-
if not env_name:
|
|
66
|
-
raise ValueError("No environment found for RDS secret name. Please add an environment to the deployment.")
|
|
67
|
-
return self.__config.get("secret_name", f"/{env_name}/db/creds")
|
|
68
|
-
|
|
69
|
-
@property
|
|
70
|
-
def allocated_storage(self) -> int:
|
|
71
|
-
"""Allocated storage in GB"""
|
|
72
|
-
# Ensure we return an integer
|
|
73
|
-
return int(self.__config.get("allocated_storage", 20))
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def storage_encrypted(self) -> bool:
|
|
77
|
-
"""Whether storage is encrypted"""
|
|
78
|
-
return self.__config.get("storage_encrypted", True)
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def multi_az(self) -> bool:
|
|
82
|
-
"""Whether to enable Multi-AZ deployment"""
|
|
83
|
-
return self.__config.get("multi_az", False)
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def backup_retention(self) -> int:
|
|
87
|
-
"""Backup retention period in days"""
|
|
88
|
-
return self.__config.get("backup_retention", 7)
|
|
89
|
-
|
|
90
|
-
@property
|
|
91
|
-
def deletion_protection(self) -> bool:
|
|
92
|
-
"""Whether deletion protection is enabled"""
|
|
93
|
-
return self.__config.get("deletion_protection", False)
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def enable_performance_insights(self) -> bool:
|
|
97
|
-
"""Whether to enable Performance Insights"""
|
|
98
|
-
return self.__config.get("enable_performance_insights", True)
|
|
99
|
-
|
|
100
|
-
@property
|
|
101
|
-
def subnet_group_name(self) -> str:
|
|
102
|
-
"""Subnet group name for database placement"""
|
|
103
|
-
return self.__config.get("subnet_group_name", "db")
|
|
104
|
-
|
|
105
|
-
@property
|
|
106
|
-
def security_group_ids(self) -> List[str]:
|
|
107
|
-
"""Security group IDs for the database"""
|
|
108
|
-
return self.__config.get("security_group_ids", [])
|
|
109
|
-
|
|
110
|
-
@property
|
|
111
|
-
def cloudwatch_logs_exports(self) -> List[str]:
|
|
112
|
-
"""Log types to export to CloudWatch"""
|
|
113
|
-
return self.__config.get("cloudwatch_logs_exports", ["postgresql"])
|
|
114
|
-
|
|
115
|
-
@property
|
|
116
|
-
def removal_policy(self) -> str:
|
|
117
|
-
"""Removal policy for the database"""
|
|
118
|
-
return self.__config.get("removal_policy", "retain")
|
|
119
|
-
|
|
120
|
-
@property
|
|
121
|
-
def existing_instance_id(self) -> Optional[str]:
|
|
122
|
-
"""Existing RDS instance ID to import (if using existing)"""
|
|
123
|
-
return self.__config.get("existing_instance_id")
|
|
124
|
-
|
|
125
|
-
@property
|
|
126
|
-
def tags(self) -> Dict[str, str]:
|
|
127
|
-
"""Tags to apply to the RDS instance"""
|
|
128
|
-
return self.__config.get("tags", {})
|
|
129
|
-
|
|
130
|
-
@property
|
|
131
|
-
def vpc_id(self) -> str | None:
|
|
132
|
-
"""Returns the VPC ID for the Security Group"""
|
|
133
|
-
return self.__config.get("vpc_id")
|
|
134
|
-
|
|
135
|
-
@vpc_id.setter
|
|
136
|
-
def vpc_id(self, value: str):
|
|
137
|
-
"""Sets the VPC ID for the Security Group"""
|
|
138
|
-
self.__config["vpc_id"] = value
|
|
139
|
-
|
|
140
|
-
@property
|
|
141
|
-
def ssm_imports(self) -> Dict[str, str]:
|
|
142
|
-
"""SSM parameter imports for the RDS instance"""
|
|
143
|
-
# Check both nested and flat structures for backwards compatibility
|
|
144
|
-
if "ssm" in self.__config and "imports" in self.__config["ssm"]:
|
|
145
|
-
return self.__config["ssm"]["imports"]
|
|
146
|
-
return self.__config.get("ssm_imports", {})
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def ssm_exports(self) -> Dict[str, str]:
|
|
150
|
-
"""SSM parameter exports for the RDS instance"""
|
|
151
|
-
# Check both nested and flat structures for backwards compatibility
|
|
152
|
-
if "ssm" in self.__config and "exports" in self.__config["ssm"]:
|
|
153
|
-
return self.__config["ssm"]["exports"]
|
|
154
|
-
return self.__config.get("ssm_exports", {})
|
|
155
|
-
|
|
156
|
-
def _sanitize_database_name(self, name: str) -> str:
|
|
157
|
-
"""
|
|
158
|
-
Sanitize database name to meet RDS requirements:
|
|
159
|
-
- Must begin with a letter (a-z, A-Z)
|
|
160
|
-
- Can contain alphanumeric characters and underscores
|
|
161
|
-
- Max 64 characters
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
name: Raw database name from config
|
|
165
|
-
|
|
166
|
-
Returns:
|
|
167
|
-
Sanitized database name
|
|
168
|
-
|
|
169
|
-
Raises:
|
|
170
|
-
ValueError: If name starts with a number or is empty after sanitization
|
|
171
|
-
"""
|
|
172
|
-
if not name:
|
|
173
|
-
raise ValueError("Database name cannot be empty")
|
|
174
|
-
|
|
175
|
-
# Replace hyphens with underscores, remove other invalid chars
|
|
176
|
-
sanitized = name.replace('-', '_')
|
|
177
|
-
sanitized = re.sub(r'[^a-zA-Z0-9_]', '', sanitized)
|
|
178
|
-
|
|
179
|
-
if not sanitized:
|
|
180
|
-
raise ValueError(f"Database name '{name}' contains no valid characters")
|
|
181
|
-
|
|
182
|
-
# Check if it starts with a number
|
|
183
|
-
if sanitized[0].isdigit():
|
|
184
|
-
raise ValueError(
|
|
185
|
-
f"Database name '{name}' (sanitized to '{sanitized}') cannot start with a number. "
|
|
186
|
-
f"Please ensure the database name begins with a letter."
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# Truncate to 64 characters if needed
|
|
190
|
-
if len(sanitized) > 64:
|
|
191
|
-
sanitized = sanitized[:64]
|
|
192
|
-
|
|
193
|
-
# Log if sanitization changed the name
|
|
194
|
-
if sanitized != name:
|
|
195
|
-
logger.info(f"Sanitized database name from '{name}' to '{sanitized}'")
|
|
196
|
-
|
|
197
|
-
return sanitized
|
|
198
|
-
|
|
199
|
-
def _sanitize_username(self, username: str) -> str:
|
|
200
|
-
"""
|
|
201
|
-
Sanitize username to meet RDS requirements:
|
|
202
|
-
- Must begin with a letter (a-z, A-Z)
|
|
203
|
-
- Can contain alphanumeric characters and underscores
|
|
204
|
-
- Max 16 characters for MySQL
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
username: Raw username from config
|
|
208
|
-
|
|
209
|
-
Returns:
|
|
210
|
-
Sanitized username
|
|
211
|
-
|
|
212
|
-
Raises:
|
|
213
|
-
ValueError: If username is invalid
|
|
214
|
-
"""
|
|
215
|
-
if not username:
|
|
216
|
-
raise ValueError("Username cannot be empty")
|
|
217
|
-
|
|
218
|
-
# Replace hyphens with underscores, remove other invalid chars
|
|
219
|
-
sanitized = username.replace('-', '_')
|
|
220
|
-
sanitized = re.sub(r'[^a-zA-Z0-9_]', '', sanitized)
|
|
221
|
-
|
|
222
|
-
if not sanitized:
|
|
223
|
-
raise ValueError(f"Username '{username}' contains no valid characters")
|
|
224
|
-
|
|
225
|
-
# Check if it starts with a number
|
|
226
|
-
if sanitized[0].isdigit():
|
|
227
|
-
raise ValueError(
|
|
228
|
-
f"Username '{username}' (sanitized to '{sanitized}') cannot start with a number. "
|
|
229
|
-
f"Please ensure the username begins with a letter."
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
# Truncate to 16 characters for MySQL (other engines may vary)
|
|
233
|
-
if len(sanitized) > 16:
|
|
234
|
-
sanitized = sanitized[:16]
|
|
235
|
-
|
|
236
|
-
# Log if sanitization changed the username
|
|
237
|
-
if sanitized != username:
|
|
238
|
-
logger.info(f"Sanitized username from '{username}' to '{sanitized}'")
|
|
239
|
-
|
|
240
|
-
return sanitized
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.15.12"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|