Skip to content

Instantly share code, notes, and snippets.

@acmcelwee
Last active April 9, 2020 16:01
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save acmcelwee/6f488f3d74b2734ca159d105d2927a9e to your computer and use it in GitHub Desktop.
Save acmcelwee/6f488f3d74b2734ca159d105d2927a9e to your computer and use it in GitHub Desktop.
Workflows-friendly adaptation of CircleCI's build locking https://github.com/bellkev/circle-lock-test
#!/usr/bin/env bash
set -o xtrace -o errexit -o pipefail -o nounset
########################################################################################
# CircleCI's current recommendation for roughly serializing a subset
# of build commands for a given branch
#
# circle discussion thread - https://discuss.circleci.com/t/serializing-deployments/153
# Code from - https://github.com/bellkev/circle-lock-test
########################################################################################
# sets $branch, $job_name, $tag, $rest
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-b|--branch) branch="$2" ;;
-j|--job-name) job_name="$2" ;;
-t|--tag) tag="$2" ;;
*) break ;;
esac
shift 2
done
rest=("$@")
}
# reads $branch, $tag, $commit_message
should_skip() {
if [[ "$branch" && "$CIRCLE_BRANCH" != "$branch" ]]; then
echo "Not on branch $branch. Skipping..."
return 0
fi
if [[ "$job_name" && "$CIRCLE_JOB" != "$job_name" ]]; then
echo "Not running $job_name. Skipping..."
return 0
fi
if [[ "$tag" && "$commit_message" != *\[$tag\]* ]]; then
echo "No [$tag] commit tag found. Skipping..."
return 0
fi
return 1
}
# reads $branch, $job_name, $tag
# sets $jq_prog
make_jq_prog() {
local jq_filters=""
if [[ $branch ]]; then
jq_filters+=" and .branch == \"$branch\""
fi
if [[ $job_name ]]; then
jq_filters+=" and (.workflows?.job_name? == \"$job_name\" or .build_parameters?.CIRCLE_JOB? == \"$job_name\")"
fi
if [[ $tag ]]; then
jq_filters+=" and (.subject | contains(\"[$tag]\"))"
fi
jq_prog=".[] | select(.build_num < $CIRCLE_BUILD_NUM and (.status | test(\"running|pending|queued\")) $jq_filters) | .build_num"
}
if [[ "$0" != *bats* ]]; then
set -e
set -u
set -o pipefail
branch=""
tag=""
job_name=""
rest=()
api_url="https://circleci.com/api/v1/project/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME?circle-token=$CIRCLE_TOKEN&limit=100"
parse_args "$@"
commit_message=$(git log -1 --pretty=%B)
if should_skip; then exit 0; fi
make_jq_prog
echo "Checking for running builds..."
# unset errexit so we can detect and handle temporary circleci api failures
set +e
consecutive_failures=0
while true; do
builds=$(curl --fail --silent --connect-timeout 5 --max-time 10 -H "Accept: application/json" "$api_url" | jq "$jq_prog")
if [[ $? -ne 0 ]]; then
echo "CircleCI api call failed"
consecutive_failures=$(($consecutive_failures + 1))
elif [[ ! -z ${builds} ]]; then
# reset failure counter
consecutive_failures=0
echo "Waiting on builds:"
echo "$builds"
else
break
fi
# limit the number of consecutive failures that we're willing to tolerate
if [[ ${consecutive_failures} -gt 5 ]]; then
echo "Failed $consecutive_failures consecutive attempts...giving up"
exit 1
fi
echo "Retrying in 10 seconds..."
sleep 10
done
echo "Acquired lock"
if [[ "${#rest[@]}" -ne 0 ]]; then
"${rest[@]}"
fi
fi
@phoolish
Copy link

FYI The -j|--job-name argument is required.

@jmcvetta
Copy link

Super awesome 👍

@acmcelwee
Copy link
Author

@jmcvetta, I'd forgotten that I even had this in a public gist, but now that you've reminded me, I've updated it to include some error handling around occasional circleci api failures. We had to add that a year or so back, because we were seeing builds fail due to CircleCI api error causing the lock script to fail, which then bubbled up to the job and forced us to re-run. Tolerating occasional failures like this in the lock script drastically improved our satisfaction w/ this rudimentary approach. Enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment