CDK Pipelines - Adding a Pre-Build Step
Intro
While working with CDK Pipelines, I had to insert a custom step/logic between the Source and Build Steps generated by the pipeline. It took me a while to find documentation or a working example, this is why I decided to write this brief article about it.
Code
Here’s a simplified code snippet depicting how my Pipeline initially looked like:
# Git Connection
source_action = CodePipelineSource.connection(
repo_string,
branch=branch,
connection_arn=RepoConfig.REPO_CONNECTION_ARN
)
Pipeline using source_action
as ShellStep input:
pipeline = CodePipeline(
self,
f"{pipeline_id}-stack",
pipeline_name=pipeline_id,
cli_version=CDK_VERSION,
role=pipeline_role,
synth=ShellStep(
"Synth",
input=source_action,
commands=[
f"npm install -g aws-cdk@{CDK_VERSION}",
"python -m pip install -r requirements.txt",
"cdk synth",
],
)
)
Since CDK determines the placement of the actions you create based on their inputs and outputs, updating these values allows us to add a step before the build.
The initial step involves creating a CodeBuildStep
and a Role
to be utilized for executing that step.
# Custom Role
pre_build_role = iam.Role(
scope,
f"{pipeline_id}-pre-build-role",
role_name=f"{pipeline_id}-pre-build-role",
assumed_by=iam.ServicePrincipal("codebuild.amazonaws.com"),
)
# Pre-Build Step
pre_build_step = CodeBuildStep(
"PreBuildStep",
input=source_action,
commands=[
"...",
],
role=pre_build_role,
primary_output_directory="./",
)
For that CodeBuildStep
, we utilize the source_action
as its input (Note that this is presently the input of our pipeline).
Finally, we update our pipeline’s input value, replacing source_input
with pre_built_step
(the step just created).
pipeline = CodePipeline(
self,
f"{pipeline_id}-stack",
pipeline_name=pipeline_id,
cli_version=CDK_VERSION,
role=pipeline_role,
synth=ShellStep(
"Synth",
input=pre_build_step,
commands=[
f"npm install -g aws-cdk@{CDK_VERSION}",
"python -m pip install -r requirements.txt",
"cdk synth",
],
)
)
After the pipeline is deployed, you’ll notice the new step positioned just before the Synth step:
Why would you want to do this?
I’m exploring this approach in two similar scenarios. In both cases, a pre-build step executes either Python or shell script code, generating a file on disk that is later utilized by the Synth step.
For example, I run a git diff --name-only HEAD~1
command to identify which configuration files have changed since the last commit. Subsequently, I include these changes as a stage only if modifications are detected. Otherwise, I avoid redundant deployments.
Important
It’s worth noting that if any command within the CodeBuildStep
fails, the entire pipeline will fail, and you won’t be able to fix it pushing a fix. Since the Synth & Mutate step come after, they won’t be able to execute.