Repository: awslabs/aws-ddk (language: python)

Issue:
typescript: Add default props for all resources



Discussion / hints:

Fix patch:
diff --git a/src/core/glue-defaults.ts b/src/core/glue-defaults.ts
new file mode 100644
index 00000000..9b1cef53
--- /dev/null
+++ b/src/core/glue-defaults.ts
@@ -0,0 +1,20 @@
+import * as glue from "@aws-cdk/aws-glue-alpha";
+import * as cdk from "aws-cdk-lib";
+import { Construct } from "constructs";
+import { overrideProps } from "./utils";
+
+export function assignGlueJobProps(scope: Construct, id: string, props: glue.JobProps) {
+  const defaultProps: Partial<glue.JobProps> = {
+    maxConcurrentRuns: 1,
+    maxRetries: 1,
+    timeout: cdk.Duration.hours(10),
+    securityConfiguration: new glue.SecurityConfiguration(scope, id, {
+      securityConfigurationName: `${id}-security-config`,
+      s3Encryption: {
+        mode: glue.S3EncryptionMode.S3_MANAGED,
+      },
+    }),
+  };
+
+  return overrideProps(defaultProps, props);
+}
diff --git a/src/core/index.ts b/src/core/index.ts
new file mode 100644
index 00000000..b8bde06f
--- /dev/null
+++ b/src/core/index.ts
@@ -0,0 +1,7 @@
+export * from "./glue-defaults";
+export * from "./kinesis-firehose-defaults";
+export * from "./kms-defaults";
+export * from "./lambda-defaults";
+export * from "./s3-defaults";
+export * from "./sns-defaults";
+export * from "./utils";
diff --git a/src/core/kinesis-firehose-defaults.ts b/src/core/kinesis-firehose-defaults.ts
index cf9bd55d..35b9cd23 100644
--- a/src/core/kinesis-firehose-defaults.ts
+++ b/src/core/kinesis-firehose-defaults.ts
@@ -1,11 +1,13 @@
 import { Compression, S3BucketProps } from "@aws-cdk/aws-kinesisfirehose-destinations-alpha";
 import * as cdk from "aws-cdk-lib";
+import { overrideProps } from "./utils";
 
