Tips for Writing Concourse Resource-Types in Bash
Writing Concourse resource-types in bash can be fairly fast, especially if you’re relying on external tools to do some of the heavy lifting for you. I think a lot of us in this space are also use to do writing up quick bash scripts to do things, so writing up a quick and dirty resource-type in bash feels natural. Here are some tips for writing resource-types in bash.
I’m not going to explain how resource-types work in Concourse. I’ll instead refer you to the documentation: resource-types
1. Redirecting stdout
and stderr
The top of all of my resource-type scripts start with this; no exceptions:
#!/usr/bin/env bash
set -euo pipefail
exec 3>&1 # make stdout available as file descriptor 3 for the result
exec 1>&2 # redirect all output to stderr for logging
The first two lines are standard “this is how all your bash scripts
should start” stuff. I won’t explain why you should use them here. The
last two lines starting with exec
are the Concourse-specific bits. I’ll
explain what those do.
Concourse expects the information for a version
generated by the
resource to come from stdout
. We need to lock down output to stdout
then. We do this by first mapping file descriptor (fd) 3
to 1
. This
means when we write to fd 3
we’ll actually write to stdout
. Then to
stop every other executable from writing to stdout
by default within our
shell session, we remap fd 1
to 2
which is stderr
. Whenever
something writes to fd 1
it’ll actually be written to fd 2
, the
stderr
file.
2. Reading Input From Concourse
Concourse can pass information on stdin
to your script. Here’s how I
capture and parse this information. This part requires jq
since
Concourse passes in data as JSON.
payload="$(cat <&0)"
my_var="$(jq -r '.source.my_var // ""' <<< "$payload")"
foobar="$(jq -r '.source.foobar // ""' <<< "$payload")"
3. Logging Errors
Logging errors in your script is easy now after redirecting fd 1
to 2
.
Simple call echo
with your error and exit 1
. Exiting with exit code
1
is how you tell Concourse that something went wrong. It’s also
standard behaviour in Unix in general.
echo "The really bad thing happened!"
exit 1
I commonly error out if some inputs to the script are not set.
if [[ -z "${my_var}" ]]; then
echo "my_var is not set and is required"
exit 1
fi
4. Capturing the input/output Paths
Only applies to the /opt/resource/in
and /opt/resource/out
scripts.
Depending on what your resource-type does, these may be completely irrelevant to you and you can ignore them.
Both the in
and out
scripts are passed a file path as the first
command line argument. For the in
script, this is a file path where it
can store artifacts that later steps in a job can use.
For the out
script, this is where artifacts from previous steps are
usually passed in.
For /opt/resource/in
I usually write:
output_dir="${1}"
For /opt/resource/out
I usually write:
inputs_dir="${1}"
To avoid worrying about if there’s a trailing slash or not I always add a slash after the variable like so:
echo "hello" > "${output_dir}/world"
This works because having two forward slashes //
in a path is the same
as having one forward slash. So /tmp/world
and /tmp//world
are
equivalent.
5. Outputting Versions with jq
For the following examples, $version
would be set to a json object of
key-value pairs, such as:
version='{"id": "abc123"}'
The tldr of this tip is that --argjson
is your best friend for crafting
json responses
To output a new version in a /opt/resource/check
script I’ll use the
following jq
:
jq -n \
--argjson version $version "[$version]" >&3
For the /opt/resource/in
script I’ll get the version
passed in on
stdin
and re-use it:
version=$(jq -r '.version // ""' <<< "$payload")
# rest of the script here
jq -n \
--argjson version $version \
'{
version: $version,
metadata: []
}' >&3
It’s the same for /opt/resource/out
, except $version
will come from a
file or the put steps params
:
jq -n \
--argjson version $version \
'{
version: $version,
metadata: []
}' >&3
6. Packaging the Resource-type
Not much of a tip, but here’s the Dockerfile that I use for all my
bash-based resource-types. I use a custom BASE_IMAGE
that has jq
and
bash
installed.
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN mkdir -p /opt/resource
COPY check.sh /opt/resource/check
COPY in.sh /opt/resource/in
COPY out.sh /opt/resource/out