cdk-factory 0.19.6__py3-none-any.whl → 0.19.10__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.
@@ -52,7 +52,7 @@ class LambdaEdgeConfig(EnhancedBaseConfig):
52
52
  """Timeout in seconds (max 5 for origin-request)"""
53
53
  timeout = int(self._config.get("timeout", 5))
54
54
  if timeout > 5:
55
- raise ValueError("Lambda@Edge origin-request timeout cannot exceed 5 seconds")
55
+ raise ValueError("Lambda@Edge origin-request timeout cannot exceed 5 seconds. Value was set to {}".format(timeout))
56
56
  return timeout
57
57
 
58
58
  @property
@@ -145,7 +145,7 @@ class CloudFrontStack(IStack):
145
145
  return
146
146
 
147
147
  # Check if certificate ARN is provided
148
- cert_arn = cert_config.get("arn")
148
+ cert_arn = self.resolve_ssm_value(self, cert_config.get("arn"), "CertificateARN")
149
149
  if cert_arn:
150
150
  self.certificate = acm.Certificate.from_certificate_arn(
151
151
  self, "Certificate", certificate_arn=cert_arn
@@ -173,14 +173,14 @@ class CloudFrontStack(IStack):
173
173
  "CloudFront certificates must be created in us-east-1"
174
174
  )
175
175
  return
176
-
176
+
177
177
  # Create the certificate
178
178
  # Get hosted zone from SSM imports
179
179
  hosted_zone_id = cert_config.get("hosted_zone_id")
180
180
  hosted_zone = route53.HostedZone.from_hosted_zone_id(
181
181
  self, "HostedZone", hosted_zone_id
182
182
  )
183
-
183
+
184
184
  self.certificate = acm.Certificate(
185
185
  self,
186
186
  "Certificate",
@@ -223,27 +223,29 @@ class CloudFrontStack(IStack):
223
223
 
224
224
  def _create_custom_origin(self, config: Dict[str, Any]) -> cloudfront.IOrigin:
225
225
  """Create custom origin (ALB, API Gateway, etc.)"""
226
- domain_name = config.get("domain_name")
226
+ domain_name = self.resolve_ssm_value(
227
+ self, config.get("domain_name"), config.get("domain_name")
228
+ )
227
229
  origin_id = config.get("id")
228
230
 
229
231
  if not domain_name:
230
232
  raise ValueError("domain_name is required for custom origin")
231
233
 
232
- # Check if domain name is a placeholder from ssm_imports
233
- if domain_name.startswith("{{") and domain_name.endswith("}}"):
234
- placeholder_key = domain_name[2:-2] # Remove {{ and }}
235
- if placeholder_key in self.ssm_imported_values:
236
- domain_name = self.ssm_imported_values[placeholder_key]
237
- logger.info(f"Resolved domain from SSM import: {placeholder_key}")
238
- else:
239
- logger.warning(f"Placeholder {domain_name} not found in SSM imports")
240
-
241
- # Legacy support: Check if domain name is an SSM parameter reference
242
- elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
243
- # Extract SSM parameter name
244
- ssm_param = domain_name[6:-2] # Remove {{ssm: and }}
245
- domain_name = ssm.StringParameter.value_from_lookup(self, ssm_param)
246
- logger.info(f"Resolved domain from SSM lookup {ssm_param}: {domain_name}")
234
+ # # Check if domain name is a placeholder from ssm_imports
235
+ # if domain_name.startswith("{{") and domain_name.endswith("}}"):
236
+ # placeholder_key = domain_name[2:-2] # Remove {{ and }}
237
+ # if placeholder_key in self.ssm_imported_values:
238
+ # domain_name = self.ssm_imported_values[placeholder_key]
239
+ # logger.info(f"Resolved domain from SSM import: {placeholder_key}")
240
+ # else:
241
+ # logger.warning(f"Placeholder {domain_name} not found in SSM imports")
242
+
243
+ # # Legacy support: Check if domain name is an SSM parameter reference
244
+ # elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
245
+ # # Extract SSM parameter name
246
+ # ssm_param = domain_name[6:-2] # Remove {{ssm: and }}
247
+ # domain_name = ssm.StringParameter.value_from_lookup(self, ssm_param)
248
+ # logger.info(f"Resolved domain from SSM lookup {ssm_param}: {domain_name}")
247
249
 
248
250
  # Build custom headers (e.g., X-Origin-Secret)
249
251
  custom_headers = {}
@@ -297,20 +299,22 @@ class CloudFrontStack(IStack):
297
299
 
298
300
  def _create_s3_origin(self, config: Dict[str, Any]) -> cloudfront.IOrigin:
299
301
  """Create S3 origin"""
300
- bucket_name = config.get("bucket_name")
301
- domain_name = config.get("domain_name")
302
+ bucket_name = self.resolve_ssm_value(
303
+ self, config.get("bucket_name"), config.get("bucket_name")
304
+ )
305
+
302
306
  origin_path = config.get("origin_path", "")
303
-
307
+
304
308
  if not bucket_name:
305
309
  raise ValueError("S3 origin requires 'bucket_name' configuration")
306
-
310
+
307
311
  # For S3 origins, we need to import the bucket by name
308
312
  bucket = s3.Bucket.from_bucket_name(
309
313
  self,
310
314
  id=f"S3OriginBucket-{config.get('id', 'unknown')}",
311
- bucket_name=bucket_name
315
+ bucket_name=bucket_name,
312
316
  )
313
-
317
+
314
318
  # Create S3 origin with OAC (Origin Access Control) for security
315
319
  origin = origins.S3BucketOrigin.with_origin_access_control(
316
320
  bucket,
@@ -320,7 +324,7 @@ class CloudFrontStack(IStack):
320
324
  cloudfront.AccessLevel.LIST,
321
325
  ],
322
326
  )