-export function defaultDestinationsS3BucketProps(_dataOutputPrefix?: string) {
-  return {
+export function assignDestinationsS3BucketProps(props: S3BucketProps) {
+  const defaultProps: Partial<S3BucketProps> = {
     compression: Compression.GZIP,
     bufferingInterval: cdk.Duration.seconds(300),
     bufferingSize: cdk.Size.mebibytes(5),
-    ...(_dataOutputPrefix !== undefined && { dataOutputPrefix: _dataOutputPrefix }),
-  } as S3BucketProps;
+  };
+
+  return overrideProps(defaultProps, props);
 }
diff --git a/src/core/kms-defaults.ts b/src/core/kms-defaults.ts
new file mode 100644
index 00000000..028cc251
--- /dev/null
+++ b/src/core/kms-defaults.ts
@@ -0,0 +1,12 @@
+import * as cdk from "aws-cdk-lib";
+import * as kms from "aws-cdk-lib/aws-kms";
+import { overrideProps } from "./utils";
+
+export function assignKmsKeyProps(props: kms.KeyProps) {
+  const defaultProps: Partial<kms.KeyProps> = {
+    enableKeyRotation: true,
+    pendingWindow: cdk.Duration.days(30),
+  };
+
+  return overrideProps(defaultProps, props);
+}
diff --git a/src/core/lambda-defaults.ts b/src/core/lambda-defaults.ts
new file mode 100644
index 00000000..23a66977
--- /dev/null
+++ b/src/core/lambda-defaults.ts
@@ -0,0 +1,12 @@
+import * as cdk from "aws-cdk-lib";
+import * as lambda from "aws-cdk-lib/aws-lambda";
+import { overrideProps } from "./utils";
+
+export function assignLambdaFunctionProps(props: lambda.FunctionProps) {
+  const defaultProps: Partial<lambda.FunctionProps> = {
+    memorySize: 256,
+    timeout: cdk.Duration.seconds(120),
+  };
+
+  return overrideProps(defaultProps, props);
+}
diff --git a/src/core/s3-defaults.ts b/src/core/s3-defaults.ts
index d1b5c1d6..c711e6e2 100644
--- a/src/core/s3-defaults.ts
+++ b/src/core/s3-defaults.ts
@@ -1,15 +1,16 @@
 import * as cdk from "aws-cdk-lib";
 import * as s3 from "aws-cdk-lib/aws-s3";
+import { overrideProps } from "../core/utils";
 
-export function defaultS3BucketProps(loggingBucket?: s3.Bucket, lifecycleRules?: s3.LifecycleRule[]): s3.BucketProps {
-  return {
+export function assignS3BucketProps(props: s3.BucketProps): s3.BucketProps {
+  const defaultProps: Partial<s3.BucketProps> = {
     encryption: s3.BucketEncryption.S3_MANAGED,
     versioned: false,
     blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
     removalPolicy: cdk.RemovalPolicy.RETAIN,
     enforceSSL: true,
     eventBridgeEnabled: false,
-    ...(lifecycleRules !== undefined && { lifecycleRules }),
-    ...(loggingBucket !== undefined && { serverAccessLogsBucket: loggingBucket }),
-  } as s3.BucketProps;
+  };
+
+  return overrideProps(defaultProps, props);
 }
diff --git a/src/index.ts b/src/index.ts
index 75634336..d57c858b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,3 +3,4 @@ export * from "./cicd";
 export * from "./config";
 export * from "./pipelines";
 export * from "./stages";
+export * from "./core";
diff --git a/src/stages/appflow-ingestion.ts b/src/stages/appflow-ingestion.ts
index 8d96aa52..c0f79568 100644
--- a/src/stages/appflow-ingestion.ts
+++ b/src/stages/appflow-ingestion.ts
@@ -8,6 +8,7 @@ import * as lambda from "aws-cdk-lib/aws-lambda";
 import * as sfn from "aws-cdk-lib/aws-stepfunctions";
 import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks";
 import { Construct } from "constructs";
+import { assignLambdaFunctionProps } from "../core/lambda-defaults";
 import { StateMachineStage, StateMachineStageProps } from "../pipelines/stage";
 
 export interface AppFlowIngestionStageProps extends StateMachineStageProps {
@@ -112,12 +113,16 @@ export class AppFlowIngestionStage extends StateMachineStage {
       description: "lambda role to check appflow flow execution status",
     });
 
-    const statusLambda = new lambda.Function(this, "Flow Execution Status Lambda", {
-      code: lambda.Code.fromAsset(path.join(__dirname, "lambda_handlers/appflow_check_flow_status/")),
-      handler: "lambda_function.lambda_handler",
-      role: statusLambdaRole,
-      runtime: lambda.Runtime.PYTHON_3_9,
-    });
+    const statusLambda = new lambda.Function(
+      this,
+      "Flow Execution Status Lambda",
+      assignLambdaFunctionProps({
+        code: lambda.Code.fromAsset(path.join(__dirname, "lambda_handlers/appflow_check_flow_status/")),
+        handler: "lambda_function.lambda_handler",
+        role: statusLambdaRole,
+        runtime: lambda.Runtime.PYTHON_3_9,
+      }),
+    );
 
     // Enable the function to get flow execution records
     statusLambda.addToRolePolicy(
diff --git a/src/stages/glue-transform.ts b/src/stages/glue-transform.ts
index 3062ee0f..d1fb7682 100644
--- a/src/stages/glue-transform.ts
+++ b/src/stages/glue-transform.ts
@@ -5,6 +5,7 @@ import * as glue from "aws-cdk-lib/aws-glue";
 import * as sfn from "aws-cdk-lib/aws-stepfunctions";
 import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks";
 import { Construct } from "constructs";
+import { assignGlueJobProps } from "../core/glue-defaults";
 import { StateMachineStage, StateMachineStageProps } from "../pipelines/stage";
 
 export interface GlueTransformStageProps extends StateMachineStageProps {
@@ -23,7 +24,7 @@ export class GlueTransformStage extends StateMachineStage {
   constructor(scope: Construct, id: string, props: GlueTransformStageProps) {
     super(scope, id, props);
 
-    const glueJob = this.getGlueJob(props);
+    const glueJob = this.getGlueJob(scope, id, props);
     const jobRunArgs = props.jobRunArgs;
     const crawlerName = this.getCrawlerName(props);
 
@@ -59,9 +60,9 @@ export class GlueTransformStage extends StateMachineStage {
     } = this.createStateMachine(definition, props));
   }
 
-  private getGlueJob(props: GlueTransformStageProps): glue_alpha.IJob {
+  private getGlueJob(scope: Construct, id: string, props: GlueTransformStageProps): glue_alpha.IJob {
     if (props.jobName) {
-      return glue_alpha.Job.fromJobAttributes(this, "Glue Job", {
+      return glue_alpha.Job.fromJobAttributes(this, `${id} Job`, {
         jobName: props.jobName,
       });
     }
@@ -70,7 +71,13 @@ export class GlueTransformStage extends StateMachineStage {
       throw TypeError("'jobName' or 'jobProps' must be set to instantiate this stage");
     }
 
-    return new glue_alpha.Job(this, "Glue Job", props.jobProps);
+    return new glue_alpha.Job(
+      this,
+      `${id} Job`,
+      assignGlueJobProps(scope, `${id} Job`, {
+        ...props.jobProps,
+      }),
+    );
   }
 
   private getCrawlerName(props: GlueTransformStageProps): string {
diff --git a/src/stages/kinesis-s3.ts b/src/stages/kinesis-s3.ts
index 6b9f1984..a70a1cad 100644
--- a/src/stages/kinesis-s3.ts
+++ b/src/stages/kinesis-s3.ts
@@ -4,9 +4,8 @@ import * as events from "aws-cdk-lib/aws-events";
 import * as kinesis from "aws-cdk-lib/aws-kinesis";
 import * as s3 from "aws-cdk-lib/aws-s3";
 import { Construct } from "constructs";
-import { defaultDestinationsS3BucketProps } from "../core/kinesis-firehose-defaults";
-import { defaultS3BucketProps } from "../core/s3-defaults";
-import { consolidateProps, overrideProps } from "../core/utils";
+import { assignDestinationsS3BucketProps } from "../core/kinesis-firehose-defaults";
+import { assignS3BucketProps } from "../core/s3-defaults";
 import { DataStage, DataStageProps } from "../pipelines/stage";
 
 export interface FirehoseToS3StageProps extends DataStageProps {
@@ -35,9 +34,11 @@ export class FirehoseToS3Stage extends DataStage {
     if (props.s3Bucket) {
       this.bucket = props.s3Bucket;
     } else if (props.s3BucketProps) {
-      const defaultBucketProps = overrideProps(defaultS3BucketProps(), { eventBridgeEnabled: true });
-      const consolidatedBucketProps = consolidateProps(defaultBucketProps, props.s3BucketProps);
-      this.bucket = new s3.Bucket(this, "Stage Bucket", consolidatedBucketProps);
+      const bucketProps = assignS3BucketProps({
+        ...props.s3BucketProps,
+        eventBridgeEnabled: true,
+      });
+      this.bucket = new s3.Bucket(this, "Stage Bucket", bucketProps);
     } else {
       throw TypeError("'s3Bucket' or 's3BucketProps' must be set to instantiate this stage");
     }
@@ -47,21 +48,20 @@ export class FirehoseToS3Stage extends DataStage {
       this.dataStream = new kinesis.Stream(this, "Data Stream", {});
     }
 
-    const destinationsBucketProps = props.kinesisFirehoseDestinationsS3BucketProps ?? {};
-    const consolidatedDestinationsBucketProps = consolidateProps(
-      defaultDestinationsS3BucketProps(props.dataOutputPrefix),
-      destinationsBucketProps,
-    );
+    const destinationsBucketProps = assignDestinationsS3BucketProps({
+      ...props.kinesisFirehoseDestinationsS3BucketProps,
+      dataOutputPrefix: props.dataOutputPrefix,
+    });
     this.deliveryStream = new firehose.DeliveryStream(this, "Delivery Stream", {
-      destinations: [new destinations.S3Bucket(this.bucket, consolidatedDestinationsBucketProps)],
+      destinations: [new destinations.S3Bucket(this.bucket, destinationsBucketProps)],
       sourceStream: this.dataStream,
       ...props.firehoseDeliveryStreamProps,
     });
-    const dataOutputPrefix: string = consolidatedDestinationsBucketProps.dataOutputPrefix;
+    const dataOutputPrefix: string = destinationsBucketProps.dataOutputPrefix;
 
     this.addAlarm("Data Freshness Errors", {
       metric: this.deliveryStream.metric("DeliveryToS3.DataFreshness", {
-        period: consolidatedDestinationsBucketProps.bufferingInterval,
+        period: destinationsBucketProps.bufferingInterval,
         statistic: "Maximum",
       }),
       threshold: props.deliveryStreamDataFreshnessErrorsAlarmThreshold,
diff --git a/src/stages/sqs-lambda.ts b/src/stages/sqs-lambda.ts
index 2168e982..0f7814d6 100644
--- a/src/stages/sqs-lambda.ts
+++ b/src/stages/sqs-lambda.ts
@@ -7,6 +7,7 @@ import * as lambda from "aws-cdk-lib/aws-lambda";
 import * as lambda_event_sources from "aws-cdk-lib/aws-lambda-event-sources";
 import * as sqs from "aws-cdk-lib/aws-sqs";
 import { Construct } from "constructs";
+import { assignLambdaFunctionProps } from "../core/lambda-defaults";
 import { DataStage, DataStageProps } from "../pipelines/stage";
 
 export interface SqsToLambdaStageFunctionProps extends lambda.FunctionProps {
@@ -47,20 +48,24 @@ export class SqsToLambdaStage extends DataStage {
     } else if (props.lambdaFunctionProps) {
       const functionProps: SqsToLambdaStageFunctionProps = props.lambdaFunctionProps;
 
-      this.function = new lambda.Function(this, "Process Function", {
-        code: functionProps.code,
-        runtime: functionProps.runtime,
-        handler: functionProps.handler,
-        timeout: functionProps.timeout,
-        memorySize: functionProps.memorySize,
-        layers: functionProps.layers,
-        role: functionProps.role,
-        environment: {
-          EVENT_SOURCE: eventSource,
-          EVENT_DETAIL_TYPE: eventDetailType,
-          ...(functionProps.environment ?? {}),
-        },
-      });
+      this.function = new lambda.Function(
+        this,
+        "Process Function",
+        assignLambdaFunctionProps({
+          code: functionProps.code,
+          runtime: functionProps.runtime,
+          handler: functionProps.handler,
+          timeout: functionProps.timeout,
+          memorySize: functionProps.memorySize,
+          layers: functionProps.layers,
+          role: functionProps.role,
+          environment: {
+            EVENT_SOURCE: eventSource,
+            EVENT_DETAIL_TYPE: eventDetailType,
+            ...(functionProps.environment ?? {}),
+          },
+        }),
+      );
     } else {
       throw TypeError("'lambdaFunction' or 'lambdaFunctionProps' must be set to instantiate this stage");
     }


Test patch:
diff --git a/integ/integ.data-pipelines.test.ts b/integ/integ.data-pipelines.test.ts
index 78ac3b1c..f8d10fb2 100644
--- a/integ/integ.data-pipelines.test.ts
+++ b/integ/integ.data-pipelines.test.ts
@@ -19,9 +19,9 @@ class DataPipelineTestStack extends cdk.Stack {
     super(scope, id, props);
     const bucket = new s3.Bucket(this, "Bucket", {removalPolicy: cdk.RemovalPolicy.DESTROY});
 
-    const firehoseToS3Stage = new FirehoseToS3Stage(this, "Firehose To S3 Stage", { s3Bucket: bucket });
+    const firehoseToS3Stage = new FirehoseToS3Stage(this, `${id} Firehose To S3 Stage`, { s3Bucket: bucket });
 
-    const sqsToLambdaStage = new SqsToLambdaStage(this, "SQS To Lambda Stage 2", {
+    const sqsToLambdaStage = new SqsToLambdaStage(this, `${id} SQS To Lambda Stage`, {
       lambdaFunctionProps: {
         code: lambda.Code.fromInline("def lambda_handler(event, context): return 200"),
         handler: "lambda_function.lambda_handler",
@@ -30,7 +30,7 @@ class DataPipelineTestStack extends cdk.Stack {
       },
     });
 
-    const glueTransformStage = new GlueTransformStage(this, "Glue Transform Stage", {
+    const glueTransformStage = new GlueTransformStage(this, `${id}Glue Transform Stage`, {
       jobProps: {
         executable: glue_alpha.JobExecutable.pythonShell({
           glueVersion: glue_alpha.GlueVersion.V1_0,
@@ -41,7 +41,7 @@ class DataPipelineTestStack extends cdk.Stack {
       crawlerName: "dummy-crawler",
     })
 
-    const pipeline = new DataPipeline(this, "Pipeline", {});
+    const pipeline = new DataPipeline(this, `${id} Pipeline`, {});
 
     if (props.testWithScheduledEvent) {
       pipeline.addNotifications().addStage({ stage: firehoseToS3Stage }).addStage({ stage: sqsToLambdaStage }).addStage({ stage: glueTransformStage, schedule: events.Schedule.rate(cdk.Duration.minutes(5))});
diff --git a/test/kms-defaults.test.ts b/test/kms-defaults.test.ts
new file mode 100644
index 00000000..5c561975
--- /dev/null
+++ b/test/kms-defaults.test.ts
@@ -0,0 +1,16 @@
+import * as cdk from "aws-cdk-lib";
+import { Template } from "aws-cdk-lib/assertions";
+import * as kms from "aws-cdk-lib/aws-kms";
+
+import { assignKmsKeyProps } from "../src";
+
+test("KMS Defaults", () => {
+  const stack = new cdk.Stack();
+  new kms.Key(stack, "Default Key", assignKmsKeyProps({}));
+
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::KMS::Key", {
+    EnableKeyRotation: true,
+    PendingWindowInDays: 30,
+  });
+});