-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #185 from capralifecycle/experimental-cdk-pipeline…
…s-speedup Experimental CDK Pipelines speed up
- Loading branch information
Showing
6 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", {}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.`, | ||
) | ||
} | ||
} |