-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathgitrise.sh
executable file
·441 lines (400 loc) · 14.7 KB
/
gitrise.sh
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
#!/usr/bin/env bash
# shellcheck disable=SC2155
# disbales "Declare and assign separately to avoid masking return values."
# shellcheck disable=SC2120
# disables "foo references arguments, but none are ever passed."
VERSION="0.10.0"
APP_NAME="Gitrise"
STATUS_POLLING_INTERVAL=30
build_slug=""
build_url=""
build_status=0
current_build_status_text=""
exit_code=0
log_url=""
build_artifacts_slugs=()
function usage() {
echo ""
echo "Usage: gitrise.sh [-d] [-e] [-h] [-T] [-v] -a token -s project_slug -w workflow [-b branch|-t tag|-c commit]"
echo
echo " -a, --access-token <string> Bitrise access token"
echo " -b, --branch <string> Git branch"
echo " -c, --commit <string> Git commit hash "
echo " -d, --debug Debug mode enabled"
echo " --download-artifacts <string> List of build artifact names to download in the form of name1,name2"
echo " -e, --env <string> List of environment variables in the form of key1:value1,key2:value2"
echo " -h, --help Print this help text"
echo " -p, --poll <string> Polling interval (in seconds) to get the build status."
echo " --stream Stream build logs"
echo " -s, --slug <string> Bitrise project slug"
echo " -T, --test Test mode enabled"
echo " -t, --tag <string> Git tag"
echo " -v, --version App version"
echo " -w, --workflow <string> Bitrise workflow"
echo
}
# parsing space separated options
while [ $# -gt 0 ]; do
key="$1"
case $key in
-v|--version)
echo "$APP_NAME version $VERSION"
exit 0
;;
-w|--workflow)
WORKFLOW="$2"
shift;shift
;;
-c|--commit)
COMMIT="$2"
shift;shift
;;
-t|--tag)
TAG="$2"
shift;shift
;;
-b|--branch)
BRANCH="$2"
shift;shift
;;
-a|--access-token)
ACCESS_TOKEN="$2"
shift;shift
;;
-s|--slug)
PROJECT_SLUG="$2"
shift;shift
;;
-e|--env)
ENV_STRING="$2"
shift;shift
;;
-h|--help)
usage
exit 0
;;
-T|--test)
TESTING_ENABLED="true"
shift
;;
-d|--debug)
DEBUG="true"
shift
;;
--stream)
STREAM="true"
shift
;;
-p|--poll)
STATUS_POLLING_INTERVAL="$2"
shift;shift
;;
--download-artifacts)
BUILD_ARTIFACTS="$2"
shift;shift
;;
*)
echo "Invalid option '$1'"
usage
exit 1
;;
esac
done
# Create temp directory if debugging mode enabled
if [ "$DEBUG" == "true" ]; then
[ -d gitrise_temp ] && rm -r gitrise_temp
mkdir -p gitrise_temp
fi
# Create build_artifacts directory when downloading build artifacts
if [ -n "$BUILD_ARTIFACTS" ]; then
[ -d build_artifacts ] && rm -r build_artifacts
mkdir -p build_artifacts
fi
function validate_input() {
if [ -z "$WORKFLOW" ] || [ -z "$ACCESS_TOKEN" ] || [ -z "$PROJECT_SLUG" ]; then
printf "\e[31m ERROR: Missing arguments(s). All these args must be passed: --workflow,--slug,--access-token \e[0m\n"
usage
exit 1
fi
local count=0
[[ -n "$TAG" ]] && ((count++))
[[ -n "$COMMIT" ]] && ((count++))
[[ -n "$BRANCH" ]] && ((count++))
if [[ $count -gt 1 ]]; then
printf "\n\e[33m Warning: Too many building arguments passed. Only one of these is needed: --commit, --tag, --branch \e[0m\n"
elif [[ $count == 0 ]]; then
printf "\e[31m ERROR: Missing build argument. Pass one of these: --commit, --tag, --branch\e[0m\n"
usage
exit 1
fi
if [[ $STATUS_POLLING_INTERVAL -lt 10 ]]; then
printf "\e[31m ERROR: polling interval is too short. The minimum acceptable value is 10, but received %s.\e[0m\n" "$STATUS_POLLING_INTERVAL"
exit 1
fi
}
# map environment variables to objects Bitrise will accept.
# ENV_STRING is passed as argument
function process_env_vars() {
local env_string=""
local result=""
input_length=$(grep -c . <<< "$1")
if [[ $input_length -gt 1 ]]; then
while read -r line
do
env_string+=$line
done <<< "$1"
else
env_string="$1"
fi
IFS=',' read -r -a env_array <<< "$env_string"
for i in "${env_array[@]}"
do
# shellcheck disable=SC2162
# disables "read without -r will mangle backslashes"
IFS=':' read -a array_from_pair <<< "$i"
key="${array_from_pair[0]}"
value="${array_from_pair[1]}"
# shellcheck disable=SC2089
# disables "Quotes/backslashes will be treated literally. Use an array."
result+="{\"mapped_to\":\"$key\",\"value\":\"$value\",\"is_expand\":true},"
done
echo "[${result/%,}]"
}
function generate_build_payload() {
local environments=$(process_env_vars "$ENV_STRING")
cat << EOF
{
"build_params": {
"branch": "$BRANCH",
"commit_hash": "$COMMIT",
"tag": "$TAG",
"workflow_id" : "$WORKFLOW",
"environments": $environments
},
"hook_info": {
"type": "bitrise"
}
}
EOF
}
function trigger_build() {
local response=""
if [ -z "${TESTING_ENABLED}" ]; then
local command="curl --silent -X POST https://api.bitrise.io/v0.1/apps/$PROJECT_SLUG/builds \
--data '$(generate_build_payload)' \
--header 'Accept: application/json' --header 'Authorization: $ACCESS_TOKEN'"
response=$(eval "${command}")
else
response=$(<./testdata/"$1"_build_trigger_response.json)
fi
[ "$DEBUG" == "true" ] && log "${command%'--data'*}" "$response" "trigger_build.log"
status=$(echo "$response" | jq ".status" | sed 's/"//g' )
if [ "$status" != "ok" ]; then
msg=$(echo "$response" | jq ".message" | sed 's/"//g')
printf "%s" "ERROR: $msg"
exit 1
else
build_url=$(echo "$response" | jq ".build_url" | sed 's/"//g')
build_slug=$(echo "$response" | jq ".build_slug" | sed 's/"//g')
fi
printf "\nHold on... We're about to liftoff! 🚀\n \nBuild URL: %s\n" "${build_url}"
}
function process_build() {
local status_counter=0
local current_log_chunks_positions=()
while [ "${build_status}" = 0 ]; do
# Parameter is a test json file name and is only passed for testing.
check_build_status "$1"
if [[ "$STREAM" == "true" ]] && [[ "$current_build_status_text" != "on-hold" ]]; then stream_logs; fi
if [[ $TESTING_ENABLED == true ]] && [[ "${FUNCNAME[1]}" != "testFailureUponReceivingHTMLREsponse" ]]; then break; fi
sleep "$STATUS_POLLING_INTERVAL"
done
if [ "$build_status" != 1 ]; then exit_code=$(( exit_code + 1 )); fi
}
function check_build_status() {
local response=""
local retry=3
if [ -z "${TESTING_ENABLED}" ]; then
local command="curl --silent -X GET -w \"status_code:%{http_code}\" https://api.bitrise.io/v0.1/apps/$PROJECT_SLUG/builds/$build_slug \
--header 'Accept: application/json' --header 'Authorization: $ACCESS_TOKEN'"
response=$(eval "${command}")
else
response=$(< ./testdata/"$1")
fi
[ "$DEBUG" == "true" ] && log "${command%%'--header'*}" "$response" "get_build_status.log"
if [[ "$response" != *"<!DOCTYPE html>"* ]]; then
handle_status_response "${response%'status_code'*}"
else
if [[ $status_counter -lt $retry ]]; then
build_status=0
((status_counter++))
else
echo "ERROR: Invalid response received from Bitrise API"
build_status="null"
fi
fi
}
function handle_status_response() {
local response="$1"
local build_status_text=$(echo "$response" | jq ".data .status_text" | sed 's/"//g')
if [ "$build_status_text" != "$current_build_status_text" ]; then
echo "Build $build_status_text"
current_build_status_text="${build_status_text}"
fi
build_status=$(echo "$response" | jq ".data .status")
}
function stream_logs() {
local response=""
local log_chunks_positions=()
if [ -z "${TESTING_ENABLED}" ] ; then
local command="curl --silent -X GET https://api.bitrise.io/v0.1/apps/$PROJECT_SLUG/builds/$build_slug/log \
--header 'Accept: application/json' --header 'Authorization: $ACCESS_TOKEN'"
response=$(eval "$command")
else
response="$(< ./testdata/"$1"_log_info_response.json)"
fi
[ "$DEBUG" == "true" ] && log "${command%'--header'*}" "$response" "get_log_info.log"
# Every chunk has an accompanying position. Storing the chunks' positions to track the chunks.
while IFS='' read -r line; do log_chunks_positions+=("$line"); done < <(echo "$response" | jq ".log_chunks[].position")
new_log_chunck_positions=()
for i in "${log_chunks_positions[@]}"; do
skip=
for j in "${current_log_chunks_positions[@]}"; do
[[ $i == "$j" ]] && { skip=1; break; }
done
[[ -z $skip ]] && new_log_chunck_positions+=("$i")
done
if [[ ${#new_log_chunck_positions[@]} != 0 ]]; then
for i in "${new_log_chunck_positions[@]}"; do
parsed_chunk=$(echo "$response" | jq --arg index "$i" '.log_chunks[] | select(.position == ($index | tonumber)) | .chunk')
cleaned_chunk=$(echo "${parsed_chunk}" | sed -e 's/^"//' -e 's/"$//')
printf "%b" "$cleaned_chunk"
done
else
return
fi
current_log_chunks_positions=("${log_chunks_positions[@]}")
}
function get_build_logs() {
local log_is_archived=false
local counter=0
local retry=4
local polling_interval=15
local response=""
while ! "$log_is_archived" && [[ "$counter" -lt "$retry" ]]; do
if [ -z "${TESTING_ENABLED}" ] ; then
sleep "$polling_interval"
local command="curl --silent -X GET https://api.bitrise.io/v0.1/apps/$PROJECT_SLUG/builds/$build_slug/log \
--header 'Accept: application/json' --header 'Authorization: $ACCESS_TOKEN'"
response=$(eval "$command")
else
response="$(< ./testdata/"$1"_log_info_response.json)"
fi
[ "$DEBUG" == "true" ] && log "${command%'--header'*}" "$response" "get_log_info.log"
log_is_archived=$(echo "$response" | jq ".is_archived")
((counter++))
done
log_url=$(echo "$response" | jq ".expiring_raw_log_url" | sed 's/"//g')
if ! "$log_is_archived" || [ -z "$log_url" ]; then
echo "LOGS WERE NOT AVAILABLE - navigate to $build_url to see the logs."
exit ${exit_code}
else
print_logs "$log_url"
fi
}
function print_logs() {
local url="$1"
local logs=$(curl --silent -X GET "$url")
echo "================================================================================"
echo "============================== Bitrise Logs Start =============================="
echo "$logs"
echo "================================================================================"
echo "============================== Bitrise Logs End =============================="
}
function build_status_message() {
local status="$1"
case "$status" in
"0")
echo "Build TIMED OUT based on mobile trigger internal setting"
;;
"1")
echo "Build Successful 🎉"
;;
"2")
echo "Build Failed 🚨"
;;
"3")
echo "Build Aborted 💥"
;;
*)
echo "Invalid build status 🤔"
exit 1
;;
esac
}
function get_build_artifacts() {
local build_artifacts_names=()
local artifact_slug=""
local response=""
if [ -z "${TESTING_ENABLED}" ]; then
local command="curl --silent -X GET https://api.bitrise.io/v0.1/apps/$PROJECT_SLUG/builds/$build_slug/artifacts \
--header 'Accept: application/json' --header 'Authorization: $ACCESS_TOKEN'"
response=$(eval "${command}")
else
response=$(<./testdata/build_artifacts_response.json)
fi
[ "$DEBUG" == "true" ] && log "${command%%'--header'*}" "$response" "get_all_artifacts.log"
IFS=',' read -r -a build_artifacts_names <<< "$BUILD_ARTIFACTS"
for name in "${build_artifacts_names[@]}"
do
artifact_slug=$(echo "$response" | jq --arg artifact_name "$name" '.data[] | select(.title | contains($artifact_name)) | .slug' | sed 's/"//g')
[ -n "$artifact_slug" ] && build_artifacts_slugs+=("${artifact_slug}")
done
if [[ ${#build_artifacts_slugs[@]} == 0 ]]; then
printf "%b" "\e[31m ERROR: Invalid download artifacts arguments(s). Make sure artifact names are correct and are passed in the format of --download-artifacts name1,name2 \e[0m\n"
exit 1
fi
}
function download_single_artifact() {
local artifact_slug="$1"
local response=""
if [ -z "${TESTING_ENABLED}" ]; then
local command="curl --silent -X GET https://api.bitrise.io/v0.1/apps/$PROJECT_SLUG/builds/$build_slug/artifacts/$artifact_slug \
--header 'Accept: application/json' --header 'Authorization: $ACCESS_TOKEN'"
response=$(eval "${command}")
else
response=$(<./testdata/single_artifact_response.json)
fi
[ "$DEBUG" == "true" ] && log "${command%%'--header'*}" "$response" "get_single_artifact.log"
artifact_url=$(echo "$response" | jq ".data.expiring_download_url" | sed 's/"//g')
artifact_title=$(echo "$response" | jq ".data.title" | sed 's/"//g')
printf "%b" "Downloading build artifact $artifact_title\n"
curl -X GET "$artifact_url" --output "./build_artifacts/$artifact_title"
exit_code=$(( exit_code + $? ))
}
function download_build_artifacts() {
get_build_artifacts
for slug in "${build_artifacts_slugs[@]}"
do
download_single_artifact "$slug"
done
}
function log() {
local request="$1"
local response="$2"
local log_file="$3"
secured_request=${request/\/'apps'\/*\//\/'apps'\/'[REDACTED]'\/}
printf "%b" "\n[$(TZ="EST6EDT" date +'%T')] REQUEST: ${secured_request}\n[$(TZ="EST6EDT" date +'%T')] RESPONSE: $response\n" >> ./gitrise_temp/"$log_file"
}
# No function execution when the script is sourced
# shellcheck disable=SC2119
# disables "use foo "$@" if function's $1 should mean script's $1."
if [ "$0" = "${BASH_SOURCE[0]}" ] && [ -z "${TESTING_ENABLED}" ]; then
validate_input
trigger_build
process_build
[ -z "$STREAM" ] && get_build_logs
build_status_message "$build_status"
[ -n "$BUILD_ARTIFACTS" ] && download_build_artifacts
exit ${exit_code}
fi