My Blog

A Blog about software development

CDK Pipelines - Adding a Pre-Build Step

Mar 6, 2024

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:

CDK Pipeline

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.