cdk-factory 0.15.12__py3-none-any.whl → 0.15.13__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/resources/rds.py +185 -43
- cdk_factory/version.py +1 -1
- {cdk_factory-0.15.12.dist-info → cdk_factory-0.15.13.dist-info}/METADATA +1 -1
- {cdk_factory-0.15.12.dist-info → cdk_factory-0.15.13.dist-info}/RECORD +7 -7
- {cdk_factory-0.15.12.dist-info → cdk_factory-0.15.13.dist-info}/WHEEL +0 -0
- {cdk_factory-0.15.12.dist-info → cdk_factory-0.15.13.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.15.12.dist-info → cdk_factory-0.15.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,12 +5,15 @@ MIT License. See Project Root for license information.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, Literal
|
|
9
9
|
from aws_lambda_powertools import Logger
|
|
10
10
|
from cdk_factory.configurations.enhanced_base_config import EnhancedBaseConfig
|
|
11
11
|
|
|
12
12
|
logger = Logger(service="RdsConfig")
|
|
13
13
|
|
|
14
|
+
# Supported RDS engines
|
|
15
|
+
Engine = Literal["mysql", "mariadb", "postgres", "aurora-mysql", "aurora-postgres", "sqlserver", "oracle"]
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
class RdsConfig(EnhancedBaseConfig):
|
|
16
19
|
"""
|
|
@@ -27,6 +30,74 @@ class RdsConfig(EnhancedBaseConfig):
|
|
|
27
30
|
def name(self) -> str:
|
|
28
31
|
"""RDS instance name"""
|
|
29
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
|
|
30
101
|
|
|
31
102
|
@property
|
|
32
103
|
def engine(self) -> str:
|
|
@@ -155,10 +226,8 @@ class RdsConfig(EnhancedBaseConfig):
|
|
|
155
226
|
|
|
156
227
|
def _sanitize_database_name(self, name: str) -> str:
|
|
157
228
|
"""
|
|
158
|
-
Sanitize database name to meet RDS requirements
|
|
159
|
-
|
|
160
|
-
- Can contain alphanumeric characters and underscores
|
|
161
|
-
- Max 64 characters
|
|
229
|
+
Sanitize database name to meet RDS requirements (engine-specific).
|
|
230
|
+
Implements rules from RDS documentation for each engine type.
|
|
162
231
|
|
|
163
232
|
Args:
|
|
164
233
|
name: Raw database name from config
|
|
@@ -167,41 +236,89 @@ class RdsConfig(EnhancedBaseConfig):
|
|
|
167
236
|
Sanitized database name
|
|
168
237
|
|
|
169
238
|
Raises:
|
|
170
|
-
ValueError: If name
|
|
239
|
+
ValueError: If name cannot be sanitized to meet requirements
|
|
171
240
|
"""
|
|
172
241
|
if not name:
|
|
173
242
|
raise ValueError("Database name cannot be empty")
|
|
174
243
|
|
|
175
|
-
|
|
176
|
-
sanitized =
|
|
177
|
-
|
|
244
|
+
engine = self.engine.lower()
|
|
245
|
+
sanitized, notes = self._sanitize_db_name_impl(engine, name)
|
|
246
|
+
|
|
247
|
+
if notes:
|
|
248
|
+
logger.info(f"Sanitized database name from '{name}' to '{sanitized}': {', '.join(notes)}")
|
|
249
|
+
|
|
250
|
+
return sanitized
|
|
251
|
+
|
|
252
|
+
def _sanitize_db_name_impl(self, engine: str, name: str) -> Tuple[str, List[str]]:
|
|
253
|
+
"""
|
|
254
|
+
Engine-specific database name sanitization.
|
|
255
|
+
Based on AWS RDS naming requirements:
|
|
256
|
+
- MySQL/MariaDB: 1-64 chars, start with letter, letters/digits/underscore
|
|
257
|
+
- PostgreSQL: 1-63 chars, start with letter, letters/digits/underscore
|
|
258
|
+
- SQL Server: 1-128 chars, start with letter, letters/digits/underscore
|
|
259
|
+
- Oracle: 1-8 chars (SID), alphanumeric only, start with letter
|
|
260
|
+
"""
|
|
261
|
+
notes: List[str] = []
|
|
178
262
|
|
|
179
|
-
|
|
180
|
-
|
|
263
|
+
# Determine engine-specific limits
|
|
264
|
+
if engine in ("mysql", "mariadb", "aurora-mysql"):
|
|
265
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
266
|
+
max_len = 64
|
|
267
|
+
elif engine in ("postgres", "postgresql", "aurora-postgres", "aurora-postgresql"):
|
|
268
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
269
|
+
max_len = 63
|
|
270
|
+
elif engine in ("sqlserver", "sqlserver-ee", "sqlserver-se", "sqlserver-ex", "sqlserver-web"):
|
|
271
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
272
|
+
max_len = 128
|
|
273
|
+
elif engine in ("oracle", "oracle-ee", "oracle-se2", "oracle-se1"):
|
|
274
|
+
allowed_chars = r"A-Za-z0-9" # No underscore for Oracle SID
|
|
275
|
+
max_len = 8
|
|
276
|
+
else:
|
|
277
|
+
# Default to conservative rules
|
|
278
|
+
allowed_chars = r"A-Za-z0-9_"
|
|
279
|
+
max_len = 64
|
|
280
|
+
notes.append(f"unknown engine '{engine}', using default MySQL rules")
|
|
181
281
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
282
|
+
# Replace hyphens with underscores (except Oracle which doesn't allow underscores)
|
|
283
|
+
s = name
|
|
284
|
+
if "oracle" not in engine:
|
|
285
|
+
s = s.replace("-", "_")
|
|
286
|
+
if "_" in name and "-" in name:
|
|
287
|
+
notes.append("replaced hyphens with underscores")
|
|
188
288
|
|
|
189
|
-
#
|
|
190
|
-
|
|
191
|
-
|
|
289
|
+
# Strip disallowed characters
|
|
290
|
+
s_clean = re.sub(f"[^{allowed_chars}]", "", s)
|
|
291
|
+
if s_clean != s:
|
|
292
|
+
notes.append("removed invalid characters")
|
|
293
|
+
s = s_clean
|
|
192
294
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
logger.info(f"Sanitized database name from '{name}' to '{sanitized}'")
|
|
295
|
+
if not s:
|
|
296
|
+
raise ValueError(f"Database name '{name}' contains no valid characters after sanitization")
|
|
196
297
|
|
|
197
|
-
|
|
298
|
+
# Must start with a letter
|
|
299
|
+
if not re.match(r"^[A-Za-z]", s):
|
|
300
|
+
s = f"db{s}"
|
|
301
|
+
notes.append("prefixed with 'db' to start with a letter")
|
|
302
|
+
|
|
303
|
+
# Truncate to max length
|
|
304
|
+
if len(s) > max_len:
|
|
305
|
+
s = s[:max_len]
|
|
306
|
+
notes.append(f"truncated to {max_len} characters")
|
|
307
|
+
|
|
308
|
+
# SQL Server: can't start with 'rdsadmin'
|
|
309
|
+
if "sqlserver" in engine and s.lower().startswith("rdsadmin"):
|
|
310
|
+
s = f"db_{s}"
|
|
311
|
+
notes.append("prefixed to avoid 'rdsadmin' (SQL Server restriction)")
|
|
312
|
+
|
|
313
|
+
return s, notes
|
|
198
314
|
|
|
199
315
|
def _sanitize_username(self, username: str) -> str:
|
|
200
316
|
"""
|
|
201
|
-
Sanitize username to meet RDS requirements:
|
|
317
|
+
Sanitize master username to meet RDS requirements:
|
|
202
318
|
- Must begin with a letter (a-z, A-Z)
|
|
203
319
|
- Can contain alphanumeric characters and underscores
|
|
204
|
-
- Max 16 characters
|
|
320
|
+
- Max 16 characters (AWS RDS master username limit)
|
|
321
|
+
- Cannot be a reserved word
|
|
205
322
|
|
|
206
323
|
Args:
|
|
207
324
|
username: Raw username from config
|
|
@@ -215,26 +332,51 @@ class RdsConfig(EnhancedBaseConfig):
|
|
|
215
332
|
if not username:
|
|
216
333
|
raise ValueError("Username cannot be empty")
|
|
217
334
|
|
|
335
|
+
sanitized, notes = self._sanitize_master_username_impl(username)
|
|
336
|
+
|
|
337
|
+
if notes:
|
|
338
|
+
logger.info(f"Sanitized username from '{username}' to '{sanitized}': {', '.join(notes)}")
|
|
339
|
+
|
|
340
|
+
return sanitized
|
|
341
|
+
|
|
342
|
+
def _sanitize_master_username_impl(self, username: str) -> Tuple[str, List[str]]:
|
|
343
|
+
"""
|
|
344
|
+
Sanitize master username according to AWS RDS rules:
|
|
345
|
+
- 1-16 characters
|
|
346
|
+
- Start with a letter
|
|
347
|
+
- Letters, digits, underscore only
|
|
348
|
+
- Not a reserved word
|
|
349
|
+
"""
|
|
350
|
+
notes: List[str] = []
|
|
351
|
+
s = username
|
|
352
|
+
|
|
218
353
|
# Replace hyphens with underscores, remove other invalid chars
|
|
219
|
-
|
|
220
|
-
|
|
354
|
+
s = s.replace("-", "_")
|
|
355
|
+
s_clean = re.sub(r"[^A-Za-z0-9_]", "", s)
|
|
356
|
+
if s_clean != s:
|
|
357
|
+
notes.append("removed invalid characters")
|
|
358
|
+
s = s_clean
|
|
221
359
|
|
|
222
|
-
if not
|
|
223
|
-
raise ValueError(f"Username '{username}' contains no valid characters")
|
|
360
|
+
if not s:
|
|
361
|
+
raise ValueError(f"Username '{username}' contains no valid characters after sanitization")
|
|
224
362
|
|
|
225
|
-
#
|
|
226
|
-
if
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
f"Please ensure the username begins with a letter."
|
|
230
|
-
)
|
|
363
|
+
# Must start with a letter
|
|
364
|
+
if not re.match(r"^[A-Za-z]", s):
|
|
365
|
+
s = f"user{s}"
|
|
366
|
+
notes.append("prefixed with 'user' to start with a letter")
|
|
231
367
|
|
|
232
|
-
# Truncate to 16 characters
|
|
233
|
-
if len(
|
|
234
|
-
|
|
368
|
+
# Truncate to 16 characters
|
|
369
|
+
if len(s) > 16:
|
|
370
|
+
s = s[:16]
|
|
371
|
+
notes.append("truncated to 16 characters")
|
|
235
372
|
|
|
236
|
-
#
|
|
237
|
-
|
|
238
|
-
|
|
373
|
+
# Check against common reserved words
|
|
374
|
+
reserved = {"postgres", "mysql", "root", "admin", "rdsadmin", "system", "sa", "user"}
|
|
375
|
+
if s.lower() in reserved:
|
|
376
|
+
s = f"{s}_usr"
|
|
377
|
+
# Re-truncate if needed after adding suffix
|
|
378
|
+
if len(s) > 16:
|
|
379
|
+
s = s[:16]
|
|
380
|
+
notes.append("appended '_usr' to avoid reserved username")
|
|
239
381
|
|
|
240
|
-
return
|
|
382
|
+
return s, notes
|
cdk_factory/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.15.
|
|
1
|
+
__version__ = "0.15.13"
|
|
@@ -2,7 +2,7 @@ cdk_factory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
cdk_factory/app.py,sha256=RnX0-pwdTAPAdKJK_j13Zl8anf9zYKBwboR0KA8K8xM,10346
|
|
3
3
|
cdk_factory/cdk.json,sha256=SKZKhJ2PBpFH78j-F8S3VDYW-lf76--Q2I3ON-ZIQfw,3106
|
|
4
4
|
cdk_factory/cli.py,sha256=FGbCTS5dYCNsfp-etshzvFlGDCjC28r6rtzYbe7KoHI,6407
|
|
5
|
-
cdk_factory/version.py,sha256=
|
|
5
|
+
cdk_factory/version.py,sha256=6AIqVLeKq3uS7QeuesqvTQrN9gWiCBFTAygmSJRA6-0,24
|
|
6
6
|
cdk_factory/builds/README.md,sha256=9BBWd7bXpyKdMU_g2UljhQwrC9i5O_Tvkb6oPvndoZk,90
|
|
7
7
|
cdk_factory/commands/command_loader.py,sha256=QbLquuP_AdxtlxlDy-2IWCQ6D-7qa58aphnDPtp_uTs,3744
|
|
8
8
|
cdk_factory/configurations/base_config.py,sha256=JKjhNsy0RCUZy1s8n5D_aXXI-upR9izaLtCTfKYiV9k,9624
|
|
@@ -38,7 +38,7 @@ cdk_factory/configurations/resources/lambda_layers.py,sha256=gVeP_-LC3Eq0lkPaG_J
|
|
|
38
38
|
cdk_factory/configurations/resources/lambda_triggers.py,sha256=MD7cdMNKEulNBhtMLIFnWJuJ5R-yyIqa0LHUgbSQerA,834
|
|
39
39
|
cdk_factory/configurations/resources/load_balancer.py,sha256=idpKdvkkCM7J9J2pNjMBOY1DNaFR1tk1tFjTg76bvrY,5267
|
|
40
40
|
cdk_factory/configurations/resources/monitoring.py,sha256=zsfDMa7yph33Ql8iP7lIqqLAyixh-Mesi0imtZJFdcE,2310
|
|
41
|
-
cdk_factory/configurations/resources/rds.py,sha256=
|
|
41
|
+
cdk_factory/configurations/resources/rds.py,sha256=lSj6BqAmx1knpu3Kv1Fq-tw-lTF94NFzEFGgWcMMIFI,14009
|
|
42
42
|
cdk_factory/configurations/resources/resource_mapping.py,sha256=cwv3n63RJ6E59ErsmSTdkW4i-g8huhHtKI0ExbRhJxA,2182
|
|
43
43
|
cdk_factory/configurations/resources/resource_naming.py,sha256=VE9S2cpzp11qqPL2z1sX79wXH0o1SntO2OG74nEmWC8,5508
|
|
44
44
|
cdk_factory/configurations/resources/resource_types.py,sha256=1WQHyDoErb-M-tETZZzyLDtbq_jdC85-I403dM48pgE,2317
|
|
@@ -129,8 +129,8 @@ cdk_factory/utilities/lambda_function_utilities.py,sha256=S1GvBsY_q2cyUiaud3HORJ
|
|
|
129
129
|
cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITEP7EuSU,1019
|
|
130
130
|
cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
|
|
131
131
|
cdk_factory/workload/workload_factory.py,sha256=yDI3cRhVI5ELNDcJPLpk9UY54Uind1xQoV3spzT4z7E,6068
|
|
132
|
-
cdk_factory-0.15.
|
|
133
|
-
cdk_factory-0.15.
|
|
134
|
-
cdk_factory-0.15.
|
|
135
|
-
cdk_factory-0.15.
|
|
136
|
-
cdk_factory-0.15.
|
|
132
|
+
cdk_factory-0.15.13.dist-info/METADATA,sha256=ZEIwXiuhfk9EeWo0OgSz3-yK3sl2l7rVPLPLE9cVC2w,2452
|
|
133
|
+
cdk_factory-0.15.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
134
|
+
cdk_factory-0.15.13.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
|
|
135
|
+
cdk_factory-0.15.13.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
|
|
136
|
+
cdk_factory-0.15.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|