323
-
327
+
324
328
  return origin
325
329
 
326
330
  def _create_distribution(self) -> None:
@@ -226,6 +226,9 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
226
226
  "CloudWatchAgentServerPolicy"
227
227
  )
228
228
  )
229
+
230
+ # add any custom policies
231
+ self._add_custom_task_policies(task_role)
229
232
 
230
233
  # Create task definition based on launch type
231
234
  if self.ecs_config.launch_type == "EC2":
@@ -257,6 +260,37 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
257
260
  # Add containers
258
261
  self._add_containers_to_task()
259
262
 
263
+ def _add_custom_task_policies(self, task_role: iam.Role) -> None:
264
+ """
265
+ Add custom task policies to the task definition.
266
+ """
267
+ for policy in self.ecs_config.task_definition.get("policies", []):
268
+
269
+ effect = policy.get("effect", "Allow")
270
+ action = policy.get("action", None)
271
+ actions = policy.get("actions", [])
272
+ if action:
273
+ actions.append(action)
274
+ resources = policy.get("resources", [])
275
+ resource = policy.get("resource", None)
276
+ if resource:
277
+ resources.append(resource)
278
+
279
+ if effect == "Allow" and actions:
280
+ effect = iam.Effect.ALLOW
281
+ if effect == "Deny" and actions:
282
+ effect = iam.Effect.DENY
283
+
284
+ sid = policy.get("sid", None)
285
+ task_role.add_to_policy(
286
+ iam.PolicyStatement(
287
+ effect=effect,
288
+ actions=actions,
289
+ resources=resources,
290
+ sid=sid,
291
+ )
292
+ )
293
+
260
294
  def _add_volumes_to_task(self) -> None:
261
295
  """
262
296
  Add volumes to the task definition.
@@ -53,6 +53,8 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
53
53
  self.workload: Optional[WorkloadConfig] = None
54
54
  self.function: Optional[_lambda.Function] = None
55
55
  self.function_version: Optional[_lambda.Version] = None
56
+ # Cache for resolved environment variables to prevent duplicate construct creation
57
+ self._resolved_env_cache: Optional[Dict[str, str]] = None
56
58
 
57
59
  def build(
58
60
  self,
@@ -101,12 +103,32 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
101
103
  # Add outputs
102
104
  self._add_outputs(function_name)
103
105
 
106
+ def _sanitize_construct_name(self, name: str) -> str:
107
+ """
108
+ Create a deterministic, valid CDK construct name from any string.
109
+ Replaces non-alphanumeric characters with dashes and limits length.
110
+ """
111
+ # Replace non-alphanumeric characters with dashes
112
+ sanitized = ''.join(c if c.isalnum() else '-' for c in name)
113
+ # Remove consecutive dashes
114
+ while '--' in sanitized:
115
+ sanitized = sanitized.replace('--', '-')
116
+ # Remove leading/trailing dashes
117
+ sanitized = sanitized.strip('-')
118
+ # Limit to 255 characters (CDK limit)
119
+ return sanitized[:255]
120
+
104
121
  def _resolve_environment_variables(self) -> Dict[str, str]:
105
122
  """
106
123
  Resolve environment variables, including SSM parameter references.
107
124
  Supports {{ssm:parameter-path}} syntax for dynamic SSM lookups.
108
125
  Uses CDK tokens that resolve at deployment time, not synthesis time.
