Teams may make use of multiple branches for their development. For instance, some teams create feature branches while
working on new functionality - once this functionality is ready, the branch will be merged into the main branch and the
feature branch will be deleted.
While a feature is under development, you'll often want to run tests against the feature branch and possibly deploy to a
staging environment. To model this in Concourse, you'll need to have a pipeline for each active feature branch. Manually
setting (and eventually archiving) a pipeline for each feature branch would be quite a burden. For this type of
workflow, Concourse has a few important tools to help you out: the set_pipeline step, across, and instanced pipelines.
In this guide, we'll cover:
Writing a pipeline to Test, Build & Deploy a branch to a staging environment. We'll
use Terraform for our deployment
We'll start out by defining the pipeline that should run for each active branch. For this example, we'll be working with
the following sample Go application.
Our pipeline will have three stages:
Run unit tests
Build and upload a binary to a blobstore (in our case, we'll
use Google Cloud Storage)
Trigger a terraform apply to deploy our app to a staging environment.
The Terraform module we'll use here
doesn't actually provision any infrastructure, and is just used as an example
Since the pipeline config is intended to be used as a template for multiple different branches, we can
use Vars to parameterize the config. In particular, we'll use the vars ((feature)) and ((branch)),
which represent the name of the feature and the name of the branch, respectively.
Below is the full pipeline config from
the Examples Repo:
In addition to the branch pipeline template, we'll also need a pipeline to track the list of branches and set a pipeline
for each one.
To track the list of branches in a repository, we can use aoldershaw/git-branches-resource. This resource_type emits a new resource version
whenever a branch is created or deleted. It also lets us filter the list of branches by a regular expression. In this
case, let's assume our feature branches match the regular expression feature/.*.
Below is the current pipeline config for this tracker pipeline:
resource_types:-name:git-branchestype:registry-imagesource:repository:aoldershaw/git-branches-resourceresources:-name:feature-branchestype:git-branchessource:uri:https://github.com/concourse/examples# The "(?P<name>pattern)" syntax defines a named capture group.# aoldershaw/git-branches-resource emits the value of each named capture# group under the `groups` key.## e.g. feature/some-feature ==> {"groups": {"feature": "some-feature"}}branch_regex:'feature/(?P<feature>.*)'-name:examplestype:gitsource:uri:https://github.com/concourse/examplesjobs:-name:set-feature-pipelinesplan:-in_parallel:-get:feature-branchestrigger:true-get:examples-load_var:branchesfile:feature-branches/branches.json-across:-var:branchvalues:((.:branches))set_pipeline:devfile:examples/pipelines/multi-branch/template.ymlinstance_vars:{ feature:((.:branch.groups.feature))}vars:{ branch:((.:branch.name))}
We set each pipeline as an instanced pipeline - this will result in Concourse
grouping all the related dev pipelines in the UI.
Cleaning Up Old Workspaces
With the setup described in Tracking Branches, Concourse will automatically archive any pipelines
for branches that get removed. However, Concourse doesn't know that it should destroy Terraform workspaces when a branch
is removed. To accomplish this, we can yet again make use of
the Terraform resource to destroy these workspaces. We'll add
another job to
the tracker pipeline that
figures out which workspaces don't belong to an active branch and destroy them.
resource_types:-name:git-branchestype:registry-imagesource:repository:aoldershaw/git-branches-resource-name:terraformtype:registry-imagesource:repository:ljfranklin/terraform-resourceresources:-name:feature-branchestype:git-branchessource:uri:https://github.com/concourse/examples# The "(?P<name>pattern)" syntax defines a named capture group.# aoldershaw/git-branches-resource emits the value of each named capture# group under the `groups` key.## e.g. feature/some-feature ==> {"groups": {"feature": "some-feature"}}branch_regex:'feature/(?P<feature>.*)'-name:examplestype:gitsource:uri:https://github.com/concourse/examples-name:staging-envtype:terraformsource:backend_type:gcsbackend_config:&terraform_backend_configbucket:concourse-examplesprefix:multi-branch/terraformcredentials:((gcp_service_account_key))jobs:-name:set-feature-pipelinesbuild_log_retention:builds:50plan:-in_parallel:-get:feature-branchestrigger:true-get:examples-load_var:branchesfile:feature-branches/branches.json-across:-var:branchvalues:((.:branches))set_pipeline:devfile:examples/pipelines/multi-branch/template.ymlinstance_vars:{feature:((.:branch.groups.feature))}vars:{branch:((.:branch.name))}-name:cleanup-inactive-workspacesbuild_log_retention:builds:50plan:-in_parallel:-get:feature-branchespassed:[set-feature-pipelines]trigger:true-get:examples-task:find-inactive-workspacesconfig:platform:linuximage_resource:type:registry-imagesource:{repository:hashicorp/terraform}inputs:-name:feature-branchesoutputs:-name:extra-workspacesparams:TERRAFORM_BACKEND_CONFIG:gcs:*terraform_backend_configrun:path:shargs:--c-|set -euo pipefailapk add -q jqactive_features="$(jq '[.[].groups.feature]' feature-branches/branches.json)"jq -n "{terraform: {backend: $TERRAFORM_BACKEND_CONFIG}}" > backend.tf.jsonterraform init# List all active workspaces, ignoring the default workspaceactive_workspaces="$(terraform workspace list | grep -v '^[*]' | tr -d ' ' | jq --raw-input --slurp 'split("\n") | map(select(. != ""))')"jq -n "$active_workspaces - $active_features" > extra-workspaces/workspaces.json-load_var:extra_workspacesfile:extra-workspaces/workspaces.json-across:-var:workspacevalues:((.:extra_workspaces))put:staging-envparams:terraform_source:examples/terraform/stagingenv_name:((.:workspace))action:destroyget_params:action:destroy