Skip to content

Commit 04d9bdf

Browse files
committed
feat: add logic
1 parent 1c6ba93 commit 04d9bdf

File tree

2 files changed

+149
-7
lines changed

2 files changed

+149
-7
lines changed

src/commands/queue.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ parameters:
2525
Due to concurrency issues, how many times should we requery the pipeline list to ensure previous jobs are "pending",
2626
but not yet active. This number indicates the threshold for API returning no previous pending pipelines.
2727
Default is one confirmation, increase if you see issues.
28-
circleci-user-auth:
29-
type: env_var_name
30-
default: CIRCLECI_USER_AUTH
31-
description: "In the event you wish to supply CCI basic-auth credentials via a different environment value, set the name here."
3228
steps:
3329
- run:
3430
name: Queue Until Front of Line
3531
environment:
36-
DEBUG_ENABLED: "<< parameters.debug >>"
32+
CONFIG_DEBUG_ENABLED: "<< parameters.debug >>"
33+
CONFIG_TIME: "<< parameters.time >>"
34+
CONFIG_DONT_QUIT: "<< parameters.dont-quit >>"
35+
CONFIG_ONLY_ON_BRANCH: "<< parameters.only-on-branch >>"
36+
CONFIG_CONFIDENCE: "<< parameters.confidence >>"
3737
command: <<include(scripts/queue.sh)>>

src/scripts/queue.sh

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,147 @@ debug() {
66
fi
77
}
88

