diff --git a/_scripts/colors.sh b/_scripts/colors.sh new file mode 100644 index 0000000..6c1c381 --- /dev/null +++ b/_scripts/colors.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +function P () { + tput "$@" 2>/dev/null +} + +_normal_="$(P sgr0)" + +_bold_="$(P bold)" +_uline_="$(P smul)" + +COLORS_ARRAY=( black red green yellow blue magenta cyan white unused default ) + +for i in "${!COLORS_ARRAY[@]}"; do + eval "_fg_${COLORS_ARRAY[$i]}_=\"$(P setaf "$i")\"" + eval "_bg_${COLORS_ARRAY[$i]}_=\"$(P setab "$i")\"" +done diff --git a/_scripts/generate-and-verify.sh b/_scripts/generate-and-verify.sh index 0e25f49..011c886 100755 --- a/_scripts/generate-and-verify.sh +++ b/_scripts/generate-and-verify.sh @@ -1,13 +1,18 @@ #!/usr/bin/env bash +# shellcheck disable=SC2154 source=/dev/null + set -Eumo pipefail +SELF_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +source "$SELF_DIR/colors.sh" + SOURCE_FILE="$1" TARGET_FILE="$2" ENV_VARS="$(cut -d= -f1 .env | awk '{print "$" $0}')" -echo -n "[~] Generating '$TARGET_FILE': " +echo -n "[*] Generating '$TARGET_FILE': " if [ -e "$TARGET_FILE" ] && [ ! -f "$TARGET_FILE" ] ; then echo "CANNOT OVERWRITE!" @@ -17,8 +22,8 @@ fi env -i - $(paste .env) \ envsubst "$ENV_VARS"\ < "$SOURCE_FILE" \ - > "$TARGET_FILE" -[ $? -eq 0 ] || exit 1 + > "$TARGET_FILE" \ + || exit 1 SRC_VARS=$(envsubst -v "$(cat "$SOURCE_FILE")" | paste -sd '|') @@ -30,4 +35,4 @@ if grep -qE "$SRC_VARS" "$TARGET_FILE" ; then exit 1 fi -echo VERIFIED +echo "${_fg_green_}VERIFIED${_normal_}" diff --git a/_scripts/test_comp.sh b/_scripts/test_comp.sh index 2769609..b653387 100755 --- a/_scripts/test_comp.sh +++ b/_scripts/test_comp.sh @@ -1,88 +1,91 @@ #!/usr/bin/env bash +#shellcheck disable=SC2086,SC2154 source=/dev/null set -Eumo pipefail SELF_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" cd "$SELF_DIR/.." -TEST_COMP_TMP_PATH=/tmp/__test_comp_intermediate_storage_dir__ - -rm -rf "$TEST_COMP_TMP_PATH" -mkdir -p "$TEST_COMP_TMP_PATH" +DEBUG="${DEBUG:-}" +source _scripts/colors.sh source <( grep -P "^EXIT_CODE_[A-Z_]+=.+" ./comp ) -# __test_comp -function __test_comp () { - local DEBUG="${6:-}" +FINAL_EXIT_CODE=0 + +TEST_COMP_TMP_PATH=/tmp/__test_comp_intermediate_storage_dir__ +rm -rf "$TEST_COMP_TMP_PATH" +mkdir -p "$TEST_COMP_TMP_PATH" - echo "[>] Test '$1': " +# SETUP +function SETUP () { + echo -e "\n${_fg_white_}${_bg_black_}${_bold_}[#]${_normal_} Test $1: " local io_path_prefix="$TEST_COMP_TMP_PATH/$1" - local err_path="$io_path_prefix.err" - local out_path="$io_path_prefix.out" + COMP_ERR_PATH="$io_path_prefix.err" + COMP_OUT_PATH="$io_path_prefix.out" if [ -n "$DEBUG" ]; then - echo "cmd: ./comp $2" - echo "in: $3" + echo "COMMAND: ./comp $2" + echo "INPUT: $3" fi - echo "$3" | ./comp $2 >"$out_path" 2>"$err_path" - local exit_code=$? + + echo "$3" | ./comp $2 >"$COMP_OUT_PATH" 2>"$COMP_ERR_PATH" + COMP_EXIT_CODE=$? if [ -n "$DEBUG" ]; then - echo "\$err_path contents:" - cat "$err_path" - echo "\$out_path contents:" - cat "$out_path" - echo "exit_code: $exit_code" - echo -e "condition:\n $4" + echo "\$COMP_ERR_PATH contents:" + cat "$COMP_ERR_PATH" + echo "\$COMP_OUT_PATH contents:" + cat "$COMP_OUT_PATH" + echo "COMP_EXIT_CODE: $COMP_EXIT_CODE" fi +} + +function CHECK { + [ -z "$DEBUG" ] || echo "CHECK: $1" - eval -- $4 \ - && echo "[+] SUCCESS!" \ - || echo "[-] FAILURE [$5]" - echo + if eval -- $1 ; then + echo " ${_fg_green_}+${_normal_} $2: ${_fg_green_}SUCCESS${_normal_}" + else + echo " ${_fg_red_}${_bold_}-${_normal_} $2: ${_fg_red_}${_bold_}FAILURE${_normal_}" + FINAL_EXIT_CODE=1 + fi } -DEBUG_ALL="${DEBUG:-}" - -__test_comp \ - "Usage on invocation without args" \ - "" \ - "" \ - $'[ $exit_code -eq $EXIT_CODE_USAGE_ERROR ]' \ - "Expected EXIT_CODE_USAGE_ERROR" \ - "$DEBUG_ALL" - -__test_comp \ - "Error on status of non-existent composition" \ - "status unknown_comp" \ - "" \ - $'[ $exit_code -eq $EXIT_CODE_COMPOSITION_NOT_FOUND ] - && - grep -sq "is not a base directory" "$err_path"' \ - "Expected EXIT_CODE_COMPOSITION_NOT_FOUND and helpful error message" \ - "$DEBUG_ALL" - -__test_comp \ - "Confirmation before deleting data (deny)" \ - "clean tang" \ - "n" \ - $'[ $exit_code -eq 0 ] - && - grep -sq "Remove \'tang/data\' (y/N)?" "$out_path" - && - grep -vsq "removed \'.env\'" "$out_path"' \ - "Expected EXIT_CODE = 0, confirmation prompt, and data preserved" \ - "$DEBUG_ALL" - -__test_comp \ - "Confirmation before deleting data (allow)" \ - "clean tang" \ - "y" \ - $'[ $exit_code -eq 0 ] - && - grep -sq "Remove \'tang/data\' (y/N)?" "$out_path" - && - grep -sq "removed \'.env\'" "$out_path"' \ - "Expected EXIT_CODE = 0, confirmation prompt, and data deleted" \ - "$DEBUG_ALL" +./comp status tang &> /dev/null + +SETUP "invocation without args" \ + "" \ + "" +CHECK $'[ $COMP_EXIT_CODE -eq $EXIT_CODE_USAGE_ERROR ]' \ + "Expected EXIT_CODE_USAGE_ERROR" + +SETUP "status of non-existent composition" \ + "status unknown_comp" \ + "" +CHECK $'[ $COMP_EXIT_CODE -eq $EXIT_CODE_COMPOSITION_NOT_FOUND ]'\ + "Expected EXIT_CODE_COMPOSITION_NOT_FOUND" +CHECK $'grep -sq "is not a base directory" "$COMP_ERR_PATH"' \ + "Expected a helpful error message" + +SETUP "deny deleting data" \ + "clean tang" \ + "n" +CHECK $'[ $COMP_EXIT_CODE -eq 0 ]' \ + "Expected EXIT_CODE = 0" +CHECK $'grep -sq "Remove \'tang/data\' (y/N)?" "$COMP_OUT_PATH"' \ + "Expected confirmation prompt before deletion" +CHECK $'[ -f tang/.env ]' \ + "Expected data is not deleted" + +SETUP "allow deleting data" \ + "clean tang" \ + "y" +CHECK $'[ $COMP_EXIT_CODE -eq 0 ]' \ + "Expected EXIT_CODE = 0" +CHECK $'grep -sq "Remove \'tang/data\' (y/N)?" "$COMP_OUT_PATH"' \ + "Expected confirmation prompt before deletion" +CHECK $'! [ -f tang/.env ]' \ + "Expected data is deleted" + +exit "$FINAL_EXIT_CODE" diff --git a/comp b/comp index 6b75f41..ae101c7 100755 --- a/comp +++ b/comp @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034,SC2317 source=/dev/null +# shellcheck disable=SC2016,SC2034,SC2068,SC2154,SC2317 source=/dev/null set -Eumo pipefail @@ -40,6 +40,8 @@ OPTION_PORTS="" : "${COMP_INTERNAL_CALL:=}" +source "$SCRIPTS_DIR/colors.sh" + # # # # # # # # # # # # # # # # # # # HELPER ROUTINES # # # # # # # # # # # # # # # # # # # __append_env_from () { @@ -80,7 +82,7 @@ __do_prereqs () { local verb="$1" local ensure_running="${2:-no}" - while IFS= read -rd '' prereq_comp_dir ; do + while IFS= read -r prereq_comp_dir ; do if [ "$verb" = "status" ] ; then if ! __CALL_SELF__ status -F "$prereq_comp_dir" ; then [ "$ensure_running" = "yes" ] || return 1 @@ -89,11 +91,11 @@ __do_prereqs () { else __CALL_SELF__ "$verb" -F "$prereq_comp_dir" || return 1 fi - done < <(cat "$PREREQS_FILENAME" "$PREREQS_OVERRIDE_FILENAME") + done < <(cat "$PREREQS_FILENAME" "$PREREQS_OVERRIDE_FILENAME" 2>/dev/null) } __error () { - echo "[X] ERROR: $1" >&2 + echo "${_fg_red_}${_bold_}[X] ERROR${_normal_}: $1" >&2 } __gen_env () { @@ -123,18 +125,17 @@ __gen_env () { __gen_templates () { find ./extra -name '*.template.*' 2>/dev/null | grep -q . || return 0 - echo "[*] Verifying templated files ..." - - find ./extra -name '*.template.*' -print0 | \ - while IFS= read -rd '' template_file ; do - local generated_file="./generated${template_file#./extra}" - mkdir -p "$(dirname "$generated_file")" - generated_file="${generated_file/.template/}" - if [ ! -f "$generated_file" ] || [ "$FLAG_SKIP_REGENERATE" != "yes" ] ; then - "$SCRIPTS_DIR/generate-and-verify.sh" "$template_file" "$generated_file" \ - || return 1 - fi - done + echo "[~] Verifying templated files ..." + + while IFS= read -r template_file ; do + local generated_file="./generated${template_file#./extra}" + mkdir -p "$(dirname "$generated_file")" + generated_file="${generated_file/.template/}" + if [ ! -f "$generated_file" ] || [ "$FLAG_SKIP_REGENERATE" != "yes" ] ; then + "$SCRIPTS_DIR/generate-and-verify.sh" "$template_file" "$generated_file" \ + || return 1 + fi + done < <(find ./extra -name '*.template.*') } __maybe_fail_fast () { @@ -176,20 +177,20 @@ __read_option () { } __run_hooks () { - ls "docker-compose.$1.$2_hook*.sh" &> /dev/null || return 0 + local stage="$1.$2" + ls docker-compose."$stage"_hook*.sh &> /dev/null || return 0 echo "[*] Running '$2' hooks for '$1' ..." - if ! ( set -a && source .env && "./docker-compose.$1.$2_hook.sh" ) ; then - __error "HOOK 'docker-compose.$1.$2_hook.sh' FAILED!" ; return 1 + if ! ( set -a && source .env && ./docker-compose."$stage"_hook.sh ) ; then + __error "HOOK 'docker-compose.${stage}_hook.sh' FAILED!" ; return 1 fi if [ "$FLAG_SKIP_OVERRIDES" != "yes" ] ; then - find . -maxdepth 1 -type f -name "docker-compose.$1.$2_hook.override*.sh" -print0 | \ - while IFS= read -r -d '' hook_file ; do - if ! ( set -a && source .env && "$hook_file" ) ; then - __error "HOOK '${hook_file#./}' FAILED!" ; return 1 - fi - done + while IFS= read -r hook_file ; do + if ! ( set -a && source .env && "$hook_file" ) ; then + __error "HOOK '${hook_file#./}' FAILED!" ; return 1 + fi + done < <(find . -maxdepth 1 -type f -name "docker-compose.${stage}_hook.override*.sh") fi } @@ -221,7 +222,7 @@ __will_invoke_compose () { do_validate () { [ "$1" = "shallow" ] || __do_prereqs validate || return 1 - printf '[v] Validating service:' + printf '[~] Validating service:' for svc in $("$YQ_CMD" -M '.services | keys | .[]' docker-compose.yml) ; do local attrs=( $("$YQ_CMD" -M ".services.\"$svc\" | keys | .[]" docker-compose.yml) ) echo -n " $svc" @@ -252,20 +253,18 @@ do_overrides () { __do_prereqs overrides || return 1 local any_override='' - find .. -maxdepth 1 -type f -iname '*override*' -print0 | \ - while IFS= read -rd '' ofile ; do - any_override='YES' - printf '[G] ' ; realpath -s --relative-to="$(pwd)/.." "$ofile" - done - [ -z "$any_override" ] && echo '[>] No global override files.' + while IFS= read -r ofile ; do + any_override='YES' + printf '[G] ' ; realpath -s --relative-to="$(pwd)/.." "$ofile" + done < <(find .. -maxdepth 1 -type f -iname '*override*') + [ -n "$any_override" ] || echo '[>] No global override files.' any_override='' - find . -maxdepth 1 -type f -iname '*override*' -print0 | \ - while IFS= read -rd '' ofile ; do - any_override='YES' - printf '[L] ' ; realpath -s --relative-to="$(pwd)/.." "$ofile" - done - [ -z "$any_override" ] && echo '[>] No local override files.' + while IFS= read -r ofile ; do + any_override='YES' + printf '[L] ' ; realpath -s --relative-to="$(pwd)/.." "$ofile" + done < <(find . -maxdepth 1 -type f -iname '*override*') + [ -n "$any_override" ] || echo '[>] No local override files.' } do_pull () { @@ -289,7 +288,7 @@ do_status () { do_up () { __do_prereqs status "yes" || return 1 - $DOCKER_COMPOSE_CMD "${COMPOSE_FILES[@]/#/-f }" up -d + $DOCKER_COMPOSE_CMD ${COMPOSE_FILES[@]/#/-f } up -d } # # # # # # # # # # # # # # # # # # # OPTIONS PARSING # # # # # # # # # # # # # # # # # # # @@ -474,9 +473,9 @@ perform () { local SIMPLE_VERB="$1" for comp in "${COMPOSITIONS[@]}" ; do comp="${comp%/}" - echo -ne "\n[+] Executing '$SIMPLE_VERB' on " + echo -ne "\n${_fg_white_}${_bg_black_}${_bold_}[${SIMPLE_VERB:0:1}]${_normal_} Executing ${_bold_}$SIMPLE_VERB${_normal_} on " [ -z "$COMP_INTERNAL_CALL" ] || printf 'pre-req ' - echo "'$comp' ... " + echo "${_bold_}$comp${_normal_} ... " if ! { [ "$comp" = "$(basename "$comp")" ] && [ -d "$SELF_DIR/$comp" ]; } ; then __error "'$comp' is not a base directory at '$SELF_DIR'!" __maybe_fail_fast $EXIT_CODE_COMPOSITION_NOT_FOUND || continue