Repository: awslabs/aws-ddk
Pull request #283 (2023-03-22 19:48:56)

feat: `CICDPipelineStack()` updates
-  allowing command failure in generic actions like `Synth` & `Tests` to support different cdk language `beforeInstall` commands
- enforce `EnableKeyRotation` in pipeline kms key


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


malachi-constant: 
### AWS CodeBuild CI Report

* CodeBuild project: DDKIntegrationTestsD8472609-5SCHWjldmWcf
* Commit ID: 9e719a171f8d56440fe600c77fbe085475d0442d
* Result: SUCCEEDED
* [Build Logs](https://9hm1y4sor9.execute-api.us-east-1.amazonaws.com/Prod/buildlogs?key=20f9268b-64cc-4c86-a4ec-3c416b8b6194%2Fbuild.log) (available for 30 days)

*Powered by [github-codebuild-logs](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:277187709615:applications~github-codebuild-logs), available on the [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/)*


<!--
CREATED BY GITHUB-CODEBUILD-LOGS
-->


malachi-constant: 
### AWS CodeBuild CI Report

* CodeBuild project: DDKIntegrationTestsD8472609-5SCHWjldmWcf
* Commit ID: bb23e54439d2b5d1034b9c2c146bbee9e3c375dd
* Result: SUCCEEDED
* [Build Logs](https://9hm1y4sor9.execute-api.us-east-1.amazonaws.com/Prod/buildlogs?key=f6995164-8725-4237-821d-0876fddfaba5%2Fbuild.log) (available for 30 days)

*Powered by [github-codebuild-logs](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:277187709615:applications~github-codebuild-logs), available on the [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/)*


<!--
CREATED BY GITHUB-CODEBUILD-LOGS
-->


malachi-constant: 
### AWS CodeBuild CI Report

* CodeBuild project: DDKIntegrationTestsD8472609-5SCHWjldmWcf
* Commit ID: 4fc69465d1a47a907a7e5fc2369da19b5fd63345
* Result: SUCCEEDED
* [Build Logs](https://9hm1y4sor9.execute-api.us-east-1.amazonaws.com/Prod/buildlogs?key=808c4cfe-7be0-42b7-9cce-8090fd7650cc%2Fbuild.log) (available for 30 days)

*Powered by [github-codebuild-logs](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:277187709615:applications~github-codebuild-logs), available on the [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/)*


<!--
CREATED BY GITHUB-CODEBUILD-LOGS
-->



Patch:
diff --git a/API.md b/API.md
index c3dbf90..43d1181 100644
--- a/API.md
+++ b/API.md
@@ -1953,13 +1953,14 @@ The construct to start the search from.
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.nestedStackParent">nestedStackParent</a></code> | <code>aws-cdk-lib.Stack</code> | If this is a nested stack, returns it's parent stack. |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.nestedStackResource">nestedStackResource</a></code> | <code>aws-cdk-lib.CfnResource</code> | If this is a nested stack, this represents its `AWS::CloudFormation::Stack` resource. |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | Whether termination protection is enabled for this stack. |
+| <code><a href="#aws-ddk-core.CICDPipelineStack.property.cdkLanguage">cdkLanguage</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.config">config</a></code> | <code><a href="#aws-ddk-core.Configurator">Configurator</a></code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipelineId">pipelineId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipelineName">pipelineName</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.notificationRule">notificationRule</a></code> | <code>aws-cdk-lib.aws_codestarnotifications.NotificationRule</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipeline">pipeline</a></code> | <code>aws-cdk-lib.pipelines.CodePipeline</code> | *No description.* |
-| <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipelineKey">pipelineKey</a></code> | <code>constructs.IConstruct</code> | *No description.* |
+| <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipelineKey">pipelineKey</a></code> | <code>aws-cdk-lib.aws_kms.CfnKey</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.sourceAction">sourceAction</a></code> | <code>aws-cdk-lib.pipelines.CodePipelineSource</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.synthAction">synthAction</a></code> | <code>aws-cdk-lib.pipelines.CodeBuildStep</code> | *No description.* |
 
@@ -2295,6 +2296,16 @@ Whether termination protection is enabled for this stack.
 
 ---
 
+##### `cdkLanguage`<sup>Required</sup> <a name="cdkLanguage" id="aws-ddk-core.CICDPipelineStack.property.cdkLanguage"></a>
+
+```typescript
+public readonly cdkLanguage: string;
+```
+
+- *Type:* string
+
+---
+
 ##### `config`<sup>Required</sup> <a name="config" id="aws-ddk-core.CICDPipelineStack.property.config"></a>
 
 ```typescript
@@ -2358,10 +2369,10 @@ public readonly pipeline: CodePipeline;
 ##### `pipelineKey`<sup>Optional</sup> <a name="pipelineKey" id="aws-ddk-core.CICDPipelineStack.property.pipelineKey"></a>
 
 ```typescript
-public readonly pipelineKey: IConstruct;
+public readonly pipelineKey: CfnKey;
 ```
 
-- *Type:* constructs.IConstruct
+- *Type:* aws-cdk-lib.aws_kms.CfnKey
 
 ---
 
@@ -5726,6 +5737,7 @@ const cICDPipelineStackProps: CICDPipelineStackProps = { ... }
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.config">config</a></code> | <code>string \| object</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.permissionsBoundaryArn">permissionsBoundaryArn</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.cdkLanguage">cdkLanguage</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.pipelineName">pipelineName</a></code> | <code>string</code> | *No description.* |
 
 ---
@@ -5953,6 +5965,16 @@ public readonly permissionsBoundaryArn: string;
 
 ---
 
+##### `cdkLanguage`<sup>Optional</sup> <a name="cdkLanguage" id="aws-ddk-core.CICDPipelineStackProps.property.cdkLanguage"></a>
+
+```typescript
+public readonly cdkLanguage: string;
+```
+
+- *Type:* string
+
+---
+
 ##### `pipelineName`<sup>Optional</sup> <a name="pipelineName" id="aws-ddk-core.CICDPipelineStackProps.property.pipelineName"></a>
 
 ```typescript
diff --git a/src/cicd/pipelines.ts b/src/cicd/pipelines.ts
index f85f7f7..0feaad7 100644
--- a/src/cicd/pipelines.ts
+++ b/src/cicd/pipelines.ts
@@ -2,9 +2,10 @@ import * as cdk from "aws-cdk-lib";
 import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
 import * as codestarnotifications from "aws-cdk-lib/aws-codestarnotifications";
 import * as iam from "aws-cdk-lib/aws-iam";
+import * as kms from "aws-cdk-lib/aws-kms";
 import * as sns from "aws-cdk-lib/aws-sns";
 import * as pipelines from "aws-cdk-lib/pipelines";
-import { Construct, IConstruct } from "constructs";
+import { Construct } from "constructs";
 import { CICDActions } from "./actions";
 import { toTitleCase } from "./utils";
 import { BaseStack, BaseStackProps } from "../base";
@@ -60,6 +61,7 @@ export interface AddCustomStageProps {
 
 export interface CICDPipelineStackProps extends BaseStackProps {
   readonly pipelineName?: string;
+  readonly cdkLanguage?: string;
 }
 
 export interface AdditionalPipelineProps {
@@ -82,9 +84,10 @@ export class CICDPipelineStack extends BaseStack {
   readonly pipelineName?: string;
   readonly pipelineId?: string;
   readonly config: Configurator;
+  readonly cdkLanguage: string;
   public notificationRule?: codestarnotifications.NotificationRule;
   public pipeline?: pipelines.CodePipeline;
-  public pipelineKey?: IConstruct;
+  public pipelineKey?: kms.CfnKey;
   public sourceAction?: pipelines.CodePipelineSource;
   public synthAction?: pipelines.CodeBuildStep;
 
@@ -96,6 +99,7 @@ export class CICDPipelineStack extends BaseStack {
     this.pipelineId = id;
     const config = props.config ?? "./ddk.json";
     this.config = new Configurator(this, config, this.environmentId);
+    this.cdkLanguage = props.cdkLanguage ?? "typescript";
   }
 
   addSourceAction(props: SourceActionProps) {
@@ -123,6 +127,11 @@ export class CICDPipelineStack extends BaseStack {
   }
 
   addSynthAction(props: SynthActionProps = {}) {
+    const languageInstallCommand: any = {
+      typescript: "npm install",
+      python: "pip install -r requirements.txt",
+    };
+
     this.synthAction =
       props.synthAction ||
       CICDActions.getSynthAction({
@@ -135,7 +144,9 @@ export class CICDPipelineStack extends BaseStack {
         codeartifactRepository: props.codeartifactRepository,
         codeartifactDomain: props.codeartifactDomain,
         codeartifactDomainOwner: props.codeartifactDomainOwner,
-        additionalInstallCommands: props.additionalInstallCommands,
+        additionalInstallCommands: props.additionalInstallCommands
+          ? [languageInstallCommand[this.cdkLanguage]].concat(props.additionalInstallCommands)
+          : [languageInstallCommand[this.cdkLanguage]],
       });
     return this;
   }
@@ -250,11 +261,9 @@ export class CICDPipelineStack extends BaseStack {
 
   synth() {
     this.pipeline?.buildPipeline();
-    this.pipelineKey = this.pipeline?.pipeline.artifactBucket.encryptionKey?.node.defaultChild ?? undefined;
+    this.pipelineKey = this.pipeline?.pipeline.artifactBucket.encryptionKey?.node.defaultChild as kms.CfnKey;
+    this.pipelineKey.addPropertyOverride("EnableKeyRotation", true);
 
-    // if (this.pipelineKey) {
-    //   this.pipelineKey = true;
-    // }
     return this;
   }
 }
diff --git a/src/config/configurator.ts b/src/config/configurator.ts
index 48739bc..fc0f8ab 100644
--- a/src/config/configurator.ts
+++ b/src/config/configurator.ts
@@ -116,7 +116,9 @@ class ConfiguratorAspect implements cdk.IAspect {
       node.addPropertyOverride(this.propertyName, this.propertyValue);
     }
 
-    if (this.resourceId && cdk.CfnResource.isCfnResource(node)) {
+    const nodePathItemRegex = new RegExp(`^(.*\/)?(${this.resourceId})(\/.*)?$`);
+
+    if (this.resourceId && cdk.CfnResource.isCfnResource(node) && nodePathItemRegex.test(node.node.path)) {
       node.addPropertyOverride(this.propertyName, this.propertyValue);
     }
   }


Test patch:
diff --git a/test/cicd-pipeline-stack.test.ts b/test/cicd-pipeline-stack.test.ts
index c72bd5e..57af547 100644
--- a/test/cicd-pipeline-stack.test.ts
+++ b/test/cicd-pipeline-stack.test.ts
@@ -107,6 +107,9 @@ test("Basic CICDPipeline", () => {
   template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
     Name: "dummy-pipeline",
   });
+  template.hasResourceProperties("AWS::KMS::Key", {
+    EnableKeyRotation: true,
+  });
 });
 
 test("CICDPipeline with manual approval set", () => {
diff --git a/test/config.test.ts b/test/config.test.ts
index 285bf47..375b121 100644
--- a/test/config.test.ts
+++ b/test/config.test.ts
@@ -1,10 +1,11 @@
 import { assert } from "console";
 import path from "path";
 import * as cdk from "aws-cdk-lib";
-import { Template } from "aws-cdk-lib/assertions";
+import { Match, Template } from "aws-cdk-lib/assertions";
 import * as lambda from "aws-cdk-lib/aws-lambda";
 import * as s3 from "aws-cdk-lib/aws-s3";
 import * as sqs from "aws-cdk-lib/aws-sqs";
+import { Construct } from "constructs";
 
 import {
   Configurator,
@@ -58,6 +59,12 @@ test("Config Simple Override", () => {
 });
 
 test("Config Override By Id", () => {
+  class NestedStack extends cdk.Stack {
+    constructor(scope: Construct, id: string) {
+      super(scope, id);
+      new s3.Bucket(this, "AVeryLongBucketNameInsideANestedStack");
+    }
+  }
   const sampleConfig = {
     environments: {
       dev: {
@@ -65,6 +72,9 @@ test("Config Override By Id", () => {
           MyBucket: {
             BucketName: "my-exact-bucket-name-for-this-resource",
           },
+          AVeryLongBucketNameInsideANestedStack: {
+            BucketName: "override-bucket-name",
+          },
           MyQueue: {
             KmsMasterKeyId: "alias/aws/sqs",
           },
@@ -75,9 +85,8 @@ test("Config Override By Id", () => {
   const stack = new cdk.Stack();
   new s3.Bucket(stack, "MyBucket");
   new sqs.Queue(stack, "MyQueue");
-
+  new sqs.Queue(stack, "MyUnencryptedQueue");
   new Configurator(stack, sampleConfig, "dev");
-
   const template = Template.fromStack(stack);
   template.hasResourceProperties("AWS::S3::Bucket", {
     BucketName: "my-exact-bucket-name-for-this-resource",
@@ -85,6 +94,17 @@ test("Config Override By Id", () => {
   template.hasResourceProperties("AWS::SQS::Queue", {
     KmsMasterKeyId: "alias/aws/sqs",
   });
+  template.hasResourceProperties("AWS::SQS::Queue", {
+    KmsMasterKeyId: Match.absent(),
+  });
+
+  const app = new cdk.App();
+  const nestedStack = new NestedStack(app, "PlaceholderWithExcessivelyLongNameABCDEFGHIJFKLMNOPQRSTUVXXYZ");
+  new Configurator(nestedStack, sampleConfig, "dev");
+  const nestedTemplate = Template.fromStack(nestedStack);
+  nestedTemplate.hasResourceProperties("AWS::S3::Bucket", {
+    BucketName: "override-bucket-name",
+  });
 });
 
 test("Different values per environment", () => {