Allows steps to be injected into the pipeline based on a common template.
A common requirement at Culture Amp is to have a series of steps that are repeated (either on demand or automatically) by environment. This is done by writing a block/select step, after which a generation script is run. The script then uploads pipeline steps for each of the selected environments.
For example, if there were two destination environments: "development" and "production", and a step ran a bash script to execute for each, you would have a selection step with a pipeline step:
- block: Deploy to?
fields:
- select: Environment
key: deploy-environment
multiple: true
required: true
options:
- label: Dev
value: development
- label: Prod
value: production
- name: ":pipeline:"
command: "bin/ci_deployment_steps"
The deployment steps script gets the deploy-environment
key from build metadata,
then runs buildkite-agent pipeline upload
on a YAML fragment:
steps:
- label: "Deploy to ${STEP_ENVIRONMENT})"
depends_on: "build"
command: "bin/ci_deploy"
env:
TARGET: ${STEP_ENVIRONMENT}
concurrency: 1
concurrency_group: ${BUILDKITE_PIPELINE_SLUG}/${STEP_ENVIRONMENT}/deploy
agents:
queue: ${AGENT}
The environment changes for each upload
, allowing Buildkite to execute differently
in each of the target environments.
A deployment can be made repeatable for a pipeline by uploading the "selection" fragment after the deployment steps, allowing you to select again.
This plugin focuses on implementing the script that can output the selection steps and the deployment steps, reducing the amount of "glue" code that is shared between repositories.
The idea is that the repo supplies at least two ingredients:
- The environment steps template (
step-template
), and - Either (or both) of:
- The environment selection template (
selector-template
) - The automatic selections (
auto-selections
)
- The environment selection template (
Given these, the plugin will upload the step-template
as a pipeline fragment, modifying
the environment for the fragment each time it's uploaded, allowing the same fragment to
be used with different base parameters.
You may optionally supply a series of .env
files alongside the step-template
, named
for each of the expected environments. These are key/value pairs that will form part of
the steps environment on each execution.
Given the following in the pipeline.yml
:
steps:
- plugins:
- cultureamp/step-templates#v1.2.0:
step-template: deploy-steps.yml
step-var-names: ["type", "region"]
auto-selections:
- "production-us;production;us-west-1"
- "production-eu;production;eu-west-2"
selector-template: deploy-selector.yml
This then will require two other templates to exist, both of which are Buildkite pipelines:
deploy-steps.yaml
# output for each selected environment
steps:
- label: "Deploy to ${STEP_ENVIRONMENT} (${REGION})"
depends_on: "build"
command: "bin/ci_deploy"
env:
ENV: ${STEP_ENVIRONMENT}
concurrency: 1
concurrency_group: ${BUILDKITE_PIPELINE_SLUG}/${STEP_ENVIRONMENT}/${TYPE}/${REGION}/deploy
agents:
queue: ${AGENT}
deploy-selector.yaml
# Presents an environment selector driving the output of pipeline
# steps per selected environment
steps:
- block: Deploy to?
fields:
- select: Farm
key: deploy-environment
multiple: true
required: true
options:
- label: Env 1
# semi-colon separated, supplied to the template as environment variables. The first will
# be called `STEP_ENVIRONMENT`, the second `ENV` and the third, `REGION`
value: development-us;flamingo;us-west-2
- label: Env 2
value: staging-eu;preprod;eu-west-1
- plugins:
- cultureamp/step-templates#v1.2.0:
step-template: deploy-steps.yml
# names the second and subsequent variables supplied as arguments
# environment (`ENV` and `REGION`). If this wasn't supplied, they would be called:
# `STEP_VAR_1` and `STEP_VAR_2`.
step-var-names: ["env", "region"]
selector-template: deploy-selector.yml
You could (optionally) supply a file called staging-us.env
alongside
deploy-steps.yml
, with additional environment variables to set for that
environment:
AGENT=staging_agent_pool_name
This is an easy way of adding additional variables per environment without making the selector definitions very long and hard to read.
The block step (from the deploy-selector.yaml
) has a select
field.
When Buildkite receives user input from the UI, it creates a buildkite meta-data
item with the key supplied for the select
field (deploy-environment
).
Buildkite sets the value of that key to a newline-separated string of the values
selected.
This will be something like:
development-us;flamingo;us-west-2
staging-us;preprod;us-west-2
The deploy-selector.yaml
has another step that executes the plugin, running
after a selection occurs. The plugin reads the meta-data key and iterates over
the lines (created from the user selection).
For each line, it's split on ;
, and the first value is always assigned to
STEP_ENVIRONMENT
. Since step-var-names: ["type", "region"]
is defined, the
next two components will be assigned to the TYPE
and REGION
variable names
respectively
So for the first line of the above example, we'll get:
export STEP_ENVIRONMENT=development-us
export TYPE=flamingo
export REGION=us-west-2
These variables are in the environment when the plugin runs buildkite-agent pipeline upload
for deploy-steps.yml
, so any variable references in the YAML
will be interpolated by the agent correctly.
The template to render for each selected/specified environment. The selected
environment will be presented as STEP_ENVIRONMENT
, and additional variables
will be given as STEP_VAR_1
to STEP_VAR_n
unless named otherwise by
step-var-names
.
Note that if there a files alongside this YAML file named STEP_ENVIRONMENT.env
the key/value pairs specified therein will be present for the template as
environment variables.
The selector can have semi-colon separated values: this names the second
and subsequent values and avoids the default STEP_VAR_n
name. The supplied
names are uppercased.
Thus if the names were ["type", "region"]
, and the value was
staging;preprod;us-west-1
, the step template would receive the following
environment:
STEP_ENVIRONMENT=staging
TYPE=preprod
REGION=us-west-1
Alternatively:
It is possible to use export
style syntax in the env file for more complex use cases.
export STEP_ENVIRONMENT="staging"
export TYPE="preprod"
export REGION="us-west-1"
The two syntaxes cannot be used in the same file. The presence of a line with the prefix export \w
will trigger the alternate syntax.
A list of environment pre-selections that will be rendered immediately by the plugin using the values specified (semi-colon separated).
When a template is rendered as an auto-selection, the value of the standard
Buildkite variable BUILDKITE_PIPELINE_DEFAULT_BRANCH
will be copied to an
environment variable named AUTO_SELECTION_DEFAULT_BRANCH
. This allows steps
rendered for auto-selections to use branch filters that work differently. For
example, a step definition like:
steps:
- label: "Deploy to ${STEP_ENVIRONMENT} (${REGION})"
command: "bin/ci_deploy"
branches: "${AUTO_SELECTION_DEFAULT_BRANCH:-*}"
When output as an auto-selection, it will only run on the default branch. When output from a selector, it will run on any branch.
More details can be found in the wiki.
A template containing the available environment specified as a Buildkite pipeline
block
step that supplies a set of fields
for selection. The selection may be
optional.
Note: The value for
key:
has to be unique per pipeline, as it is used as the name of the metadata key that the selections are read from. If you use have a pipeline with multiple block steps that have options, each of them has to be assigned a different value.
This repository tests its functionality using the BATS testing framework for Bash. Be sure to add tests for any new functionality. Using the tests can significantly speed development time when compared to testing in a real pipeline, and it's a big win for maintenance.
To run the tests:
docker-compose run --rm tests
Running the linter:
docker-compose run --rm lint