Concourse Manual Approval Step
A request I’ve seen from Concourse users every so often is that they want a way for a Concourse Job to stop what it’s doing and wait for approval from a human. They want what I call a “manual approval” step in their jobs.
A more concrete example that I’ve seen is when users are running terraform apply
in their pipelines. They usually end up wanting a Concourse Job that
looks like:
- Run
terraform plan
- Have human review and approve the plan
- Job continues running and runs
terraform apply
Steps 1 and 3 are obvious to do for anyone that’s written a Concourse pipeline. Step two, the “manual approval” step is less obvious. There are many ways such a step could be crafted. This is how I decided to solve it since it only requires a small bash script.
#!/usr/bin/env bash
set -euo pipefail
echo -n "waiting for manual approval..."
while true; do
sleep 5
if [[ -f /tmp/approved ]]; then
break
fi
echo -n "."
done
To approve the job when it gets to this step I’d run the following from my local terminal:
fly -t ci i -u BUILD_URL --step manual-approval touch /tmp/approved
If I want to reject the job then I simply hit the big red cancel button in the build logs page in the Concourse web UI.
More Bells and Whistles
Now this script is super simple. One thing you’ll probably want to change is to
have the step timeout after waiting some period of time. Let’s modify the
while
loop condition to timeout after 10mins.
#!/usr/bin/env bash
set -euo pipefail
timeout=$(($EPOCHSECONDS+600))
echo -n "waiting for manual approval..."
while [[ ${$EPOCHSECONDS} -lt ${timeout} ]]; do
sleep 5
if [[ -f /tmp/approved ]]; then
break
fi
echo -n "."
done
if [[ ! -f /tmp/approved ]]; then
echo "Approval timeout reached. Aborting job."
exit 1
fi
We’ve got a few changes here. Let’s go through them.
First is the usage of Bash’s $EPOCHSECONDS
variable.
$EPOCHSECONDS
returns the number of seconds since the Unix Epoch.
We take the value of $EPOCHSECONDS
and add 10 minutes in seconds to get the time
in the future of our timeout. Also note two sets of parentheses are used. This
is how you do math in Bash apparently!
The last addition to discuss is the second if [[ !-f /tmp/approved ]];
after
the while
loop. The second if
is necessary to check why we exited the
while
loop. If the step was approved, in which case /tmp/approved
should
exist, then the script can exit normally. If the file does not exist then we
can assume the timeout was reached and fail the step, and subsequently the
Concourse Job.
A final change we could make is to parameterize our timeout value. We can set
the default timeout value in the Concourse Task Configuration. Pipeline authors
can override the timeout in their
task.params
when adding this step to their job plan.
platform: linux
inputs:
- name: repo
params:
APPROVAL_TIMEOUT: 600 #default of 10mins
run:
path: repo/tasks/manual-approval/run.sh
Our run.sh
:
#!/usr/bin/env bash
set -euo pipefail
timeout=$((EPOCHSECONDS+APPROVAL_TIMEOUT))
echo -n "waiting for manual approval..."
while [[ ${EPOCHSECONDS} -lt ${timeout} ]]; do
sleep 5
if [[ -f /tmp/approved ]]; then
break
fi
echo -n "."
done
if [[ ! -f /tmp/approved ]]; then
echo "Approval timeout reached. Aborting job."
exit 1
fi
That’s how I do manual approval steps in my pipelines. There are of course many different ways you could do this, probably with a better UX for whoever the end user is that needs to approve the job. I found this is the simplest way to add this kind of step without pulling in a bunch of extra tooling into my stack though.