126
+ Caches results to prevent duplicate construct creation.
109
127
  """
128
+ # Return cached result if available
129
+ if self._resolved_env_cache is not None:
130
+ return self._resolved_env_cache
131
+
110
132
  resolved_env = {}
111
133
 
112
134
  for key, value in self.edge_config.environment.items():
@@ -115,18 +137,23 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
115
137
  # Extract SSM parameter path
116
138
  ssm_param_path = value[6:-2] # Remove {{ssm: and }}
117
139
 
140
+ # Create deterministic construct name from parameter path
141
+ construct_name = self._sanitize_construct_name(f"env-{key}-{ssm_param_path}")
142
+
118
143
  # Import SSM parameter - this creates a token that resolves at deployment time
119
144
  param = ssm.StringParameter.from_string_parameter_name(
120
145
  self,
121
- f"env-{key}-{hash(ssm_param_path) % 10000}",
146
+ construct_name,
122
147
  ssm_param_path
123
148
  )
124
149
  resolved_value = param.string_value
125
- logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path}")
150
+ logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path} as {construct_name}")
126
151
  resolved_env[key] = resolved_value
127
152
  else:
128
153
  resolved_env[key] = value
129
154
 
155
+ # Cache the result
156
+ self._resolved_env_cache = resolved_env
130
157
  return resolved_env
131
158
 
132
159
  def _create_lambda_function(self, function_name: str) -> None:
@@ -185,10 +212,12 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
185
212
  # Create runtime configuration file for Lambda@Edge
186
213
  # Since Lambda@Edge doesn't support environment variables, we bundle a config file
187
214
  # Use the full function_name (e.g., "tech-talk-dev-ip-gate") not just the base name
215
+ resolved_env = self._resolve_environment_variables()
188
216
  runtime_config = {
189
217
  'environment': self.deployment.environment,
190
218
  'function_name': function_name,
191
- 'region': self.deployment.region
219
+ 'region': self.deployment.region,
220
+ 'environment_variables': resolved_env # Add actual environment variables
192
221
  }
193
222
 
194
223
  runtime_config_path = temp_code_dir / 'runtime_config.json'
cdk_factory/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.19.6"
1
+ __version__ = "0.19.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.19.6
3
+ Version: 0.19.10
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=OGnUZIVVBzlbyfyVu63Sse_GiorSq8FdH43iLi6_7qs,23
5
+ cdk_factory/version.py,sha256=gIAvq9SB6YStPs9R4fOTA5ElNAzsxGid7xxii31j-fI,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=eJ3Pl3GWk1jVr_bYQaaWlw4_-ZiFGaiXllI_fOOX1i0,9323
@@ -34,7 +34,7 @@ cdk_factory/configurations/resources/ecr.py,sha256=iJEtKqBT7vQU0LU4urIglraIR7cPZ
34
34
  cdk_factory/configurations/resources/ecs_cluster.py,sha256=mQYJu7SUPDl5E4dMR6HCPFoWvFA3RGIb0iMNn-K7LX8,3635
35
35
  cdk_factory/configurations/resources/ecs_service.py,sha256=bOWjVECd6Kbc5NGGSnDaopnKrjRsUfmaZ6-qrsmTs3Q,6468
36
36
  cdk_factory/configurations/resources/exisiting.py,sha256=EVOLnkB-DGfTlmDgyQ5DD5k2zYfpFxqI3gugDR7mifI,478
37
- cdk_factory/configurations/resources/lambda_edge.py,sha256=B3hBoRMiewXnmjQBQS3faxr24afnTzdmpIQQTIubJJY,3410
37
+ cdk_factory/configurations/resources/lambda_edge.py,sha256=1tzxNPIsUSbwrdBHWrmqg08S629U3IGHNaNjEaLO_r8,3447
38
38
  cdk_factory/configurations/resources/lambda_function.py,sha256=VENZ9-ABJ5mjcN8J8wdLH4KHDYr1kWO0iFDH0B2mJXA,14659
39
39
  cdk_factory/configurations/resources/lambda_layers.py,sha256=gVeP_-LC3Eq0lkPaG_JfFUwboM5evRPr99SfKj53m7A,633
40
40
  cdk_factory/configurations/resources/lambda_triggers.py,sha256=MD7cdMNKEulNBhtMLIFnWJuJ5R-yyIqa0LHUgbSQerA,834
@@ -91,7 +91,7 @@ cdk_factory/stack_library/aws_lambdas/lambda_stack.py,sha256=SFbBPvvCopbyiuYtq-O
91
91
  cdk_factory/stack_library/buckets/README.md,sha256=XkK3UNVtRLE7NtUvbhCOBBYUYi8hlrrSaI1s3GJVrqI,78
92
92
  cdk_factory/stack_library/buckets/bucket_stack.py,sha256=SLoZqSffAqmeBBEVUQg54D_8Ad5UKdkjEAmKAVgAqQo,1778
93
93
  cdk_factory/stack_library/cloudfront/__init__.py,sha256=Zfx50q4xIJ4ZEoVIzUBDTKbRE9DKDM6iyVIFhtQXvww,153
94
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256=irK8iP24WyPiHW59UmuGaVy1WvlSR93x0Odq57T3RKQ,32177
94
+ cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256=7cYPqoQyiXH6r3j9jp9oLXv1ZDixeCYPAXJtTOmagPc,32309
95
95
  cdk_factory/stack_library/code_artifact/code_artifact_stack.py,sha256=o86cmC_ZV82z-K7DoAR0u1nAieoTi-vxRF01tyJn-9M,5297
96
96
  cdk_factory/stack_library/cognito/cognito_stack.py,sha256=3tjKCNcIwXZn7fd4EDQdY6H9m6CnZohI4uTQ4TpacRQ,25327
97
97
  cdk_factory/stack_library/dynamodb/dynamodb_stack.py,sha256=-_Ij1zXIxUuZIWgdevam_1vD3LEJ6pFs9U0hmw0KwIw,6743
@@ -99,9 +99,9 @@ cdk_factory/stack_library/ecr/README.md,sha256=xw2wPx9WN03Y4BBwqvbi9lAFGNyaD1FUN
99
99
  cdk_factory/stack_library/ecr/ecr_stack.py,sha256=KLbd5WN5-ZiojsS5wJ4PX-tIL0cCylCSvXjO6sVrgWY,2102
100
100
  cdk_factory/stack_library/ecs/__init__.py,sha256=o5vGDtD_h-gVXb3-Ysr8xUNpEcMsnmMVgZv2Pupcdow,219
101
101
  cdk_factory/stack_library/ecs/ecs_cluster_stack.py,sha256=sAPTLU5CAwMoLTW_pNy_cd0OtVkfDR7IxxsSq5AE0yo,12091
102
- cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=oZdFfCPrLJpNwERCj1vr1pGq1AExOE6Tt4lwdTPQfaw,27049
102
+ cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=KB4YCIsMm5JIGM9Bm-bKcr3eX5xXFgnoA7jST_ekK44,28209
103
103
  cdk_factory/stack_library/lambda_edge/__init__.py,sha256=ByBJ_CWdc4UtTmFBZH-6pzBMNkjkdtE65AmnB0Fs6lM,156
104
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=qH1kiaL0u0v8jYTX0GinNhrPjnMpPXct3L5OLpNrMoY,15643
104
+ cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=7owFVRijjtyAAgwRWfVophJlwi9ATDon9ekkJdSQTNw,17050
105
105
  cdk_factory/stack_library/load_balancer/__init__.py,sha256=wZpKw2OecLJGdF5mPayCYAEhu2H3c2gJFFIxwXftGDU,52
106
106
  cdk_factory/stack_library/load_balancer/load_balancer_stack.py,sha256=ApW5q3SAvSJtiK0RInNljmubqXqKZU5QBAaUoeIW-pM,28287
107
107
  cdk_factory/stack_library/monitoring/__init__.py,sha256=k1G_KDx47Aw0UugaL99PN_TKlyLK4nkJVApCaAK7GJg,153
@@ -136,8 +136,8 @@ cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITE
136
136
  cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
137
137
  cdk_factory/validation/config_validator.py,sha256=Pb0TkLiPFzUplBOgMorhRCVm08vEzZhRU5xXCDTa5CA,17602
138
138
  cdk_factory/workload/workload_factory.py,sha256=yDI3cRhVI5ELNDcJPLpk9UY54Uind1xQoV3spzT4z7E,6068
139
- cdk_factory-0.19.6.dist-info/METADATA,sha256=mQeGQQTw6whmIV7RajVraVMHHxikLey4Jovf0EwONXU,2451
140
- cdk_factory-0.19.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
141
- cdk_factory-0.19.6.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
142
- cdk_factory-0.19.6.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
143
- cdk_factory-0.19.6.dist-info/RECORD,,
139
+ cdk_factory-0.19.10.dist-info/METADATA,sha256=DBpgjyIwcNUX78kwx-jdTlkL9okzQz57iaG23jbF6jU,2452
140
+ cdk_factory-0.19.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
141
+ cdk_factory-0.19.10.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
142
+ cdk_factory-0.19.10.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
143
+ cdk_factory-0.19.10.dist-info/RECORD,,