Supporting multiple output artifacts from CodeBuild in CodePipeline

An AWS Lambda function for your pipeline

Andy Hayden
3 min readJan 22, 2018

After recent Jenkins security vulnerabilities*, we decided to move our CI/CD to the — relatively new — AWS solution: CodeBuild and CodePipeline. However, out-of-the-box a CodeBuild activity can’t return multiple output artifacts in CodePipeline. I’ve outlined how to work around this using the Lambda function below.

Two output artifacts from a CodeBuild.

*We missed a (critical) Jenkins update AND had temporarily left our server open to the world — whoops. A remote execution vulnerability was exploited to mine crypto currency (Monero). The t2 server exhausted its CPU credits and builds started to run very slowly…! This issue had already been reported and announced in the JenkinsCI Advisories.

Why CodePipeline

CodePipeline is in essence a state machine that passes around artifacts. Each activity takes Inputs and returns Outputs, and is either a Success or Failure. Each activity can also have side-effects e.g. a Deployment.

The Problem

After starting to migrate we realized: CodeBuild could only create at most one output artifact, even though Lambda and Jenkins (!) support up to 5.

Need for multiple output artifacts

One web deployment involved building several source bundle zips. Specifically .ebextensions must be in the source bundle. If .ebextensions differ across deployments then need multiple source bundles.
I bet there are plenty of other use cases.

An alternative workaround, is to define multiple CodePipelines or CodeBuilds. (One for each Elastic Beanstalk environment.) That’s what we did initially, but building the same code multiple times is unsatisfying…

The Solution

Until AWS CodeBuild supports more, a straightforward solution is to have:

  1. CodeBuild create up to 5 files which are zipped to a single Output artifact.
  2. Lambda unzip this Input artifact to the (5) files, and Outputs them.

Simple…

Example Code

A skeleton buildspec.yml to output two files into an output directory:

This creates two dummy files (FooZip and BarZip), and the location directory is used as the Output. The names of these output files must match CodePipeline (the expected Outputs of the Lambda activity).

Note, it’s critical to set Artifacts packaging: zip in the CodeBuild advanced settings. Otherwise, CodePipeline screws up nesting zips — and it’s zips all the way down.

This Lambda function: downloads the Output zip, unzips it and returns the Output artifacts FooZip and BarZip. It also does some book-keeping to CodePipeline to say when it’s completed.

I like Python, so I used Python (3.6). Most if the code is error handling…

This function needs to be given permission to label the activity outcome (Success or Failure). This is so CodePipeline knows whether it can carry on with further Stages.

It surprised me a little that the credentials, in the event, were only for artifacts. To be fair, the attribute for these credentials is named artifactCredentials.

The Lambda function’s role must include a policy to allows it to put the CodePipeline success or failure.

Our CodePipeline to run the CodeBuild and then invoke the Lambda function:

CodeBuild (left) to Lambda (middle) to Elastic Beanstalk (right)

et voilà

Addendum

  1. Our Elastic Beanstalk environments post to Slack on environment updates, in my continued efforts to Slack all the things...
  2. CodeBuild can cache dependencies. You should do that.
  3. Legal: I am licensing all code in this article as MIT. Enjoy!

--

--