Use CircleCI version 2.1 at the top of your .circleci/config.yml file.
1
version: 2.1
Add the orbs
stanza below your version, invoking the orb:
1
2
orbs:
workflow-queue: promiseofcake/workflow-queue@dev:da9c07f1813165fa2b6f902c50068d6eceba93de
Use workflow-queue
elements in your existing workflows and jobs.
Opt-in to use of uncertified orbs on your organization’s Security settings page.
Sample example description.
1
2
3
4
5
6
7
version: '2.1'
orbs:
<orb-name>: <namespace>/<orb-name>@1.2.3
workflows:
use-my-orb:
jobs:
- <orb-name>/<job-name>
This job is executed and blocks further execution until the necessary criteria is met.
PARAMETER | DESCRIPTION | REQUIRED | DEFAULT | TYPE |
---|---|---|---|---|
confidence | Due to concurrency issues, how many times should we requery the pipeline list to ensure previous jobs are "pending", but not yet active. This number indicates the threshold for API returning no previous pending pipelines. Default is one confirmation, increase if you see issues.
| No | '1' | string |
debug | If enabled, DEBUG messages will be logged. | No | false | boolean |
dont-quit | Force job through once time expires instead of failing. | No | false | boolean |
only-on-branch | Only queue on specified branch | No | '*' | string |
time | Minutes to wait before giving up. | No | '10' | string |
This command is executed and blocks further execution until the necessary criteria is met.
PARAMETER | DESCRIPTION | REQUIRED | DEFAULT | TYPE |
---|---|---|---|---|
confidence | Due to concurrency issues, how many times should we requery the pipeline list to ensure previous jobs are "pending", but not yet active. This number indicates the threshold for API returning no previous pending pipelines. Default is one confirmation, increase if you see issues.
| No | '1' | string |
debug | If enabled, DEBUG messages will be logged. | No | true | boolean |
dont-quit | Force job through once time expires instead of failing. | No | false | boolean |
only-on-branch | Only queue on specified branch | No | '*' | string |
time | Minutes to wait before giving up. | No | '10' | string |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# This code is licensed from CircleCI to the user under the MIT license.
# See here for details: https://circleci.com/developer/orbs/licensing
version: 2.1
description: |
Allows jobs or entire workflows to be queued to ensure they run in serial. This is ideal for deployments or other activities that must not run concurrently. May optionaly consider branch-level isolation if unique branches should run concurrently. This orb requires the project to have an CircleCI API key in order to query build states.
display:
home_url: https://github.com/promiseofcake/circleci-workflow-queue
source_url: https://github.com/promiseofcake/circleci-workflow-queue
commands:
block_execution:
description: |
This command is executed and blocks further execution until the necessary criteria is met.
parameters:
confidence:
default: "1"
description: |
Due to concurrency issues, how many times should we requery the pipeline list to ensure previous jobs are "pending", but not yet active. This number indicates the threshold for API returning no previous pending pipelines. Default is one confirmation, increase if you see issues.
type: string
debug:
default: true
description: If enabled, DEBUG messages will be logged.
type: boolean
dont-quit:
default: false
description: Force job through once time expires instead of failing.
type: boolean
only-on-branch:
default: '*'
description: Only queue on specified branch
type: string
time:
default: "10"
description: Minutes to wait before giving up.
type: string
steps:
- run:
command: |
#!/bin/bash
debug() {
if [ "${CONFIG_DEBUG_ENABLED}" == "1" ]; then
echo "DEBUG: ${*}"
fi
}
tmp="/tmp"
pipeline_file="${tmp}/pipeline_status.json"
workflows_file="${tmp}/workflow_status.json"
load_variables(){
# just confirm our required variables are present
: "${CIRCLE_WORKFLOW_ID:?"Required Env Variable not found!"}"
: "${CIRCLE_PROJECT_USERNAME:?"Required Env Variable not found!"}"
: "${CIRCLE_PROJECT_REPONAME:?"Required Env Variable not found!"}"
: "${CIRCLE_REPOSITORY_URL:?"Required Env Variable not found!"}"
: "${CIRCLE_JOB:?"Required Env Variable not found!"}"
# Only needed for private projects
if [ -z "${CIRCLECI_USER_AUTH}" ]; then
echo "CIRCLECI_USER_AUTH not set. Private projects will be inaccessible."
else
fetch "https://circleci.com/api/v2/me" "/tmp/me.cci"
me=$(jq -e '.id' /tmp/me.cci)
echo "Using API key for user: ${me}"
fi
}
fetch(){
debug "Making API Call to ${1}"
url=$1
target=$2
debug "API CALL ${url}"
http_response=$(curl -s -X GET -H "Authorization: Basic ${CIRCLECI_USER_AUTH}" -H "Content-Type: application/json" -o "${target}" -w "%{http_code}" "${url}")
if [ "${http_response}" != "200" ]; then
echo "ERROR: Server returned error code: $http_response"
debug "${target}"
exit 1
else
debug "API Success"
fi
}
fetch_pipelines(){
: "${CIRCLE_BRANCH:?"Required Env Variable not found!"}"
echo "Only blocking execution if running previous workflows on branch: ${CIRCLE_BRANCH}"
pipelines_api_url_template="https://circleci.com/api/v2/project/gh/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/pipeline?branch=${CIRCLE_BRANCH}"
debug "DEBUG Attempting to access CircleCI API. If the build process fails after this step, ensure your CIRCLECI_USER_AUTH is set."
fetch "${pipelines_api_url_template}" "${pipeline_file}"
echo "DEBUG API access successful"
}
fetch_pipeline_workflows(){
for pipeline in $(jq -r ".items[] | .id //empty" ${pipeline_file} | uniq)
do
debug "Checking time of pipeline: ${pipeline}"
pipeline_detail=${tmp}/pipeline-${pipeline}.json
fetch "https://circleci.com/api/v2/pipeline/${pipeline}/workflow" "${pipeline_detail}"
created_at=$(jq -r '.items[] | .created_at' "${pipeline_detail}")
debug "Pipeline was created at: ${created_at}"
done
jq -s '[.[].items[] | select((.status == "running") or (.status == "created"))]' ${tmp}/pipeline-*.json > ${workflows_file}
}
# CIRCLE_BUILD_NUM is going to be for job, not for pipeline
load_current_workflow_values(){
my_commit_time=$(jq ".[] | select (.id == \"${CIRCLE_WORKFLOW_ID}\").created_at" ${workflows_file})
my_workflow_id=$(jq ".[] | select (.id == \"${CIRCLE_WORKFLOW_ID}\").id" ${workflows_file})
}
update_comparables(){
fetch_pipelines
fetch_pipeline_workflows
load_current_workflow_values
# need to only get the workflows which are created or running
# need to pull out the workflows that are failed, and stuff, we need to only look for running ones
# also need to make sure that job # and pipeline # are interchangable
echo "This job will block until no previous workflows have *any* workflows running."
oldest_running_workflow_id=$(jq '. | sort_by(.created_at) | .[0].id' ${workflows_file})
oldest_commit_time=$(jq '. | sort_by(.created_at) | .[0].created_at' ${workflows_file})
if [ -z "${oldest_commit_time}" ] || [ -z "${oldest_running_workflow_id}" ]; then
echo "ERROR: API Error - unable to load previous workflow timings. File a bug"
exit 1
fi
debug "Oldest workflow: ${oldest_running_workflow_id}"
}
cancel_current_workflow(){
echo "Cancelleing workflow ${my_workflow_id}"
cancel_api_url_template="https://circleci.com/api/v2/workflow/${my_workflow_id}"
curl -s -X POST -H "Authorization: Basic ${CIRCLECI_USER_AUTH}" -H "Content-Type: application/json" "${cancel_api_url_template}" > /dev/null
}
if [ "${CONFIG_ONLY_ON_BRANCH}" = "*" ] || [ "${CONFIG_ONLY_ON_BRANCH}" = "${CIRCLE_BRANCH}" ]; then
echo "${CIRCLE_BRANCH} queueable"
else
echo "Queueing only happens on ${CONFIG_ONLY_ON_BRANCH} branch, skipping queue"
exit 0
fi
### Set values that wont change while we wait
load_variables
max_time=${CONFIG_TIME}
echo "This build will block until all previous builds complete."
echo "Max Queue Time: ${max_time} minutes."
wait_time=0
loop_time=11
max_time_seconds=$((max_time * 60))
### Queue Loop
confidence=0
while true; do
update_comparables
echo "This Workflow Timestamp: ${my_commit_time}"
echo "Oldest Workflow Timestamp: ${oldest_commit_time}"
if [[ -n "${my_commit_time}" ]] && [[ "${oldest_commit_time}" > "${my_commit_time}" || "${oldest_commit_time}" = "${my_commit_time}" ]] ; then
# API returns Y-M-D HH:MM (with 24 hour clock) so alphabetical string compare is accurate to timestamp compare as well
# Workflow API does not include pending, so it is posisble we queried in between a workfow transition, and we;re NOT really front of line.
if [ $confidence -lt "${CONFIG_CONFIDENCE}" ];then
# To grow confidence, we check again with a delay.
confidence=$((confidence+1))
echo "API shows no previous workflows, but it is possible a previous workflow has pending jobs not yet visible in API."
echo "Rerunning check ${confidence}/${CONFIG_CONFIDENCE}"
else
echo "Front of the line, WooHoo!, Build continuing"
break
fi
else
# If we fail, reset confidence
confidence=0
echo "This build (${CIRCLE_WORKFLOW_ID}) is queued, waiting for workflow (${oldest_running_workflow_id}) to complete."
echo "Total Queue time: ${wait_time} seconds."
fi
if [ $wait_time -ge $max_time_seconds ]; then
echo "Max wait time exceeded, considering response."
if [ "${CONFIG_DONT_QUIT}" == "1" ];then
echo "Orb parameter dont-quit is set to true, letting this job proceed!"
exit 0
else
cancel_current_workflow
sleep 10 # wait for API to cancel this job, rather than showing as failure
exit 1 # but just in case, fail job
fi
fi
sleep $loop_time
wait_time=$(( loop_time + wait_time ))
done
environment:
CONFIG_CONFIDENCE: << parameters.confidence >>
CONFIG_DEBUG_ENABLED: << parameters.debug >>
CONFIG_DONT_QUIT: << parameters.dont-quit >>
CONFIG_ONLY_ON_BRANCH: << parameters.only-on-branch >>
CONFIG_TIME: << parameters.time >>
name: Block execution until workflow is at the front of the line
jobs:
queue:
description: |
This job is executed and blocks further execution until the necessary criteria is met.
docker:
- image: cimg/base:stable
parameters:
confidence:
default: "1"
description: |
Due to concurrency issues, how many times should we requery the pipeline list to ensure previous jobs are "pending", but not yet active. This number indicates the threshold for API returning no previous pending pipelines. Default is one confirmation, increase if you see issues.
type: string
debug:
default: false
description: If enabled, DEBUG messages will be logged.
type: boolean
dont-quit:
default: false
description: Force job through once time expires instead of failing.
type: boolean
only-on-branch:
default: '*'
description: Only queue on specified branch
type: string
time:
default: "10"
description: Minutes to wait before giving up.
type: string
resource_class: small
steps:
- block_execution:
confidence: <<parameters.confidence>>
debug: << parameters.debug >>
dont-quit: <<parameters.dont-quit>>
only-on-branch: <<parameters.only-on-branch>>
time: <<parameters.time>>
examples:
example:
description: |
Sample example description.
usage:
version: "2.1"
orbs:
<orb-name>: <namespace>/<orb-name>@1.2.3
workflows:
use-my-orb:
jobs:
- <orb-name>/<job-name>