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.

@@ -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
- - Must begin with a letter (a-z, A-Z)
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 starts with a number or is empty after sanitization
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
- # Replace hyphens with underscores, remove other invalid chars
176
- sanitized = name.replace('-', '_')
177
- sanitized = re.sub(r'[^a-zA-Z0-9_]', '', sanitized)
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
- if not sanitized:
180
- raise ValueError(f"Database name '{name}' contains no valid characters")
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
- # 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
- )
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
- # Truncate to 64 characters if needed
190
- if len(sanitized) > 64:
191
- sanitized = sanitized[:64]
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
- # Log if sanitization changed the name
194
- if sanitized != name:
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
- return sanitized
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 for MySQL
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
- sanitized = username.replace('-', '_')
220
- sanitized = re.sub(r'[^a-zA-Z0-9_]', '', sanitized)
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 sanitized:
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
- # 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
- )
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 for MySQL (other engines may vary)
233
- if len(sanitized) > 16:
234
- sanitized = sanitized[:16]
368
+ # Truncate to 16 characters
369
+ if len(s) > 16:
370
+ s = s[:16]
371
+ notes.append("truncated to 16 characters")
235
372
 
236
- # Log if sanitization changed the username
237
- if sanitized != username:
238
- logger.info(f"Sanitized username from '{username}' to '{sanitized}'")
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 sanitized
382
+ return s, notes
cdk_factory/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.15.12"
1
+ __version__ = "0.15.13"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.15.12
3
+ Version: 0.15.13
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
@@ -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=uhKEhmOyw-8a1XvaAZVr11Ygw-f1noUzBbHqYnFKzHE,24
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=IQyi5UFf3LEWgkp71XHkjdedqW-PWS2UvpjXJpp7df0,8557
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.12.dist-info/METADATA,sha256=krTwolq-UXam3vFCtadV1i4DPAOsprJnhuNlsRWacik,2452
133
- cdk_factory-0.15.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
134
- cdk_factory-0.15.12.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
135
- cdk_factory-0.15.12.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
136
- cdk_factory-0.15.12.dist-info/RECORD,,
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,,