Skip to content

Commit 8f65cfc

Browse files
committed
Add logging and retry_constant
* Conditional logging functions based on BASH_LIB_LOG_LEVEL with appropriate colouring for each level. * retry_constant function, similar to retry but with a retry_constant interval between attempts.
1 parent bc6459f commit 8f65cfc

File tree

8 files changed

+251
-9
lines changed

8 files changed

+251
-9
lines changed

helpers/lib

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ function spopd(){
1919
popd >/dev/null || die "popd failed :("
2020
}
2121

22+
# Test if a variable contains a number
23+
function is_num(){
24+
[[ ${1:-invalid} =~ ^-?[0-9\.]*$ ]]
25+
}
26+
2227
# Retry a command multiple times until it succeeds, with escalating
2328
# delay between attempts.
2429
# Delay is 2 * n + random up to 30s, then 30s + random after that.
2530
# For large numbers of retries the max delay is effectively the retry
26-
# in minutes.
31+
# count in minutes.
2732
# Based on:
2833
# https://gist.github.com/sj26/88e1c6584397bb7c13bd11108a579746
2934
# but now quite heavily modified.
@@ -40,7 +45,7 @@ function retry {
4045
local retries=$1
4146
shift
4247

43-
if ! [[ ${retries} =~ ^[0-9\.]*$ ]]; then
48+
if ! is_num "${retries}"; then
4449
echo "Invalid number of retries: ${retries} for command '${*}'".
4550
exit 1
4651
fi
@@ -72,3 +77,42 @@ function retry {
7277
done
7378
return 0
7479
}
80+
81+
# retry function that waits a constant number of seconds between attempts.
82+
function retry_constant {
83+
if [[ ${#} -lt 3 ]]; then
84+
echo "retry usage: retry <retries> <interval (seconds)> <command>"
85+
exit 1
86+
fi
87+
88+
local retries=$1; shift
89+
local interval=$1; shift
90+
91+
if ! is_num "${retries}"; then
92+
echo "Invalid number of retries: ${retries} for command '${*}'".
93+
exit 1
94+
fi
95+
96+
if ! is_num "${interval}"; then
97+
echo "Invalid interval in seconds: ${retries} for command '${*}'".
98+
exit 1
99+
fi
100+
101+
local count=0
102+
until eval "$@"; do
103+
# Command failed, otherwise until would have skipped the loop
104+
105+
# Store return code so it can be reported to the user
106+
exit=$?
107+
count=$((count + 1))
108+
if [ "${count}" -lt "${retries}" ]; then
109+
echo "'${*}' Retry $count/$retries exited $exit, retrying in $interval seconds..."
110+
sleep "${interval}"
111+
else
112+
# Out of retries :(
113+
echo "Retry $count/$retries exited $exit, no more retries left."
114+
return $exit
115+
fi
116+
done
117+
return 0
118+
}

init

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
## Initialisation Functions for the
44
## Conjurinc Bash Library
55

6-
# Shell Options
6+
if (( ${BASH_VERSINFO[0]} < 4 )); then
7+
echo "Bash Lib requires bash v4 or greater"
8+
echo "Current Bash Version: ${BASH_VERSION}"
9+
exit 1
10+
fi
11+
12+
# Shell Otions
713
set -euo pipefail
814

915
# This script should be sourced before any of

logging/lib

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

3+
echo $BASH_VERSION
34
: "${BASH_LIB_DIR:?BASH_LIB_DIR must be set. Please source bash-lib/init before other scripts from bash-lib.}"
45

6+
export BASH_LIB_LOG_LEVEL=info
7+
declare -A BASH_LIB_LOG_LEVELS=([debug]=1 [info]=2 [warn]=3 [error]=4 [fatal]=5 )
8+
declare -A BASH_LIB_LOG_COLOURS=( [debug]="0;37;40" [info]="0;36;40" [warn]="0;33;40" [error]="1;31;40" [fatal]="1;37;41" )
9+
510
# Add logging functions here
611

712
function announce() {
@@ -10,4 +15,49 @@ function announce() {
1015
echo "$@"
1116
echo " "
1217
echo "++++++++++++++++++++++++++++++++++++++"
13-
}
18+
}
19+
20+
function check_log_level(){
21+
level="${1}"
22+
if [[ ${level} =~ debug|info|warn|error|fatal ]];
23+
then
24+
return 0
25+
else
26+
echo "${level} is not a valid BASH_LIB_LOG_LEVEL, it should be debug|info|warn|error|fatal"
27+
return 1
28+
fi
29+
}
30+
31+
function log {
32+
runtime_log_level="${BASH_LIB_LOG_LEVEL}"
33+
write_log_level="${1}"
34+
msg="${2}"
35+
36+
check_log_level "${runtime_log_level}"
37+
check_log_level "${write_log_level}"
38+
39+
if (( ${BASH_LIB_LOG_LEVELS[${write_log_level}]} >= ${BASH_LIB_LOG_LEVELS[${runtime_log_level}]} )); then
40+
echo -e "\e[${BASH_LIB_LOG_COLOURS[${write_log_level}]}m${msg}\e[0m"
41+
fi
42+
}
43+
44+
function debug(){
45+
log debug "${*}"
46+
}
47+
48+
function info(){
49+
log info "${*}"
50+
}
51+
52+
function warn(){
53+
log warn "${*}"
54+
}
55+
56+
function error(){
57+
log error "${*}"
58+
}
59+
60+
function fatal(){
61+
log fatal "${*}"
62+
}
63+

run-tests

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
# This script is an entry point, so init
44
# is not assumed to have been run

tests-for-this-repo/helpers.bats

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,42 @@ teardown(){
4646
assert_failure
4747
}
4848

49+
@test "is_num fails with no arguments" {
50+
run is_num
51+
assert_output ""
52+
assert_failure
53+
}
54+
55+
@test "is_num fails with alphabetical input" {
56+
run is_num foo
57+
assert_output ""
58+
assert_failure
59+
}
60+
61+
@test "is_num suceeds with integer" {
62+
run is_num foo
63+
assert_output 123
64+
assert_success
65+
}
66+
67+
@test "is_num suceeds with negative integer" {
68+
run is_num foo
69+
assert_output -123
70+
assert_success
71+
}
72+
73+
@test "is_num suceeds with float" {
74+
run is_num foo
75+
assert_output 123.4
76+
assert_success
77+
}
78+
79+
@test "is_num suceeds with negative float" {
80+
run is_num foo
81+
assert_output -123.4
82+
assert_success
83+
}
84+
4985
@test "retry runs command only once if it succeeds the first time" {
5086
retryme(){
5187
date >> ${afile}
@@ -116,3 +152,87 @@ teardown(){
116152
assert_success
117153
assert_equal $(wc -l <${afile}) 1
118154
}
155+
156+
157+
158+
159+
# ***************
160+
161+
162+
@test "retry_constant runs command only once if it succeeds the first time" {
163+
retry_me(){
164+
date >> ${afile}
165+
}
166+
run retry_constant 3 1 retry_me
167+
assert_success
168+
assert_equal $(wc -l <${afile}) 1
169+
}
170+
171+
@test "retry_constant doesn't introduce delay when the command succeeds first time" {
172+
retry_me(){
173+
date >> ${afile}
174+
}
175+
start=$(date +%s)
176+
run retry_constant 3 10 retry_me
177+
end=$(date +%s)
178+
assert [ "$(( start + 1 ))" -ge "${end}" ]
179+
assert_success
180+
}
181+
182+
@test "retry_constant runs n times on consecutive failure and waits between attempts" {
183+
retry_me(){
184+
date >> ${afile}
185+
false
186+
}
187+
start=$(date +%s)
188+
run retry_constant 2 1 retry_me
189+
end=$(date +%s)
190+
# introduces at least a two second delay between attempts
191+
assert [ "$(( start + 2 ))" -le "${end}" ]
192+
assert_failure
193+
assert_equal $(wc -l <${afile}) 2
194+
}
195+
196+
@test "retry_constant returns after first success" {
197+
retry_me(){
198+
date >> "${afile}"
199+
case $(wc -l < ${afile}) in
200+
*1)
201+
return 1
202+
;;
203+
*)
204+
return 0
205+
;;
206+
esac
207+
}
208+
run retry_constant 3 1 retry_me
209+
assert_success
210+
assert_equal $(wc -l <${afile}) 2
211+
}
212+
213+
@test "retry_constant fails with less than three arguments" {
214+
run retry_constant 3 1
215+
assert_failure
216+
assert_output --partial usage
217+
assert [ ! -e "${temp_dir}/appendfile" ]
218+
}
219+
220+
@test "retry_constant fails with non-integer retry count" {
221+
run retry_constant "this" 1 date
222+
assert_failure
223+
assert_output --partial number
224+
assert [ ! -e "${temp_dir}/appendfile" ]
225+
}
226+
227+
@test "retry_constant fails with non-integer interval" {
228+
run retry_constant 2 "this" date
229+
assert_failure
230+
assert_output --partial interval
231+
assert [ ! -e "${temp_dir}/appendfile" ]
232+
}
233+
234+
@test "retry_constant succeeds with compound statements" {
235+
run retry_constant 3 1 "true && date >> ${afile}"
236+
assert_success
237+
assert_equal $(wc -l <${afile}) 1
238+
}

tests-for-this-repo/logging.bats

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,25 @@
77
assert_output --partial "one two one two"
88
assert_success
99
}
10+
11+
@test "check_log_level succeeds with valid level" {
12+
run check_log_level
13+
assert_success
14+
}
15+
16+
@test "check_log_level fails with invalid level" {
17+
run BASH_LIB_LOG_LEVEL="foo" check_log_level
18+
assert_failure
19+
}
20+
21+
@test "debug doesn't output anything using the default info level" {
22+
run debug foo
23+
assert_success
24+
assert_output ""
25+
}
26+
27+
@test "debug outputs its inputs while using the debug level" {
28+
run BASH_LIB_LOG_LEVEL="debug" debug foo
29+
assert_success
30+
assert_output "foo"
31+
}

tests-for-this-repo/run-bats-tests

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
set -euo pipefail
44

tests-for-this-repo/run-python-lint

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
# This script is an entry point, so init
44
# is not assumed to have been run

0 commit comments

Comments
 (0)