@@ -174,7 +174,9 @@ check_required_bins() {
174174 done
175175
176176 if (( ${# missing[@]} )) ; then
177- echo " Missing binaries: ${missing[*]} " >&2
177+ if [ " ${verbose} " = true ]; then
178+ echo " Missing binaries: ${missing[*]} " >&2
179+ fi
178180 return ${RETURN_CODE_ERROR}
179181 fi
180182
@@ -184,11 +186,178 @@ check_required_bins() {
184186 return ${RETURN_CODE_SUCCESS}
185187}
186188
189+ # ===============================================================
190+ # FUNCTION: is_boolean
191+ # DESCRIPTION: Determine is value is a boolean.
192+ #
193+ # ARGUMENTS:
194+ # - $1: value to check (required)
195+ #
196+ # RETURNS:
197+ # 0 - Success (value is a boolean - true, True, false or False)
198+ # 1 - Failure (value is not a boolean)
199+ # 2 - Failure (incorrect usage of function)
200+ #
201+ # USAGE: is_boolean <value>
202+ # ===============================================================
203+ is_boolean () {
204+
205+ # Validate an arg has been provided
206+ if [ $# -ne 1 ]; then
207+ echo " Error: is_boolean requires exactly 1 argument, but $# were provided" >&2
208+ echo " Usage: is_boolean <value>" >&2
209+ exit ${RETURN_CODE_ERROR_INCORRECT_USAGE}
210+ fi
211+
212+ if [[ $1 =~ ^([Tt]rue| [Ff]alse)$ ]]; then
213+ return ${RETURN_CODE_SUCCESS}
214+ else
215+ return ${RETURN_CODE_ERROR}
216+ fi
217+ }
218+
219+ # ===============================================================
220+ # FUNCTION: return_mac_architecture
221+ # DESCRIPTION: Returns the architecture of the MacOS
222+ #
223+ # RETURNS:
224+ # 0 - Success
225+ # - "amd64" - if the OS is macOS and the CPU is Intel
226+ # - "arm64" - if the OS is macOS and the CPU is Apple Silicon
227+ # 2 - Failure (Did not detect MacOS)
228+ #
229+ # USAGE: return_mac_architecture
230+ # ===============================================================
231+ return_mac_architecture () {
232+
233+ local arch=" arm64"
234+ local cpu
235+ if [[ ${OSTYPE} == ' darwin' * ]]; then
236+ cpu=" $( sysctl -a | grep machdep.cpu.brand_string) "
237+ if [[ " ${cpu} " == ' machdep.cpu.brand_string: Intel' * ]]; then
238+ # macOS on Intel architecture
239+ arch=" amd64"
240+ fi
241+ else
242+ echo " Unsupported OS: ${OSTYPE} " >&2
243+ return ${RETURN_CODE_ERROR_INCORRECT_USAGE}
244+ fi
245+ echo ${arch}
246+ }
247+
248+ # ===============================================================
249+ # FUNCTION: install_jq
250+ # DESCRIPTION: Installs jq binary
251+ #
252+ # ENVIRONMENT VARIABLES:
253+ # - VERBOSE: If set to true, print verbose output (optional, defaults to false)
254+ #
255+ # ARGUMENTS:
256+ # - $1: version of jq to install (optional, defaults to latest). Example format of valid version is "1.8.1".
257+ # - $2: location to install jq to (optional, defaults to /usr/local/bin)
258+ # - $3: if set to true, skips installation if jq is already detected (optional, defaults to true)
259+ # - $4: the exact url to download jq from (optional, defaults to https://github.com/jqlang/jq/releases/latest/download/jq-${os}-${arch})
260+ #
261+ # RETURNS:
262+ # 0 - Success (jq installation successful)
263+ # 1 - Failure (jq installation failed)
264+ # 2 - Failure (incorrect usage of function)
265+ #
266+ # USAGE: install_jq "latest" "/usr/local/bin" "true"
267+ # ===============================================================
268+ install_jq () {
269+
270+ local version=${1:- " latest" } # default to latest if not specified
271+ local location=${2:- " /usr/local/bin" }
272+ local skip_if_detected=${3:- " true" }
273+ local link_to_binary=${4:- " " }
274+ local verbose=${VERBOSE:- false}
275+
276+ # Validate $3 arg is boolean
277+ if ! is_boolean " ${skip_if_detected} " ; then
278+ echo " Unsupported value detected for the 3rd argument. Only 'true' or 'false' is supported. Found: ${skip_if_detected} ." >&2
279+ return ${RETURN_CODE_ERROR_INCORRECT_USAGE}
280+ fi
281+
282+ # return 0 if jq already installed and skip_if_detected is true
283+ if [ " ${skip_if_detected} " == " true" ]; then
284+ if check_required_bins jq; then
285+ if [ " ${verbose} " = true ]; then
286+ echo " Found jq already installed. Taking no action."
287+ fi
288+ return ${RETURN_CODE_SUCCESS}
289+ fi
290+ fi
291+ # ensure curl is installed
292+ check_required_bins curl || return $?
293+
294+ # strip "v" prefix if it exists in version
295+ if [[ " ${version} " == " v" * ]]; then
296+ version=" ${version# v} "
297+ fi
298+
299+ # if no link to binary passed, determine the download link based on detected os and arch
300+ if [ -z " ${link_to_binary} " ]; then
301+ # determine the OS and architecture
302+ local os=" linux"
303+ local arch=" amd64"
304+ if [[ ${OSTYPE} == ' darwin' * ]]; then
305+ arch=$( return_mac_architecture)
306+ fi
307+ # determine download link to binary
308+ link_to_binary=" https://github.com/jqlang/jq/releases/download/jq-${version} /jq-${os} -${arch} "
309+ if [ " ${version} " = " latest" ]; then
310+ link_to_binary=" https://github.com/jqlang/jq/releases/latest/download/jq-${os} -${arch} "
311+ fi
312+ fi
313+ if [ " ${verbose} " = true ]; then
314+ echo " Using download link: ${link_to_binary} "
315+ fi
316+
317+ # use sudo if needed
318+ local arg=" "
319+ if ! [ -w " ${location} " ]; then
320+ echo " No write permission to ${location} . Using sudo..."
321+ arg=sudo
322+ fi
323+
324+ # remove if already exists
325+ ${arg} rm -f " ${location} /jq"
326+
327+ # download binary
328+ set +e
329+ if ! ${arg} curl --silent \
330+ --connect-timeout 5 \
331+ --max-time 10 \
332+ --retry 3 \
333+ --retry-delay 2 \
334+ --retry-connrefused \
335+ --fail \
336+ --show-error \
337+ --location \
338+ --output " ${location} /jq" \
339+ " ${link_to_binary} "
340+ then
341+ echo " Failed to download ${link_to_binary} "
342+ return ${RETURN_CODE_ERROR}
343+ fi
344+ set -e
345+
346+ # make executable
347+ ${arg} chmod +x " ${location} /jq"
348+
349+ if [ " ${verbose} " = true ]; then
350+ echo " Successfully completed installation to ${location} /jq"
351+ fi
352+ return ${RETURN_CODE_SUCCESS}
353+ }
354+
187355# ===============================================================
188356# UNIT TESTS
189357# ===============================================================
190358
191359_test () {
360+ local make_api_calls=" ${MAKE_API_CALLS:- false} "
192361 printf " %s\n\n" " Running tests.."
193362
194363 # check_env_vars
@@ -223,6 +392,55 @@ _test() {
223392 assert_fail " ${rc} "
224393 printf " %s\n\n" " ✅ PASS"
225394
395+ # is_boolean
396+ # -----------------------------------
397+ # - Test valid booleans
398+ for i in " true" " false" " True" " False" ; do
399+ printf " %s\n" " Running 'is_boolean $i '"
400+ rc=${RETURN_CODE_SUCCESS}
401+ is_boolean $i > /dev/null 2>&1 || rc=$?
402+ assert_pass " ${rc} "
403+ printf " %s\n\n" " ✅ PASS"
404+ done
405+
406+ # - Test non valid boolean
407+ printf " %s\n" " Running 'is_boolean not_a_boolean'"
408+ rc=${RETURN_CODE_SUCCESS}
409+ is_boolean not_a_boolean > /dev/null 2>&1 || rc=$?
410+ assert_fail " ${rc} "
411+ printf " %s\n\n" " ✅ PASS"
412+
413+ # install_jq
414+ # -----------------------------------
415+ if [ " ${make_api_calls} " = true ]; then
416+ # Check if jq already exists on $PATH
417+ local jq_installed=true
418+ check_required_bins jq || jq_installed=false
419+
420+ # - Test installing jq using defaults (when it does not already exist)
421+ if [ ${jq_installed} = false ]; then
422+ printf " %s\n" " Running 'install_jq' (when jq does not already exists)"
423+ rc=${RETURN_CODE_SUCCESS}
424+ install_jq > /dev/null 2>&1 || rc=$?
425+ assert_pass " ${rc} "
426+ printf " %s\n\n" " ✅ PASS"
427+ fi
428+
429+ # - Test installing it when jq already exists with default args (should be skipped)
430+ printf " %s\n" " Running 'install_jq' (when jq already exists - install will be skipped)"
431+ rc=${RETURN_CODE_SUCCESS}
432+ install_jq > /dev/null 2>&1 || rc=$?
433+ assert_pass " ${rc} "
434+ printf " %s\n\n" " ✅ PASS"
435+
436+ # - Test installing exact version to /tmp even if jq already detected on $PATH
437+ printf " %s\n" " Running 'install_jq 1.8.1 /tmp false'"
438+ rc=${RETURN_CODE_SUCCESS}
439+ install_jq " 1.8.1" " /tmp" " false" > /dev/null 2>&1 || rc=$?
440+ assert_pass " ${rc} "
441+ printf " %s\n\n" " ✅ PASS"
442+ fi
443+
226444 # -----------------------------------
227445 echo " ✅ All tests passed!"
228446}
0 commit comments