cdk-factory 0.15.16__py3-none-any.whl → 0.15.18__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.

@@ -117,8 +117,32 @@ class PipelineStageConfig:
117
117
  @property
118
118
  def builds(self) -> List[Dict[str, Any]]:
119
119
  """
120
- Returns the stages for this pipeline
121
- """
122
- builds = self.workload.get("builds", [])
123
-
124
- return builds
120
+ Returns the builds configured for this stage.
121
+
122
+ If the stage has a "builds" array of strings, resolve them to the
123
+ corresponding build objects defined at workload["builds"].
124
+ Otherwise, return an empty list.
125
+ """
126
+ stage_build_refs = self.dictionary.get("builds", [])
127
+ if not stage_build_refs:
128
+ return []
129
+
130
+ workload_builds: List[Dict[str, Any]] = self.workload.get("builds", [])
131
+ by_name: Dict[str, Dict[str, Any]] = {}
132
+ for b in workload_builds:
133
+ name = b.get("name") or b.get("id")
134
+ if name:
135
+ by_name[name] = b
136
+
137
+ resolved: List[Dict[str, Any]] = []
138
+ for ref in stage_build_refs:
139
+ if isinstance(ref, str):
140
+ if ref in by_name:
141
+ resolved.append(by_name[ref])
142
+ else:
143
+ raise ValueError(f"Build '{ref}' referenced by stage '{self.name}' not found in workload.builds")
144
+ elif isinstance(ref, dict):
145
+ # Allow inline build definitions at the stage level as a fallback
146
+ resolved.append(ref)
147
+
148
+ return resolved
@@ -84,6 +84,9 @@ class PipelineFactoryStack(IStack):
84
84
 
85
85
  self.deployment_waves: Dict[str, pipelines.Wave] = {}
86
86
 
87
+ # Cache created sources keyed by repo+branch to avoid duplicate node IDs
88
+ self._source_cache: Dict[str, pipelines.CodePipelineSource] = {}
89
+
87
90
  @property
88
91
  def aws_code_pipeline(self) -> pipelines.CodePipeline:
89
92
  """AWS Code Pipeline"""
@@ -207,8 +210,8 @@ class PipelineFactoryStack(IStack):
207
210
  stage_config=stage, pipeline_stage=pipeline_stage, deployment=deployment
208
211
  )
209
212
  # add the stacks to a wave or a regular
210
- pre_steps = self._get_pre_steps(stage)
211
- post_steps = self._get_post_steps(stage)
213
+ pre_steps = self._get_pre_steps(stage, deployment)
214
+ post_steps = self._get_post_steps(stage, deployment)
212
215
  wave_name = stage.wave_name
213
216
 
214
217
  # if we don't have any stacks we'll need to use the wave
@@ -237,16 +240,16 @@ class PipelineFactoryStack(IStack):
237
240
  )
238
241
 
239
242
  def _get_pre_steps(
240
- self, stage_config: PipelineStageConfig
243
+ self, stage_config: PipelineStageConfig, deployment: DeploymentConfig
241
244
  ) -> List[pipelines.ShellStep]:
242
- return self._get_steps("pre_steps", stage_config)
245
+ return self._get_steps("pre_steps", stage_config, deployment)
243
246
 
244
247
  def _get_post_steps(
245
- self, stage_config: PipelineStageConfig
248
+ self, stage_config: PipelineStageConfig, deployment: DeploymentConfig
246
249
  ) -> List[pipelines.ShellStep]:
247
- return self._get_steps("post_steps", stage_config)
250
+ return self._get_steps("post_steps", stage_config, deployment)
248
251
 
249
- def _get_steps(self, key: str, stage_config: PipelineStageConfig):
252
+ def _get_steps(self, key: str, stage_config: PipelineStageConfig, deployment: DeploymentConfig):
250
253
  """
251
254
  Gets the build steps from the config.json.
252
255
 
@@ -255,29 +258,204 @@ class PipelineFactoryStack(IStack):
255
258
  - A single multi-line string (treated as a single script block)
256
259
 
257
260
  This allows support for complex shell constructs like if blocks, loops, etc.
