-
Notifications
You must be signed in to change notification settings - Fork 6
/
functions
303 lines (242 loc) · 9.48 KB
/
functions
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#!/bin/bash
###################
# Basic functions #
###################
function die() {
local rc=$1
shift
local msg=$@
local death_type=OK
if [[ $rc -ne 0 ]] ; then
local death_type="ERROR"
fi
echo -e "[$PROG][$death_type]: ${msg}" >&2
exit $rc
}
function say() {
local header=$1
shift
local msg=$@
echo -e "[$PROG][$header]: $msg"
}
function cmd_exists() {
local cmd=$1
command -v $cmd &> /dev/null
}
function file_is_empty() {
local _file=$1
! test -s $_file
}
function is_int() {
local int=$1
[[ $int =~ ^[[:digit:]]+$ ]]
}
function verify_option_was_provided() {
local varname=$1
local option_name=$2
check_variable_or_die $varname "option $option_name is required"
}
function check_required_binaries() {
local required_binaries=$@
local missing_binaries=""
for cmd in $required_binaries ; do
if ! cmd_exists $cmd ; then
missing_binaries+="$cmd "
fi
done
if [[ -n $missing_binaries ]] ; then
printf "Command not found: %s\n" $missing_binaries
return 1
fi
return 0
}
function check_variable_or_die() {
local varname=$1
local error_msg=${2:-"variable $varname is undefined"}
if [[ -z ${!varname} ]] ; then
die 1 "$error_msg"
fi
return 0
}
function convert_seconds_to_time() {
local time_in_seconds=$1
local hours=$((time_in_seconds / 3600 ))
local mins=$(((time_in_seconds % 3600) / 60))
local secs=$((time_in_seconds % 60))
test $hours -gt 0 || local hours=00
test $mins -gt 0 || local mins=00
[[ $mins =~ ^[0-9]{1}$ ]] && local mins=0${mins}
[[ $secs =~ ^[0-9]{1}$ ]] && local secs=0${secs}
echo "${hours}h:${mins}m:${secs}s"
}
#################################
# workflow-control.sh functions #
#################################
function generate_uuid() {
openssl rand -hex 12
}
function curl_github_api() {
local url=$1
shift
local curl_opts=$@
curl --silent --request GET \
-H "Authorization: Basic $AUTH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
$url $curl_opts
}
function trigger_workflow() {
local github_url=$1
local workflow_branch=$2
shift 2
local workflow_inputs=$@
local curl_rc=$( \
curl --silent \
--request POST \
--output /dev/stderr \
--write-out "%{http_code}" \
-H "Authorization: Basic $AUTH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
$github_url --data "$workflow_inputs")
say DEBUG "POST request JSON data: $workflow_inputs"
if test $curl_rc -eq 204 ; then
say INFO "Successfully triggered: $github_url"
return 0
fi
die 1 "failed to trigger: $github_url, curl exit code: $curl_rc"
}
function time_in_epoch() {
date +%s
}
function conclude_death() {
local job_exit_conclusion=$1
local job_exit_status=$2
if [[ $job_exit_conclusion == "success" ]] ; then
die 0 "action conclusion: $job_exit_conclusion | status: $job_exit_status"
fi
die 1 "action conclusion: $job_exit_conclusion | status: $job_exit_status"
}
function wait_and_report_progress() {
local time_remaining=$1
# Don't clog the screen with endless messages, only report of status every once in a while;
if [[ $(( time_remaining % 2)) -eq 0 ]] ; then
echo "Still waiting.. timeout countdown: [$(convert_seconds_to_time $time_remaining)]"
fi
sleep 6
}
function date_iso8601() {
literal_date_expression=${1:-'5 minutes ago'}
date --iso-8601=minutes --utc --date="$literal_date_expression"
}
function get_jobs_urls() {
local url=$1
curl_github_api $url | jq -r -M '.workflow_runs[].jobs_url' 2> /dev/null
}
function parse_json_data() {
local data_type=$1
local json_data=$2
local param=$3
case $data_type in
"names_of_steps") jq '.steps[].name' $json_data ;;
"job_conclusion") jq -r -M '.conclusion' $json_data ;;
"job_status") jq -r -M '.status' $json_data ;;
"step_status") jq '.steps[] | select(.name == '"$param"') | .status' $json_data ;;
"step_conclusion") jq '.steps[] | select(.name == '"$param"') | .conclusion' $json_data ;;
esac
}
function calc_remaining_time() {
local start_time=$1
local wait_timeout_in_seconds=$2
local now_time=$(time_in_epoch)
local elapsed_seconds=$(( now_time - start_time ))
echo $(( wait_timeout_in_seconds - elapsed_seconds ))
}
function wait_for_status() {
local artifact_uuid=$1
local workflow_base_url=$2
local wait_timeout_in_minutes=$3
local wait_timeout_in_seconds=$((wait_timeout_in_minutes * 60))
if [[ $wait_timeout_in_minutes -eq 0 ]] ; then
die 0 "Not waiting for triggered job to finish since 'wait-timeout-in-minutes' is set to $wait_timeout_in_minutes, artifact UUID: $artifact_uuid"
fi
local start_time=$(time_in_epoch)
local time_remaining=$(( wait_timeout_in_seconds - elapsed_seconds ))
if [[ $time_remaining -le 0 ]] ; then
die 1 "timeout exceeded while waiting for artifact after $wait_timeout_in_minutes minutes"
fi
until [[ -n $job_id ]]; do
# Loop over all URLs and check for Job ID we triggered earlier;
local all_job_urls=$(get_jobs_urls "$workflow_base_url/runs?created=>$(date_iso8601)")
for url in $all_job_urls ; do
local job_id=$(curl_github_api $url | jq '.jobs[] | select(.steps[].name == "'"$artifact_uuid"'") | .id' 2> /dev/null)
if [[ -n $job_id ]] ; then
break
fi
done
done
say INFO "Found job ID of triggered workflow: $job_id, will be used to query for job status"
say INFO "Starting countdown of $wait_timeout_in_minutes minute(s) while waiting for job \"$job_id\" to finish"
local start_time=$(time_in_epoch)
local time_remaining=$wait_timeout_in_seconds
local job_datafile_json=$(mktemp)
while [[ -z $job_conclusion || $job_conclusion == "null" ]] ; do
curl_github_api "$workflow_base_url/jobs/$job_id" > $job_datafile_json
local job_conclusion=$(parse_json_data "job_conclusion" $job_datafile_json)
if [[ -n $job_conclusion && $job_conclusion != "null" ]] ; then
local job_status=$(parse_json_data "job_status" $job_datafile_json)
# Exit according to conclusion;
conclude_death $job_conclusion $job_status
fi
local all_steps="$(parse_json_data "names_of_steps" $job_datafile_json)"
local steps_tmpfile=$(mktemp)
say INFO "Gathering job's progress .."
echo "Step, Status, Conclusion" > $steps_tmpfile
# Seperator must be a newline char since $all_steps
# is a list of strings with spaces;
oIFS=$IFS
IFS=$'\n'
for step in $all_steps ; do
# No need to print the step we named after
# the unique ID since it's for internal tracking
# purposes only;
if [[ $step == \"$artifact_uuid\" ]] ; then
continue
fi
step_status=$(parse_json_data "step_status" $job_datafile_json "$step")
step_conclusion=$(parse_json_data "step_conclusion" $job_datafile_json "$step")
if [[ $step_conclusion == "null" ]] ; then
# Do not display null values in table;
step_conclusion=""
fi
echo "$step", "$step_status", "$step_conclusion" | tee -a $steps_tmpfile 1> /dev/null
done
IFS=$oIFS
printTable , "$(cat $steps_tmpfile)"
rm -f $steps_tmpfile
wait_and_report_progress $(calc_remaining_time $start_time $wait_timeout_in_seconds)
done
}
function usage() {
/bin/echo "
Description: $PROG is used to trigger a remote Github workflow from within a workflow and monitor its status for success/failure;
Usage: ${PROG} OPTIONS
Mandatory Options:
-o, --workflow-org <str> the workflow's organization/user-name on Github;
-r, --workflow-repo <str> the workflow's repository name on Github;
-y, --workflow-yaml <*.(yaml|yml)> the workflows's actual yaml file that you wish to trigger;
-b, --workflow-branch <branch> when triggering a workflow, this option is mandatory as it instructs
Github to run the workflow from the specified branch;
-a, --auth-token <token> provide a Github authentication token in the format of username:token;
$PROG would 'base64' it and pass it on in the request header;
Optional:
-i, --workflow-inputs <json> if the triggered workflow accepts inputs, you can specify them via this option;
NOTE: this option accepts JSON type input only where double quotes are escaped!
see examples in the Github repo link below;
-w, --wait-timeout-minutes <int> how long should $PROG wait when checking the status of a job before it times out;
if unspecified, defaults to $DEFAULT_WAIT_TIMEOUT_MINUTES minutes;
NOTE: if '0' is specified, $PROG will NOT wait for the triggered job to finish,
but would rather trigger it and die accordingly;
Examples:
# For detailed, practical examples, please refer to:
https://github.com/anecdotes-ai/remote-workflow-control
" >&2
}