Skip to content

Commit

Permalink
Merge pull request #185 from capralifecycle/experimental-cdk-pipeline…
Browse files Browse the repository at this point in the history
…s-speedup

Experimental CDK Pipelines speed up
  • Loading branch information
stekern authored Apr 18, 2023
2 parents 7dc71cf + 7bf724c commit 964aaa4
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 0 deletions.
132 changes: 132 additions & 0 deletions __snapshots__/app/build-artifacts.template.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions __snapshots__/app/manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/build-artifacts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class BuildArtifacts extends constructs.Construct {
bucket = new s3.Bucket(this, "S3Bucket", {
bucketName: props.bucketName,
encryption: s3.BucketEncryption.S3_MANAGED,
eventBridgeEnabled: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
versioned: true,
lifecycleRules: [
Expand Down
74 changes: 74 additions & 0 deletions src/cdk-pipelines/__tests__/liflig-cdk-pipeline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as assertions from "aws-cdk-lib/assertions"
import { App, CfnOutput, Stack, Stage } from "aws-cdk-lib"
import { LifligCdkPipeline } from "../liflig-cdk-pipeline"
import { FEATURE_FLAG_CDK_PIPELINES_SPEED_UP } from "../../feature-flags"

test("liflig-cdk-pipeline-with-feature-flag", () => {
const app = new App({
context: {
[FEATURE_FLAG_CDK_PIPELINES_SPEED_UP]: true,
},
})

const stage = new Stage(app, "Stage")
const stack = new Stack(stage, "Stack")
new CfnOutput(stack, "ExampleOutput", {
value: "hello world",
})

const pipelineStack = new Stack(app, "PipelineStack")

const pipeline = new LifligCdkPipeline(pipelineStack, "Pipeline", {
pipelineName: "test-pipeline",
sourceType: "cdk-source",
})

pipeline.cdkPipeline.addStage(stage)
const template = assertions.Template.fromStack(pipelineStack)

// Assert that S3 polling is deactivated
template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
Stages: assertions.Match.arrayWith([
assertions.Match.objectLike({
Actions: assertions.Match.arrayWith([
assertions.Match.objectLike({
ActionTypeId: assertions.Match.objectEquals({
Category: "Source",
Owner: "AWS",
Provider: "S3",
Version: "1",
}),
Configuration: assertions.Match.objectLike({
PollForSourceChanges: false,
}),
}),
]),
}),
]),
})

// Assert that there are no actions that creates changesets
template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
Stages: assertions.Match.not(
assertions.Match.arrayWith([
assertions.Match.objectLike({
Actions: assertions.Match.arrayWith([
assertions.Match.objectLike({
ActionTypeId: assertions.Match.objectEquals({
Category: "Deploy",
Owner: "AWS",
Provider: "CloudFormation",
Version: "1",
}),
Configuration: assertions.Match.objectLike({
ActionMode: "CHANGE_SET_REPLACE",
}),
}),
]),
}),
]),
),
})
// Assert that there's an EventBridge rule set up to trigger the pipeline
template.hasResource("AWS::Events::Rule", {})
})
32 changes: 32 additions & 0 deletions src/cdk-pipelines/liflig-cdk-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as codepipeline from "aws-cdk-lib/aws-codepipeline"
import * as codepipelineActions from "aws-cdk-lib/aws-codepipeline-actions"
import * as iam from "aws-cdk-lib/aws-iam"
import * as lambda from "aws-cdk-lib/aws-lambda"
import * as events from "aws-cdk-lib/aws-events"
import * as targets from "aws-cdk-lib/aws-events-targets"
import * as s3 from "aws-cdk-lib/aws-s3"
import * as cdk from "aws-cdk-lib"
import * as pipelines from "aws-cdk-lib/pipelines"
Expand All @@ -14,6 +16,10 @@ import {
CloudAssemblyLookupUserParameters,
} from "./cloud-assembly-lookup-handler"
import { SlackNotification, SlackNotificationProps } from "./slack-notification"
import {
FeatureFlags,
FEATURE_FLAG_CDK_PIPELINES_SPEED_UP,
} from "../feature-flags"

