diff --git a/.github/workflows/compose-test_docker.sock.yml b/.github/workflows/compose-test_docker_sock.yml similarity index 90% rename from .github/workflows/compose-test_docker.sock.yml rename to .github/workflows/compose-test_docker_sock.yml index d57bc32..68e6f41 100644 --- a/.github/workflows/compose-test_docker.sock.yml +++ b/.github/workflows/compose-test_docker_sock.yml @@ -1,4 +1,4 @@ -name: "Docker Compose Test (docker.sock)" +name: "Docker Compose Test (docker_sock)" on: pull_request: @@ -9,7 +9,7 @@ on: - cron: "0 8 * * *" env: - TARGET_SERVICE: docker.sock + TARGET_SERVICE: docker_sock TARGET_TIMEOUT: 15 jobs: diff --git a/.github/workflows/compose-test_kodi.db.yml b/.github/workflows/compose-test_kodi_db.yml similarity index 93% rename from .github/workflows/compose-test_kodi.db.yml rename to .github/workflows/compose-test_kodi_db.yml index 7d2ec46..f80d961 100644 --- a/.github/workflows/compose-test_kodi.db.yml +++ b/.github/workflows/compose-test_kodi_db.yml @@ -1,4 +1,4 @@ -name: "Docker Compose Test (Kodi.DB)" +name: "Docker Compose Test (Kodi_DB)" on: pull_request: @@ -9,7 +9,7 @@ on: - cron: "0 8 * * *" env: - TARGET_SERVICE: kodi.db + TARGET_SERVICE: kodi_db TARGET_TIMEOUT: 60 jobs: diff --git a/README.md b/README.md index b47341e..ebd9d24 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,8 @@ Options: { yes | no } ignores options*.conf and uses all 'docker-compose.*.{sh,yml}' files. Compositions Found (18): - airdcpp certbot docker.sock gitea hass indexarr - influxdb kodi.db monitarr navidrome nextcloud pihole + airdcpp certbot docker_sock gitea hass indexarr + influxdb kodi_db monitarr navidrome nextcloud pihole qbittorrent tang telegraf teslamate traefik unifi ``` @@ -229,11 +229,11 @@ Compositions Found (18): - docker.sock + docker_sock
-
- + A @@ -466,7 +466,7 @@ Compositions Found (18):
:3306
- + B diff --git a/_scripts/colors.sh b/_scripts/colors.sh index 6c1c381..2824489 100644 --- a/_scripts/colors.sh +++ b/_scripts/colors.sh @@ -1,6 +1,11 @@ #!/usr/bin/env bash +set -Eumo pipefail + +DISABLE_COLORS=${DISABLE_COLORS:-} + function P () { + [ -z "$DISABLE_COLORS" ] || return 0 tput "$@" 2>/dev/null } diff --git a/_scripts/test_comp.sh b/_scripts/test_comp.sh index dc349c7..458be3d 100755 --- a/_scripts/test_comp.sh +++ b/_scripts/test_comp.sh @@ -19,7 +19,7 @@ mkdir -p "$TEST_COMP_TMP_PATH" # SETUP function SETUP () { - echo -e "\n${_fg_white_}${_bg_black_}${_bold_}[#]${_normal_} Test $1: " + echo -e "\n${_fg_white_}${_bg_black_}${_bold_} T ${_normal_} Test $1: " local io_path_prefix="$TEST_COMP_TMP_PATH/$1" COMP_ERR_PATH="$io_path_prefix.err" @@ -30,7 +30,7 @@ function SETUP () { echo "INPUT: $3" fi - ./comp $2 >"$COMP_OUT_PATH" 2>"$COMP_ERR_PATH" <<<"$3" + DISABLE_COLORS=1 ./comp $2 >"$COMP_OUT_PATH" 2>"$COMP_ERR_PATH" <<<"$3" COMP_EXIT_CODE=$? if [ -n "$DEBUG" ]; then echo "\$COMP_ERR_PATH contents:" @@ -52,8 +52,6 @@ function CHECK { fi } -./comp status tang &> /dev/null - SETUP "invocation without args" \ "" \ "" @@ -63,29 +61,84 @@ CHECK $'[ $COMP_EXIT_CODE -eq $EXIT_CODE_USAGE_ERROR ]' \ SETUP "status of non-existent composition" \ "status unknown_comp" \ "" +CHECK $'grep -sq "Executing status on unknown_comp" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" 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" \ +SETUP "that 'tang' is not running" \ + "status -P tang" \ + "" +CHECK $'grep -sq "Executing status on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" +CHECK $'grep -sq "Unhealthy service: tang" "$COMP_ERR_PATH"' \ + "Expected error message for unhealthy service" +CHECK $'[ $COMP_EXIT_CODE -eq $EXIT_CODE_SIMPLE_VERB_FAILURE ]' \ + "Expected EXIT_CODE_SIMPLE_VERB_FAILURE" + +SETUP "starting 'tang' service without prerequisites" \ + "up -P tang" \ + "n" +CHECK $'grep -sq "Executing up on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" +CHECK $'[ $COMP_EXIT_CODE -eq 0 ]' \ + "Expected EXIT_CODE = 0" + +SETUP "cleaning 'tang' service before stopping" \ + "clean tang" \ + "" +CHECK $'grep -sq "Executing clean on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" +CHECK $'grep -sq "Cannot clean while tang is running" "$COMP_ERR_PATH"' \ + "Expected error message for running composition" +CHECK $'[ $COMP_EXIT_CODE -eq $EXIT_CODE_SIMPLE_VERB_FAILURE ]' \ + "Expected EXIT_CODE_SIMPLE_VERB_FAILURE" + +# Wait for tang to start and record health status +sleep 15s + +SETUP "that 'tang' is still running" \ + "status -P tang" \ + "" +CHECK $'grep -sq "Executing status on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" +CHECK $'grep -sq "tang is healthy" "$COMP_OUT_PATH"' \ + "Expected info message for healthy composition" +CHECK $'[ $COMP_EXIT_CODE -eq 0 ]' \ + "Expected EXIT_CODE = 0" + +SETUP "stopping 'tang' service" \ + "down tang" \ + "" +CHECK $'grep -sq "Executing down on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" +CHECK $'[ $COMP_EXIT_CODE -eq 0 ]' \ + "Expected EXIT_CODE = 0" + +SETUP "cleaning, but deny deleting data" \ "clean tang" \ "n" +CHECK $'grep -sq "Executing clean on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" 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 ]' \ +CHECK $'[ -d tang/data ]' \ "Expected data is not deleted" -SETUP "allow deleting data" \ +SETUP "cleaning, and allow deleting data" \ "clean tang" \ "y" +CHECK $'grep -sq "Executing clean on tang" "$COMP_OUT_PATH"' \ + "Expected info message at beginning of execution" 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 ]' \ +CHECK $'! [ -d tang/data ]' \ "Expected data is deleted" exit "$FINAL_EXIT_CODE" diff --git a/comp b/comp index 64a12e8..0440b05 100755 --- a/comp +++ b/comp @@ -138,6 +138,15 @@ __gen_templates () { done < <(find ./extra -name '*.template.*') } +__grab_ps () { + # FIXME: `podman` doesn't print health status! + local _ps_res="$( $DOCKER_CMD ps \ + --format '{{.Label "com.docker.compose.service"}}\t{{.Status}}' \ + --filter label=com.docker.compose.project=$comp \ + )" + eval "$1"='$_ps_res' +} + __maybe_fail_fast () { [ "$FLAG_FAIL_FAST" != "yes" ] || exit "$1" @@ -216,20 +225,25 @@ __verify_volumes () { } __will_invoke_compose () { - [ "$1" = "down" ] || [ "$1" = "pull" ] || [ "$1" = "status" ] || [ "$1" = "up" ] + [ "$1" = "down" ] || [ "$1" = "pull" ] || [ "$1" = "up" ] } # # # # # # # # # # # # # # # # # # # TOP-LEVEL VERBS # # # # # # # # # # # # # # # # # # # do_validate () { - [ "$1" = "shallow" ] || __do_prereqs validate || return 1 + [ "${1:-}" = "shallow" ] || __do_prereqs validate || return 1 + + if echo "$comp" | grep -q '\.'; then + __error "$DOCKER_COMPOSE_CMD will use '${comp//.}' as project name." + return 1 + fi 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" for bad_attr in devices labels logging ports ; do - if printf '%s\0' "${attrs[@]}" | grep -Fxqz -- $bad_attr; then + if printf '%s\0' "${attrs[@]}" | grep -Fxqz -- $bad_attr ; then echo ; __error "'$bad_attr' for '$svc' should be in docker_compose.$bad_attr.yml." return 1 fi @@ -239,12 +253,18 @@ do_validate () { } do_clean () { - do_down || return 1 - - echo -n "Remove '$comp/data' (y/N)? " ; read -rn1 2>&1 ; echo - [[ $REPLY =~ ^[Yy]$ ]] || return 0 + __grab_ps __ps_before_clean + if [ -n "$__ps_before_clean" ] ; then + __error "Cannot clean while ${_bold_}$comp${_normal_} is running!" + return 1 + fi - rm -rfv data generated .env + rm -rfv generated .env + if [ -d data ] ; then + echo -n "Remove '$comp/data' (y/N)? " ; read -rn1 2>&1 + [[ $REPLY =~ ^[Yy]$ ]] && echo || return 0 + fi + rm -rfv data } do_down () { @@ -277,11 +297,12 @@ do_pull () { do_status () { __do_prereqs status || return 1 + __grab_ps __ps_before_status printf '[?] Querying service:' for svc in $( "$YQ_CMD" -M '.services | keys | .[]' docker-compose.yml ) ; do echo -n " $svc" - if ! ( $DOCKER_COMPOSE_CMD ps "$svc" 2> /dev/null | grep -q healthy ) ; then - echo ; __error "'$svc' is not healthy." + if ! ( echo "$__ps_before_status" | grep "$svc" | grep -q healthy ) ; then + echo ; __error "Unhealthy service: ${_fg_red_}$svc${_normal_}" return 1 fi done @@ -303,14 +324,14 @@ usage () { Usage: $0 [,,...] [flags] [ ...] -Verbs: (short forms within []) - [c]lean Delete '/data' - [d]own Stop a composition - [o]verrides List all override files in a composition - [p]ull Pull all images for a composition - [s]tatus Display health / status of a composition - [u]p Start a composition - [v]alidate Validate a composition +Verbs: (short forms within <>) + lean Delete '/data' + own Stop a composition + verrides List all override files in a composition +

ull Pull all images for a composition + tatus Display health / status of a composition + p Start a composition + alidate Validate a composition Flags: [-P | --skip-prereqs] Ignore verifying/starting prerequisite compositions @@ -475,15 +496,15 @@ perform () { local SIMPLE_VERB="$1" for comp in "${COMPOSITIONS[@]}" ; do comp="${comp%/}" - echo -ne "\n${_fg_white_}${_bg_black_}${_bold_}[${SIMPLE_VERB:0:1}]${_normal_} Executing ${_bold_}$SIMPLE_VERB${_normal_} 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 "${_bold_}$comp${_normal_} ... " if ! { [ "$comp" = "$(basename "$comp")" ] && [ -d "$SELF_DIR/$comp" ]; } ; then - __error "'$comp' is not a base directory at '$SELF_DIR'!" + __error "${_bold_}$comp${_normal_} is not a base directory at '$SELF_DIR'!" __maybe_fail_fast $EXIT_CODE_COMPOSITION_NOT_FOUND || continue fi if ! [ -f "$SELF_DIR/$comp/docker-compose.yml" ] ; then - __error "No 'docker-compose.yml' found under '$comp'!" + __error "No 'docker-compose.yml' found under ${_bold_}$comp${_normal_}!" __maybe_fail_fast $EXIT_CODE_COMPOSITION_NOT_FOUND || continue fi @@ -526,8 +547,8 @@ perform () { local verb_exit=0 "do_${SIMPLE_VERB}" ; verb_exit=$? - [ "$SIMPLE_VERB" != "validate" ] || [ $verb_exit -ne 0 ] || echo "[=] '$comp' is valid!" - [ "$SIMPLE_VERB" != "status" ] || [ $verb_exit -ne 0 ] || echo "[=] '$comp' is healthy!" + [ "$SIMPLE_VERB" != "validate" ] || [ $verb_exit -ne 0 ] || echo "[=] ${_bold_}$comp${_normal_} is valid!" + [ "$SIMPLE_VERB" != "status" ] || [ $verb_exit -ne 0 ] || echo "[=] ${_bold_}$comp${_normal_} is healthy!" [ $verb_exit -eq 0 ] \ || __maybe_fail_fast $EXIT_CODE_SIMPLE_VERB_FAILURE || continue diff --git a/docker.sock/docker-compose.labels.yml b/docker_sock/docker-compose.labels.yml similarity index 97% rename from docker.sock/docker-compose.labels.yml rename to docker_sock/docker-compose.labels.yml index 70ca852..4ca57e7 100644 --- a/docker.sock/docker-compose.labels.yml +++ b/docker_sock/docker-compose.labels.yml @@ -5,6 +5,6 @@ services: labels: traefik.enable: true traefik.docker.network: shared - traefik.http.routers.docker_sock.rule: PathPrefix(`/docker.sock`) + traefik.http.routers.docker_sock.rule: PathPrefix(`/docker_sock`) traefik.http.services.docker_sock.loadBalancer.server.port: 9000 traefik.http.routers.docker_sock.entryPoints: lan-https diff --git a/docker.sock/docker-compose.logging.yml b/docker_sock/docker-compose.logging.yml similarity index 100% rename from docker.sock/docker-compose.logging.yml rename to docker_sock/docker-compose.logging.yml diff --git a/docker.sock/docker-compose.yml b/docker_sock/docker-compose.yml similarity index 100% rename from docker.sock/docker-compose.yml rename to docker_sock/docker-compose.yml diff --git a/kodi.db/config/mariadb/docker-entrypoint-initdb.d/kodi.sql b/kodi_db/config/mariadb/docker-entrypoint-initdb.d/kodi.sql similarity index 100% rename from kodi.db/config/mariadb/docker-entrypoint-initdb.d/kodi.sql rename to kodi_db/config/mariadb/docker-entrypoint-initdb.d/kodi.sql diff --git a/kodi.db/docker-compose.logging.yml b/kodi_db/docker-compose.logging.yml similarity index 100% rename from kodi.db/docker-compose.logging.yml rename to kodi_db/docker-compose.logging.yml diff --git a/kodi.db/docker-compose.ports.yml b/kodi_db/docker-compose.ports.yml similarity index 100% rename from kodi.db/docker-compose.ports.yml rename to kodi_db/docker-compose.ports.yml diff --git a/kodi.db/docker-compose.pre_hook.sh b/kodi_db/docker-compose.pre_hook.sh similarity index 100% rename from kodi.db/docker-compose.pre_hook.sh rename to kodi_db/docker-compose.pre_hook.sh diff --git a/kodi.db/docker-compose.yml b/kodi_db/docker-compose.yml similarity index 90% rename from kodi.db/docker-compose.yml rename to kodi_db/docker-compose.yml index bef6e3d..3e37edd 100644 --- a/kodi.db/docker-compose.yml +++ b/kodi_db/docker-compose.yml @@ -16,7 +16,7 @@ services: - ./data/mariadb/var/lib/mysql:/var/lib/mysql environment: - MARIADB_ROOT_PASSWORD: 'kodi.db_mariadb_root_password' + MARIADB_ROOT_PASSWORD: 'kodi_db_mariadb_root_password' healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] diff --git a/telegraf/pre.reqs b/telegraf/pre.reqs index 91745f5..180d4a7 100644 --- a/telegraf/pre.reqs +++ b/telegraf/pre.reqs @@ -1 +1 @@ -docker.sock +docker_sock diff --git a/traefik/pre.reqs b/traefik/pre.reqs index 91745f5..180d4a7 100644 --- a/traefik/pre.reqs +++ b/traefik/pre.reqs @@ -1 +1 @@ -docker.sock +docker_sock