9-
debug "this is a test"
10-
echo "this is a test"
9+
tmp="/tmp"
10+
pipeline_file="${tmp}/pipeline_status.json"
11+
workflows_file="${tmp}/workflow_status.json"
12+
13+
load_variables(){
14+
# just confirm our required variables are present
15+
: ${CIRCLE_BUILD_NUM:?"Required Env Variable not found!"}
16+
: ${CIRCLE_WORKFLOW_ID:?"Required Env Variable not found!"}
17+
: ${CIRCLE_PROJECT_USERNAME:?"Required Env Variable not found!"}
18+
: ${CIRCLE_PROJECT_REPONAME:?"Required Env Variable not found!"}
19+
: ${CIRCLE_REPOSITORY_URL:?"Required Env Variable not found!"}
20+
: ${CIRCLE_JOB:?"Required Env Variable not found!"}
21+
# Only needed for private projects
22+
if [ -z "${CIRCLECI_USER_AUTH}" ]; then
23+
echo "CIRCLECI_USER_AUTH not set. Private projects will be inaccessible."
24+
else
25+
fetch "https://circleci.com/api/v2/me" "/tmp/me.cci"
26+
me=$(jq -e '.id' /tmp/me.cci)
27+
echo "Using API key for user: ${me}"
28+
fi
29+
}
30+
31+
fetch(){
32+
debug "Making API Call to ${1}"
33+
url=$1
34+
target=$2
35+
debug "API CALL ${url}"
36+
http_response=$(curl -s -X GET -H "Authorization: Basic ${CIRCLECI_USER_AUTH}" -H "Content-Type: application/json" -o "${target}" -w "%{http_code}" "${url}")
37+
if [ $http_response != "200" ]; then
38+
echo "ERROR: Server returned error code: $http_response"
39+
cat ${target}
40+
# exit 1
41+
else
42+
debug "API Success"
43+
fi
44+
}
45+
46+
fetch_pipelines(){
47+
: ${CIRCLE_BRANCH:?"Required Env Variable not found!"}
48+
echo "Only blocking execution if running previous workflows on branch: ${CIRCLE_BRANCH}"
49+
pipelines_api_url_template="https://circleci.com/api/v2/project/gh/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/pipeline?branch=${CIRCLE_BRANCH}"
50+
51+
debug "DEBUG Attempting to access CircleCI API. If the build process fails after this step, ensure your CIRCLECI_USER_AUTH is set."
52+
fetch "$pipelines_api_url_template" "${pipeline_file}"
53+
echo "DEBUG API access successful"
54+
}
55+
56+
fetch_pipeline_workflows(){
57+
for pipeline in `jq -r ".items[] | .id //empty" ${pipeline_file} | uniq`
58+
do
59+
debug "Checking time of pipeline: ${pipeline}"
60+
pipeline_detail=${tmp}/pipeline-${pipeline}.json
61+
fetch "https://circleci.com/api/v2/pipeline/${pipeline}/workflow" "${pipeline_detail}"
62+
created_at=`jq -r '.items[] | .created_at' ${pipeline_detail}`
63+
debug "Pipeline was created at: ${created_at}"
64+
done
65+
jq -s '[.[].items[]]' ${tmp}/pipeline-*.json > ${workflows_file}
66+
}
67+
68+
load_current_workflow_values(){
69+
my_commit_time=`jq '.[] | select (.pipeline_number == ${CIRCLE_BUILD_NUM}).created_at' ${workflows_file}`
70+
my_workflow_id`jq '.[] | select (.pipeline_number == ${CIRCLE_BUILD_NUM}).id' ${workflows_file}`
71+
}
72+
73+
update_comparables(){
74+
fetch_pipelines
75+
76+
fetch_pipeline_workflows
77+
78+
load_current_workflow_values
79+
80+
echo "This job will block until no previous workflows have *any* workflows running."
81+
oldest_running_workflow_id=`jq '. | sort_by(.created_at) | .[0].id' ${workflows_file}`
82+
oldest_commit_time=`jq '. | sort_by(.created_at) | .[0].created_at' ${workflows_file}`
83+
if [ -z "${oldest_commit_time}" || -z "${oldest_running_workflow_id}" ]; then
84+
echo "ERROR: API Error - unable to load previous workflow timings. File a bug"
85+
exit 1
86+
fi
87+
88+
debug "Oldest workflow: ${oldest_running_workflow_id}"
89+
}
90+
91+
cancel_current_workflow(){
92+
echo "Cancelleing workflow ${my_workflow_id}"
93+
cancel_api_url_template="https://circleci.com/api/v2/workflow/${my_workflow_id}"
94+
curl -s -X POST -H "Authorization: Basic ${CIRCLECI_USER_AUTH}" -H "Content-Type: application/json" $cancel_api_url_template > /dev/null
95+
}
96+
97+
if [ "${CONFIG_ONLY_ON_BRANCH}" = "*" ] || [ "${CONFIG_ONLY_ON_BRANCH}" = "${CIRCLE_BRANCH}" ]; then
98+
echo "${CIRCLE_BRANCH} queueable"
99+
else
100+
echo "Queueing only happens on ${CONFIG_ONLY_ON_BRANCH} branch, skipping queue"
101+
exit 0
102+
fi
103+
104+
### Set values that wont change while we wait
105+
load_variables
106+
max_time=${CONFIG_TIME}
107+
echo "This build will block until all previous builds complete."
108+
echo "Max Queue Time: ${max_time} minutes."
109+
wait_time=0
110+
loop_time=11
111+
max_time_seconds=$((max_time * 60))
112+
113+
### Queue Loop
114+
confidence=0
115+
while true; do
116+
update_comparables
117+
echo "This Workflow Timestamp: $my_commit_time"
118+
echo "Oldest Workflow Timestamp: $oldest_commit_time"
119+
if [[ ! -z "$my_commit_time" ]] && [[ "$oldest_commit_time" > "$my_commit_time" || "$oldest_commit_time" = "$my_commit_time" ]] ; then
120+
# API returns Y-M-D HH:MM (with 24 hour clock) so alphabetical string compare is accurate to timestamp compare as well
121+
# 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.
122+
if [ $confidence -lt ${CONFIG_CONFIDENCE} ];then
123+
# To grow confidence, we check again with a delay.
124+
confidence=$((confidence+1))
125+
echo "API shows no previous workflows, but it is possible a previous workflow has pending jobs not yet visible in API."
126+
echo "Rerunning check ${confidence}/${CONFIG_CONFIDENCE}"
127+
else
128+
echo "Front of the line, WooHoo!, Build continuing"
129+
break
130+
fi
131+
else
132+
# If we fail, reset confidence
133+
confidence=0
134+
echo "This build (${CIRCLE_BUILD_NUM}) is queued, waiting for workflow (${oldest_running_workflow_id}) to complete."
135+
echo "Total Queue time: ${wait_time} seconds."
136+
fi
137+
138+
if [ $wait_time -ge $max_time_seconds ]; then
139+
echo "Max wait time exceeded, considering response."
140+
if [ "${CONFIG_DONT_QUIT}" == "1" ];then
141+
echo "Orb parameter dont-quit is set to true, letting this job proceed!"
142+
exit 0
143+
else
144+
cancel_current_workflow
145+
sleep 10 # wait for API to cancel this job, rather than showing as failure
146+
exit 1 # but just in case, fail job
147+
fi
148+
fi
149+
150+
sleep $loop_time
151+
wait_time=$(( loop_time + wait_time ))
152+
done

0 commit comments

Comments
 (0)