export interface LifligCdkPipelineProps {
/**
Expand Down Expand Up @@ -168,6 +174,11 @@ export class LifligCdkPipeline extends constructs.Construct {
new codepipelineActions.S3SourceAction({
actionName: "source",
bucket: artifactsBucket,
trigger: FeatureFlags.of(scope).isEnabled(
FEATURE_FLAG_CDK_PIPELINES_SPEED_UP,
)
? codepipelineActions.S3Trigger.NONE
: undefined,
bucketKey: LifligCdkPipeline.pipelineS3TriggerKey(
props.pipelineName,
),
Expand All @@ -180,8 +191,29 @@ export class LifligCdkPipeline extends constructs.Construct {
restartExecutionOnUpdate: true,
})

if (FeatureFlags.of(scope).isEnabled(FEATURE_FLAG_CDK_PIPELINES_SPEED_UP)) {
new events.Rule(this, "PipelineTrigger", {
eventPattern: {
source: ["aws.s3"],
detailType: ["Object Created"],
detail: {
bucket: {
name: [artifactsBucket.bucketName],
},
object: {
key: [LifligCdkPipeline.pipelineS3TriggerKey(props.pipelineName)],
},
},
},
targets: [new targets.CodePipeline(this.codePipeline)],
})
}

this.cdkPipeline = new pipelines.CodePipeline(this, "CdkPipeline", {
synth,
useChangeSets: !FeatureFlags.of(scope).isEnabled(
FEATURE_FLAG_CDK_PIPELINES_SPEED_UP,
),
codePipeline: this.codePipeline,
})
}
Expand Down
66 changes: 66 additions & 0 deletions src/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as constructs from "constructs"

interface FeatureFlagInfo {
/**
* The default value for the feature flag.
*
* NOTE: This will be the value used for consumers that have not
* explicitly set the feature flag (which will be most of them!),
* so we should make sure that the default value does NOT lead
* to any breaking behavior.
*/
default: boolean
/**
* A short description of the feature flag.
*/
description: string
}

// Custom feature flags for liflig-cdk
export const FEATURE_FLAG_CDK_PIPELINES_SPEED_UP =
"@liflig-cdk/cdk-pipelines:enableExperimentalSpeedUp"

const FLAGS: { [key: string]: FeatureFlagInfo } = {
[FEATURE_FLAG_CDK_PIPELINES_SPEED_UP]: {
default: false,
description:
"Reduce execution time of CDK Pipelines by making various tweaks (e.g., skip creation of CloudFormation changesets, disable CodePipeline S3 polling).",
},
}

const getFeatureFlagDefault = (flagName: string) => {
return FLAGS[flagName]?.default ?? false
}

/**
* Exposes feature flags we can use in liflig-cdk to allow consumers to opt-in
* to experimental functionality without affecting current consumers and having
* to pollute the official library API with experimental properties and behavior.
*
* NOTE: We should only use these flags temporarily and very sparingly as they lead
* to a brittle and more complex codebase with a lot of branching logic.
* Once an experiment has concluded we should remove them and update the
* official library API.
*/
export class FeatureFlags {
private constructor(private readonly scope: constructs.IConstruct) {}
public static of(scope: constructs.Construct) {
return new FeatureFlags(scope)
}
public isEnabled(flagName: string) {
if (!Object.keys(FLAGS).includes(flagName)) {
throw new Error(`Unsupported feature flag ${flagName}`)
}
const contextValue = this.scope.node.tryGetContext(flagName) as unknown
if (contextValue === undefined) {
return getFeatureFlagDefault(flagName)
} else if (
Object.prototype.toString.call(contextValue) === "[object Boolean]"
) {
return Boolean(contextValue)
}
throw new Error(
`Unsupported value for feature flag ${flagName}. Only boolean values are supported.`,
)
}
}

0 comments on commit 964aaa4

Please sign in to comment.