261
+
262
+ For builds with source/buildspec/environment, creates CodeBuildStep instead of ShellStep.
258
263
  """
259
- shell_steps: List[pipelines.ShellStep] = []
264
+ shell_steps: List[pipelines.Step] = []
265
+
266
+ # Only process builds if this stage explicitly defines them
267
+ if not stage_config.dictionary.get("builds"):
268
+ return shell_steps
260
269
 
261
270
  for build in stage_config.builds:
262
271
  if str(build.get("enabled", "true")).lower() == "true":
263
- steps = build.get(key, [])
264
- step: Dict[str, Any]
265
- for step in steps:
266
- step_id = step.get("id") or step.get("name")
267
- commands = step.get("commands", [])
268
-
269
- # Normalize commands to a list
270
- # If commands is a single string, wrap it in a list
271
- if isinstance(commands, str):
272
- commands = [commands]
273
-
274
- shell_step = pipelines.ShellStep(
275
- id=step_id,
276
- commands=commands,
277
- )
278
- shell_steps.append(shell_step)
272
+ # Check if this is a CodeBuild step (has source, buildspec, or environment)
273
+ if build.get("source") or build.get("buildspec") or build.get("environment"):
274
+ # Create CodeBuildStep for external builds
275
+ codebuild_step = self._create_codebuild_step(build, key, deployment, stage_config.name)
276
+ if codebuild_step:
277
+ shell_steps.append(codebuild_step)
278
+ else:
279
+ # Create traditional ShellStep for inline commands
280
+ steps = build.get(key, [])
281
+ step: Dict[str, Any]
282
+ for step in steps:
283
+ step_id = step.get("id") or step.get("name")
284
+ commands = step.get("commands", [])
285
+
286
+ # Normalize commands to a list
287
+ # If commands is a single string, wrap it in a list
288
+ if isinstance(commands, str):
289
+ commands = [commands]
290
+
291
+ shell_step = pipelines.ShellStep(
292
+ id=step_id,
293
+ commands=commands,
294
+ )
295
+ shell_steps.append(shell_step)
279
296
 
280
297
  return shell_steps
298
+
299
+ def _create_codebuild_step(self, build: Dict[str, Any], key: str, deployment: DeploymentConfig, stage_name: str) -> pipelines.CodeBuildStep:
300
+ """
301
+ Creates a CodeBuildStep for builds that specify source, buildspec, or environment.
302
+
303
+ Supports:
304
+ - External GitHub repositories (public and private)
305
+ - Custom buildspec files
306
+ - Environment configuration (compute type, image, privileged mode)
307
+ - Environment variables
308
+ - GitHub authentication via CodeConnections
309
+ """
310
+ build_name = build.get("name", "custom-build")
311
+
312
+ # Parse source configuration
313
+ source_config = build.get("source", {})
314
+ source_type = source_config.get("type", "GITHUB").upper()
315
+ source_location = source_config.get("location")
316
+ source_branch = source_config.get("branch", "main")
317
+
318
+ # Determine if this is the right step type (pre or post)
319
+ # Only create the step if it's supposed to run at this point
320
+ # For now, assume CodeBuild steps run as pre_steps by default
321
+ if key != "pre_steps":
322
+ return None
323
+
324
+ if not source_location:
325
+ logger.warning(f"Build '{build_name}' has no source location specified, skipping")
326
+ return None
327
+
328
+ # Parse buildspec
329
+ # If a buildspec path is specified, use the buildspec.yml from the source repo
330
+ buildspec_path = build.get("buildspec")
331
+ buildspec = None
332
+
333
+ if buildspec_path:
334
+ buildspec = codebuild.BuildSpec.from_source_filename(buildspec_path)
335
+ else:
336
+ # No buildspec specified - check for inline commands
337
+ inline_commands = build.get("commands", [])
338
+ if isinstance(inline_commands, str):
339
+ inline_commands = [inline_commands]
340
+ if inline_commands:
341
+ # Create inline buildspec from commands
342
+ buildspec = codebuild.BuildSpec.from_object({
343
+ "version": "0.2",
344
+ "phases": {
345
+ "build": {
346
+ "commands": inline_commands
347
+ }
348
+ }
349
+ })
350
+
351
+ # Parse environment configuration
352
+ env_config = build.get("environment", {})
353
+ compute_type_str = env_config.get("compute_type", "BUILD_GENERAL1_SMALL")
354
+
355
+ # Map string to CDK enum
356
+ compute_type_map = {
357
+ "BUILD_GENERAL1_SMALL": codebuild.ComputeType.SMALL,
358
+ "BUILD_GENERAL1_MEDIUM": codebuild.ComputeType.MEDIUM,
359
+ "BUILD_GENERAL1_LARGE": codebuild.ComputeType.LARGE,
360
+ "BUILD_GENERAL1_2XLARGE": codebuild.ComputeType.X2_LARGE,
361
+ }
362
+ compute_type = compute_type_map.get(compute_type_str, codebuild.ComputeType.SMALL)
363
+
364
+ build_image_str = env_config.get("image", "aws/codebuild/standard:7.0")
365
+ privileged_mode = env_config.get("privileged_mode", False)
366
+
367
+ # Parse build image
368
+ if build_image_str.startswith("aws/codebuild/standard:"):
369
+ version = build_image_str.split(":")[-1]
370
+ build_image = codebuild.LinuxBuildImage.from_code_build_image_id(f"aws/codebuild/standard:{version}")
371
+ else:
372
+ build_image = codebuild.LinuxBuildImage.from_code_build_image_id(build_image_str)
373
+
374
+ # Parse environment variables
375
+ env_vars_list = build.get("environment_variables", [])
376
+ env_vars = {}
377
+ for env_var in env_vars_list:
378
+ var_name = env_var.get("name")
379
+ var_value = env_var.get("value")
380
+ var_type = env_var.get("type", "PLAINTEXT")
381
+
382
+ if var_name and var_value is not None:
383
+ if var_type == "PLAINTEXT":
384
+ env_vars[var_name] = codebuild.BuildEnvironmentVariable(value=str(var_value))
385
+ elif var_type == "PARAMETER_STORE":
386
+ env_vars[var_name] = codebuild.BuildEnvironmentVariable(
387
+ value=str(var_value),
388
+ type=codebuild.BuildEnvironmentVariableType.PARAMETER_STORE
389
+ )
390
+ elif var_type == "SECRETS_MANAGER":
391
+ env_vars[var_name] = codebuild.BuildEnvironmentVariable(
392
+ value=str(var_value),
393
+ type=codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER
394
+ )
395
+
396
+ # Create build environment
397
+ build_environment = codebuild.BuildEnvironment(
398
+ build_image=build_image,
399
+ compute_type=compute_type,
400
+ privileged=privileged_mode,
401
+ environment_variables=env_vars
402
+ )
403
+
404
+ # Determine input source
405
+ if source_type == "GITHUB":
406
+ # GitHub source - supports both public and private repos
407
+ # For private repos, use the workload's code repository connection
408
+ repo_string = self._parse_github_repo_string(source_location)
409
+ cache_key = f"{repo_string}:{source_branch}"
410
+ input_source = self._source_cache.get(cache_key)
411
+ if not input_source:
412
+ input_source = pipelines.CodePipelineSource.connection(
413
+ repo_string=repo_string,
414
+ branch=source_branch,
415
+ connection_arn=self.workload.devops.code_repository.connector_arn,
416
+ action_name=f"{build_name}",
417
+ )
418
+ self._source_cache[cache_key] = input_source
419
+ else:
420
+ logger.warning(f"Unsupported source type '{source_type}' for build '{build_name}'")
421
+ return None
422
+
423
+ # Create CodeBuildStep
424
+ logger.info(f"Creating CodeBuildStep '{build_name}' with source from {source_location}")
425
+
426
+ # CodeBuildStep requires 'commands' param; when using a buildspec (repo or inline)
427
+ # we pass an empty list so only the buildspec runs
428
+ commands: List[str] = []
429
+
430
+ codebuild_step = pipelines.CodeBuildStep(
431
+ id=f"{build_name}-{stage_name}",
432
+ input=input_source,
433
+ commands=commands,
434
+ build_environment=build_environment,
435
+ partial_build_spec=buildspec,
436
+ )
437
+
438
+ return codebuild_step
439
+
440
+ def _parse_github_repo_string(self, location: str) -> str:
441
+ """
442
+ Converts GitHub URL to org/repo format.
443
+
444
+ Examples:
445
+ - https://github.com/geekcafe/myrepo.git -> geekcafe/myrepo
446
+ - https://github.com/geekcafe/myrepo -> geekcafe/myrepo
447
+ - geekcafe/myrepo -> geekcafe/myrepo
448
+ """
449
+ if location.startswith("https://github.com/"):
450
+ # Remove https://github.com/ prefix
451
+ repo_string = location.replace("https://github.com/", "")
452
+ # Remove .git suffix if present
453
+ if repo_string.endswith(".git"):
454
+ repo_string = repo_string[:-4]
455
+ return repo_string
456
+ else:
457
+ # Assume it's already in org/repo format
458
+ return location
281
459
 
282
460
  def __setup_stacks(
283
461
  self,
cdk_factory/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.15.16"
1
+ __version__ = "0.15.18"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.15.16
3
+ Version: 0.15.18
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=fF0Q8_DYdMnsPrA9kJWV3ce_KGlzM7rPCJcZFewIo30,24
5
+ cdk_factory/version.py,sha256=aNkgoB9um2h-SgHbaMydn49litDmfE43Bugj37-zvq8,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
@@ -14,7 +14,7 @@ cdk_factory/configurations/enhanced_base_config.py,sha256=LQQBn_99n41vkqvpU3NhY_
14
14
  cdk_factory/configurations/enhanced_ssm_config.py,sha256=J23PCinDTlji1XzsjOhIk9C8PubZc5ICpSWqPf3B0Vw,15873
15
15
  cdk_factory/configurations/management.py,sha256=TSOIyxO9hGNxbgiTsS8a3pz03ungXiNqPPtZtfOpr8M,1373
16
16
  cdk_factory/configurations/pipeline.py,sha256=3RmRP1GIk42rjYZ-A9H3357RcO13IA47N-2IQcBkySQ,4939
17
- cdk_factory/configurations/pipeline_stage.py,sha256=eAT-FoIepIuv5tObk4TXlCN47FaatQO2rrFchgbMdXU,3415
17
+ cdk_factory/configurations/pipeline_stage.py,sha256=hEjgaeaHYLRtkok5yBJwZg5zmPMPUHIRiUd0kzxWoRU,4453
18
18
  cdk_factory/configurations/stack.py,sha256=7whhC48dUYw7BBFV49zM1Q3AghTNkaiDfy4kKYDm_RQ,2217
19
19
  cdk_factory/configurations/workload.py,sha256=sM-B6UKOdOn5_H-eWmW03J9oa8YZZmO0bvQ69wbCM0Q,7756
20
20
  cdk_factory/configurations/resources/_resources.py,sha256=tnXGn4kEC0JPQaTWB3QpAZG-2hIGBtugHTzuKn1OTvE,2548
@@ -68,7 +68,7 @@ cdk_factory/interfaces/ssm_parameter_mixin.py,sha256=uA2j8HmAOpuEA9ynRj51s0WjUHM
68
68
  cdk_factory/lambdas/health_handler.py,sha256=dd40ykKMxWCFEIyp2ZdQvAGNjw_ylI9CSm1N24Hp2ME,196
69
69
  cdk_factory/lambdas/edge/ip_gate/handler.py,sha256=YrXO42gEhJoBTaH6jS7EWqjHe9t5Fpt4WLgY8vjtou0,10474
70
70
  cdk_factory/pipeline/path_utils.py,sha256=fvWdrcb4onmpIu1APkHLhXg8zWfK74HcW3Ra2ynxfXM,2586
71
- cdk_factory/pipeline/pipeline_factory.py,sha256=tY-uaJa_51CnWHb4KEvYMT-pZ_7cWy5DNnTfXHWUoHY,17295
71
+ cdk_factory/pipeline/pipeline_factory.py,sha256=gTSOqVdLKCDERdbdP4IQtiEFuGZI-EMizxIXuaPGPew,25550
72
72
  cdk_factory/pipeline/stage.py,sha256=Be7ExMB9A-linRM18IQDOzQ-cP_I2_ThRNzlT4FIrUg,437
73
73
  cdk_factory/pipeline/security/policies.py,sha256=H3-S6nipz3UtF9Pc5eJYr4-aREUTCaJWMjOUyd6Rdv4,4406
74
74
  cdk_factory/pipeline/security/roles.py,sha256=ZB_O5H_BXgotvVspS2kVad9EMcY-a_-vU7Nm1_Z5MB8,4985
@@ -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.16.dist-info/METADATA,sha256=9hzNJrGdhDwVWHrUqKMt_iOsP9MPIz6c7W09UTJUd98,2452
133
- cdk_factory-0.15.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
134
- cdk_factory-0.15.16.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
135
- cdk_factory-0.15.16.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
136
- cdk_factory-0.15.16.dist-info/RECORD,,
132
+ cdk_factory-0.15.18.dist-info/METADATA,sha256=rupyw8D3w9KLEWhGDl-sjVCXfJDrdUiEY1gWNB0IaZs,2452
133
+ cdk_factory-0.15.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
134
+ cdk_factory-0.15.18.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
135
+ cdk_factory-0.15.18.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
136
+ cdk_factory-0.15.18.dist-info/RECORD,,