Search code examples
typescriptamazon-web-servicesaws-cdkaws-codepipelineaws-codebuild

Use AWS CodePipeline variables in a custom stage


There's the following buildspec that runs some commands and exports a variable in the end:

env:
  exported-variables:
    - PROPERTY_FROM_BUILD

I'd like to use this variable inside a custom pipeline stage, but I have no idea how to get it here:

import { Stage, StageProps } from 'aws-cdk-lib';
class MyStage extends Stage {
  constructor(scope: Construct, id: string, props: StageProps) {
    new Stack(this, 'MyStack', {
        PROPERTY_FROM_BUILD: '???'
    });
  }
}

Let me post more details just in case. My code pipeline looks like this:

const codePipeline = new cdk.pipelines.CodePipeline(this, 'MyPipeline', {...});
codePipeline.addStage(new MyStage(this, 'MyStage', {...})
codePipeline.buildPipeline();

As you can see, I just create a pipeline, add my stage and build it. After that is done, I make a codebuild project and action to run my buildspec:

const project = new cdk.aws_codebuild.PipelineProject(this, 'MyProject', {
  buildSpec: cdk.aws_codebuild.BuildSpec.fromSourceFilename(`buildspec.yml`),
});
const action = new cdk.aws_codepipeline_actions.CodeBuildAction({
  actionName: 'MyAction',
  project: project,
});
codePipeline.pipeline.addStage({
  stageName: 'MyActionStage',
  actions: [action],
  placement: {
    rightBefore: codePipeline.pipeline.stage('MyStage')
  }
});

It works and generates a nice pipeline. Is there any way I can pass my PROPERTY_FROM_BUILD from variable and use it in my stack?


Solution

  • While it took me about a week to figure this out, I've managed to make it work after all. Let me leave the solution here, in case someone else encounters the issue:

    Before even starting, extend your CodeBuildAction with a custom namespace, like:

    const action = new cdk.aws_codepipeline_actions.CodeBuildAction({
      actionName: 'MyAction',
      project: project,
      variablesNamespace: 'my_action_namespace',
    });
    

    First of all, you've got to figure out the index of your deployment stage, like:

    const deployIdx = codePipeline.pipeline.stages.indexOf(codePipeline.pipeline.stage('Deploy'));
    

    Then, find all actions of type "changeset replace":

    const actionsIdxs = codePipeline.pipeline.stage('Deploy').actions.filter(x => x.actionProperties.category === 'Deploy').map((x,i)=>i);
    

    Next, use this Escape Hatches feature to edit the generated template and attach the pipeline variable as ParameterOverrides to all deployment actions:

    const cfnPipeline = codePipeline.pipeline.node.findChild('Resource') as cdk.aws_codepipeline.CfnPipeline;
    for(const i of actionsIdxs){
      cfnPipeline.addOverride(
        `Properties.Stages.${deployIdx}.Actions.${i}.Configuration.ParameterOverrides`,
        JSON.stringify({
            // Use lowercase-only here to match the autogenerated name of the CfnParameter.
            propertyfrombuild: "#{my_action_namespace.PROPERTY_FROM_BUILD}"
        }));
    }
    

    And then, finally, in my stack, I can access the value like this:

    const PROPERTY_FROM_BUILD = new cdk.CfnParameter(this, 'propertyfrombuild').valueAsString;
    

    Make sure you name the variable all-lowercase with no special characters, and place it in your stack (not in a child construct).