diff --git a/.secrets.baseline b/.secrets.baseline index 9045f87..a30cb89 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2025-11-23T00:19:45Z", + "generated_at": "2025-11-26T21:55:36Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -82,7 +82,7 @@ "hashed_secret": "1de54cf4c9c5e2b8b9aef885aebefb5c1be33332", "is_secret": false, "is_verified": false, - "line_number": 136, + "line_number": 207, "type": "Secret Keyword", "verified_result": null } diff --git a/README.md b/README.md index 10a9885..f02e131 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,77 @@ check_required_bins git jq +
+ install_jq + +Installs jq binary. + +**Environment Variables:** +- `VERBOSE`: If set to true, print verbose output (optional, defaults to false) + +**Arguments:** +- `$1`: version of jq to install (optional, defaults to latest). Example format of valid version is "1.8.1" +- `$2`: location to install jq to (optional, defaults to /usr/local/bin) +- `$3`: if set to true, skips installation if jq is already detected (optional, defaults to true) +- `$4`: the exact url to download jq from (optional, defaults to https://github.com/jqlang/jq/releases/latest/download/jq-${os}-${arch}) + +**Returns:** +- `0` - Success (jq installed successfully) +- `1` - Failure (installation failed) +- `2` - Failure (incorrect usage of function) + +**Usage:** +```bash +install_jq "latest" "/usr/local/bin" "true" +``` + + + +
+
+ is_boolean + +Determine if value is a boolean. + +**Arguments:** +- `$1`: value to check (required) + +**Returns:** +- `0` - Success (value is a boolean - true, True, false or False) +- `1` - Failure (value is not a boolean) +- `2` - Failure (incorrect usage of function) + +**Usage:** +```bash +is_boolean "true" +``` +
+ + + +
+ return_mac_architecture + +Returns the architecture of the MacOS. + +**Arguments:** n/a + +**Returns:** +- `0` - Success + - `"amd64"` - if the OS is macOS and the CPU is Intel + - `"arm64"` - if the OS is macOS and the CPU is Apple Silicon +- `2` - Failure (Did not detect MacOS) + +**Usage:** +```bash +arch=$(return_mac_architecture) +``` +
+ + + + + ## [ibmcloud/iam](ibmcloud/iam.sh)
generate_iam_bearer_token diff --git a/common/common.sh b/common/common.sh index f928236..221cdd3 100755 --- a/common/common.sh +++ b/common/common.sh @@ -174,7 +174,9 @@ check_required_bins() { done if (( ${#missing[@]} )); then - echo "Missing binaries: ${missing[*]}" >&2 + if [ "${verbose}" = true ]; then + echo "Missing binaries: ${missing[*]}" >&2 + fi return ${RETURN_CODE_ERROR} fi @@ -184,11 +186,178 @@ check_required_bins() { return ${RETURN_CODE_SUCCESS} } +#=============================================================== +# FUNCTION: is_boolean +# DESCRIPTION: Determine is value is a boolean. +# +# ARGUMENTS: +# - $1: value to check (required) +# +# RETURNS: +# 0 - Success (value is a boolean - true, True, false or False) +# 1 - Failure (value is not a boolean) +# 2 - Failure (incorrect usage of function) +# +# USAGE: is_boolean +#=============================================================== +is_boolean() { + + # Validate an arg has been provided + if [ $# -ne 1 ]; then + echo "Error: is_boolean requires exactly 1 argument, but $# were provided" >&2 + echo "Usage: is_boolean " >&2 + exit ${RETURN_CODE_ERROR_INCORRECT_USAGE} + fi + + if [[ $1 =~ ^([Tt]rue|[Ff]alse)$ ]]; then + return ${RETURN_CODE_SUCCESS} + else + return ${RETURN_CODE_ERROR} + fi +} + +#=============================================================== +# FUNCTION: return_mac_architecture +# DESCRIPTION: Returns the architecture of the MacOS +# +# RETURNS: +# 0 - Success +# - "amd64" - if the OS is macOS and the CPU is Intel +# - "arm64" - if the OS is macOS and the CPU is Apple Silicon +# 2 - Failure (Did not detect MacOS) +# +# USAGE: return_mac_architecture +#=============================================================== +return_mac_architecture() { + + local arch="arm64" + local cpu + if [[ ${OSTYPE} == 'darwin'* ]]; then + cpu="$(sysctl -a | grep machdep.cpu.brand_string)" + if [[ "${cpu}" == 'machdep.cpu.brand_string: Intel'* ]]; then + # macOS on Intel architecture + arch="amd64" + fi + else + echo "Unsupported OS: ${OSTYPE}" >&2 + return ${RETURN_CODE_ERROR_INCORRECT_USAGE} + fi + echo ${arch} +} + +#=============================================================== +# FUNCTION: install_jq +# DESCRIPTION: Installs jq binary +# +# ENVIRONMENT VARIABLES: +# - VERBOSE: If set to true, print verbose output (optional, defaults to false) +# +# ARGUMENTS: +# - $1: version of jq to install (optional, defaults to latest). Example format of valid version is "1.8.1". +# - $2: location to install jq to (optional, defaults to /usr/local/bin) +# - $3: if set to true, skips installation if jq is already detected (optional, defaults to true) +# - $4: the exact url to download jq from (optional, defaults to https://github.com/jqlang/jq/releases/latest/download/jq-${os}-${arch}) +# +# RETURNS: +# 0 - Success (jq installation successful) +# 1 - Failure (jq installation failed) +# 2 - Failure (incorrect usage of function) +# +# USAGE: install_jq "latest" "/usr/local/bin" "true" +#=============================================================== +install_jq() { + + local version=${1:-"latest"} # default to latest if not specified + local location=${2:-"/usr/local/bin"} + local skip_if_detected=${3:-"true"} + local link_to_binary=${4:-""} + local verbose=${VERBOSE:-false} + + # Validate $3 arg is boolean + if ! is_boolean "${skip_if_detected}"; then + echo "Unsupported value detected for the 3rd argument. Only 'true' or 'false' is supported. Found: ${skip_if_detected}." >&2 + return ${RETURN_CODE_ERROR_INCORRECT_USAGE} + fi + + # return 0 if jq already installed and skip_if_detected is true + if [ "${skip_if_detected}" == "true" ]; then + if check_required_bins jq; then + if [ "${verbose}" = true ]; then + echo "Found jq already installed. Taking no action." + fi + return ${RETURN_CODE_SUCCESS} + fi + fi + # ensure curl is installed + check_required_bins curl || return $? + + # strip "v" prefix if it exists in version + if [[ "${version}" == "v"* ]]; then + version="${version#v}" + fi + + # if no link to binary passed, determine the download link based on detected os and arch + if [ -z "${link_to_binary}" ]; then + # determine the OS and architecture + local os="linux" + local arch="amd64" + if [[ ${OSTYPE} == 'darwin'* ]]; then + arch=$(return_mac_architecture) + fi + # determine download link to binary + link_to_binary="https://github.com/jqlang/jq/releases/download/jq-${version}/jq-${os}-${arch}" + if [ "${version}" = "latest" ]; then + link_to_binary="https://github.com/jqlang/jq/releases/latest/download/jq-${os}-${arch}" + fi + fi + if [ "${verbose}" = true ]; then + echo "Using download link: ${link_to_binary}" + fi + + # use sudo if needed + local arg="" + if ! [ -w "${location}" ]; then + echo "No write permission to ${location}. Using sudo..." + arg=sudo + fi + + # remove if already exists + ${arg} rm -f "${location}/jq" + + # download binary + set +e + if ! ${arg} curl --silent \ + --connect-timeout 5 \ + --max-time 10 \ + --retry 3 \ + --retry-delay 2 \ + --retry-connrefused \ + --fail \ + --show-error \ + --location \ + --output "${location}/jq" \ + "${link_to_binary}" + then + echo "Failed to download ${link_to_binary}" + return ${RETURN_CODE_ERROR} + fi + set -e + + # make executable + ${arg} chmod +x "${location}/jq" + + if [ "${verbose}" = true ]; then + echo "Successfully completed installation to ${location}/jq" + fi + return ${RETURN_CODE_SUCCESS} +} + #=============================================================== # UNIT TESTS #=============================================================== _test() { + local make_api_calls="${MAKE_API_CALLS:-false}" printf "%s\n\n" "Running tests.." # check_env_vars @@ -223,6 +392,55 @@ _test() { assert_fail "${rc}" printf "%s\n\n" "✅ PASS" + # is_boolean + # ----------------------------------- + # - Test valid booleans + for i in "true" "false" "True" "False"; do + printf "%s\n" "Running 'is_boolean $i'" + rc=${RETURN_CODE_SUCCESS} + is_boolean $i >/dev/null 2>&1 || rc=$? + assert_pass "${rc}" + printf "%s\n\n" "✅ PASS" + done + + # - Test non valid boolean + printf "%s\n" "Running 'is_boolean not_a_boolean'" + rc=${RETURN_CODE_SUCCESS} + is_boolean not_a_boolean >/dev/null 2>&1 || rc=$? + assert_fail "${rc}" + printf "%s\n\n" "✅ PASS" + + # install_jq + # ----------------------------------- + if [ "${make_api_calls}" = true ]; then + # Check if jq already exists on $PATH + local jq_installed=true + check_required_bins jq || jq_installed=false + + # - Test installing jq using defaults (when it does not already exist) + if [ ${jq_installed} = false ]; then + printf "%s\n" "Running 'install_jq' (when jq does not already exists)" + rc=${RETURN_CODE_SUCCESS} + install_jq >/dev/null 2>&1 || rc=$? + assert_pass "${rc}" + printf "%s\n\n" "✅ PASS" + fi + + # - Test installing it when jq already exists with default args (should be skipped) + printf "%s\n" "Running 'install_jq' (when jq already exists - install will be skipped)" + rc=${RETURN_CODE_SUCCESS} + install_jq >/dev/null 2>&1 || rc=$? + assert_pass "${rc}" + printf "%s\n\n" "✅ PASS" + + # - Test installing exact version to /tmp even if jq already detected on $PATH + printf "%s\n" "Running 'install_jq 1.8.1 /tmp false'" + rc=${RETURN_CODE_SUCCESS} + install_jq "1.8.1" "/tmp" "false" >/dev/null 2>&1 || rc=$? + assert_pass "${rc}" + printf "%s\n\n" "✅ PASS" + fi + # ----------------------------------- echo "✅ All tests passed!" }