#!/bin/bash -e

############################################################################ begin logging
# see if it supports colors...
ncolors=$(tput colors)

if test -n "$ncolors" && test $ncolors -ge 8; then
    bold="$(tput bold)"
    underline="$(tput smul)"
    standout="$(tput smso)"
    normal="$(tput sgr0)"
    black="$(tput setaf 0)"
    red="$(tput setaf 1)"
    green="$(tput setaf 2)"
    yellow="$(tput setaf 3)"
    blue="$(tput setaf 4)"
    magenta="$(tput setaf 5)"
    cyan="$(tput setaf 6)"
    white="$(tput setaf 7)"
fi

append_msg() {
    if test $# -ne 0; then
        echo -n ": ${bold}$*"
    fi
    echo "${normal}"
}

# write a message
message() {
    if test $# -eq 0; then
        return
    fi
    echo "${bold}${while}$*${normal}" 1>&2
}

# write a success message
success() {
    echo -n "${bold}${green}success" 1>&2
    append_msg $* 1>&2
}

# write a notice
notice() {
    echo -n "${bold}${yellow}notice" 1>&2
    append_msg $* 1>&2
}

# write a warning message
warning() {
    echo -en "${bold}${red}warning" 1>&2
    append_msg $* 1>&2
}

# write error message
error() {
    echo -en "${bold}${red}error" 1>&2
    append_msg $* 1>&2
}

# run a command, print the result and abort in case of error
# option: --ignore: ignore the result, continue in case of error
run() {
    ignore=1
    while test $# -gt 0; do
        case "$1" in
            (--ignore) ignore=0;;
            (*) break;;
        esac
        shift;
    done
    echo -n "${bold}${yellow}running:${white} $*${normal} … "
    set +e
    result=$($* 2>&1)
    res=$?
    set -e
    if test $res -ne 0; then
        if test $ignore -eq 1; then
            error "failed with return code: $res"
            if test -n "$result"; then
                echo "$result"
            fi
            exit 1
        else
            warning "ignored return code: $res"
        fi
    else
        success
    fi
}

############################################################################ error handler
function traperror() {
    set +x
    local err=($1) # error status
    local line="$2" # LINENO
    local linecallfunc="$3"
    local command="$4"
    local funcstack="$5"
    IFS=" "
    for e in ${err[@]}; do
        if test -n "$e" -a "$e" != "0"; then
            error "line $line - command '$command' exited with status: $e (${err[@]})"
            if [ "${funcstack}" != "main" -o "$linecallfunc" != "0" ]; then
                echo -n "   ... error at ${funcstack} " 1>&2
                if [ "$linecallfunc" != "" ]; then
                    echo -n "called at line $linecallfunc" 1>&2
                fi
                echo
            fi
            exit $e
        fi
    done
    exit 0
}

# catch errors
trap 'traperror "$? ${PIPESTATUS[@]}" $LINENO $BASH_LINENO "$BASH_COMMAND" "${FUNCNAME[@]}" "${FUNCTION}"' ERR SIGINT INT TERM EXIT



######################################################### commandline parameter evaluation
exec=0
stop=0
log=0
stacks=
short=0
while test $# -gt 0; do
    case "$1" in
        (--help|-h) less <<EOF
SYNOPSIS

  $0 [OPTIONS] [stacks]

OPTIONS

  --help, -h                 show this help
  --log, -l                  show log command
  --exec, -e                 show exec command
  --stop, -x                 show stop command
  --short, -s                only show errors

  stacks                     optional space separated list of stacks

DESCRIPTION

Show status of all docker swarm stacks. The problems of docker swarm ps are, that you can only specify one stack to analyze, and then you get too much output. So this command lists all stacks and clearly shows which stacks are running and which stacks have a problem.

EOF
		    exit;;
	(--log|-l) log=1;;
	(--exec|-e) exec=1;;
	(--stop|-x) stop=1;;
	(--short|-s) short=1;;
        (*) stacks="$*"; break;;
    esac
    if test $# -eq 0; then
        error "missing parameter, try $0 --help"; exit 1
    fi
    shift;
done

##################################################################################### Main

stacks=${stacks:-$(docker stack ls --format='{{.Name}}')}

services=$(for stack in ${stacks}; do
	       status=$(docker stack ps --no-trunc --filter="desired-state=running" --format="{{.CurrentState}};{{.Name}};{{.Node}};{{.DesiredState}};{{.CurrentState}};{{.ID}};{{.Error}}" ${stack} | sed 's,^[^ ]* ,,')
	       IFS="
"
	       for service in ${status}; do
		   time=${service%%;*}
		   echo "$(date +%s -d "$(sed 's,about an* ,1 ,;s,less than an* ,0 ,' <<<${time})");${service#*;}"
	       done
	   done | sort -hr)

IFS="
"
for service in ${services}; do
    awk -F';' -v dolog=$log -v doexec=$exec -v dostop=$stop -v doshort=$short '
                                          {color="'"${green}"'"}
            $5 ~ /second|minute/          {color="'"${yellow}"'"}
            $5 !~ /^Running/              {printf "'"${red}"'%-15s%-40s%s %s%s\n", $3, $2, $5, $7, "'"${normal}"'"}
            $5 ~ /^Running/ && doshort==0 {printf "%s%-15s%-40s%s%s\n", color, $3, $2, $5, "'"${normal}"'"}
            dolog==1 && (doshort==0 || $5 !~ /^Running/) {printf "ssh %s docker logs -f %s.%s\n", $3, $2, $6}
            doexec==1 && (dohort==0 || $5 !~ /^Running/) {printf "ssh -t %s docker exec -it %s.%s bash\n", $3, $2, $6}
            dostop==1 && (dohort==0 || $5 !~ /^Running/) {printf "ssh %s docker stop %s.%s\n", $3, $2, $6}
        ' <<<${service}
done