#!/bin/bash
#
# Sipwise NGCP parallel SSH wrapper.
#

set -e

SELF=${0##*/}
DEBUG=${NGCP_DEBUG:-false}

debug () {
  if "${DEBUG}" ; then
    echo "[${SELF}]: $*"
  fi
}

error () {
  echo "[${SELF}]: error: $*" >&2
  exit 2
}

usage () {
  cat <<HELP
Usage: ${SELF} [<options>] <hosts> <commands>

Options:
  -l, --user=USER        User name.
  -p, --par=PAR          Max number of parallel threads.
  -o, --outdir=OUTDIR    Output directory for stdout files.
  -e, --errdir=ERRDIR    Output directory for stderr files.
  -t, --timeout=TIMEOUT  Timeout (secs) (0 = no timeout) per host.
  -O, --options=OPTIONS  SSH options.
  -x, --extra-args=ARGS  Extra command-line arguments, with processing for
                           spaces, quotes, and backslashes.
  -X, --extra-arg=ARG    Extra command-line argument.
      --force            Force disruptive action on active nodes (ex. poweroff).
      --inline-stdout    Inline standard output for each server.
  -I, --send-input       Read from standard input and send as input to ssh.
  -P, --print            Print output as we get it.
  -v, --verbose          Turn on warning and diagnostic messages.
      --help             Show this help message and exit.

<hosts> supports hostnames, or the following aliases:
  self - this node
  peer - the peer for this node
  pair - this node and its peer
  active - all active nodes in the cluster
  inactive - all inactive nodes in the cluster
  neighbours - all nodes in the cluster except for self
  all - all nodes in the cluster
HELP
}

if [ -r /etc/default/ngcp-roles ] ; then
  # shellcheck disable=SC1091
  . /etc/default/ngcp-roles
else
  NGCP_SSHD=$(awk '/Port/ { print $2 }' /etc/ssh/sshd_config)
fi

# Defaults.
PSSH_OPTS=()
PSSH_HOST=
PSSH_CMDS=
PSSH_PORT="${NGCP_SSHD:-22}"
PSSH_FORCE=false
PSSH_CHECK_DISRUPTION=false

# Parse wrapper options.
while [[ $# -gt 0 ]]
do
  optname="$1"
  shift

  case "${optname}" in
    -[lptoexXO]|--user|--par|--timeout|--outdir|--errdir|--extra-args|--extra-arg|--options)
      PSSH_OPTS+=("${optname}" "$1")
      shift
      ;;
    -[lptoexXO]*|--user=*|--par=*|--timeout=*|--outdir=*|--errdir=*|--extra-args=*|--extra-arg=*|--options=*)
      PSSH_OPTS+=("${optname}")
      ;;
    -[vIP]|--inline-stdout|--print|--verbose)
      PSSH_OPTS+=("${optname}")
      ;;
    --force)
      PSSH_FORCE=true
      ;;
    -?|--help)
      usage
      exit 0
      ;;
    *)
      if [[ $# -ne 1 ]]
      then
        usage 2>&1
        exit 1
      fi
      PSSH_HOST="${optname}"
      PSSH_CMDS="$1"
      break
      ;;
  esac
done

# Handle host aliases.
PSSH_HOSTS=()
for host in ${PSSH_HOST}
do
  case ${host} in
    self)
      PSSH_HOSTS+=("${NGCP_HOSTNAME_PSSH}")
      if ngcp-check-active -q; then
        PSSH_CHECK_DISRUPTION=true
      fi
      ;;
    peer)
      PSSH_HOSTS+=("${NGCP_PEERNAME_PSSH}")
      if ! ngcp-check-active -q; then
        PSSH_CHECK_DISRUPTION=true
      fi
      ;;
    pair)
      PSSH_HOSTS+=("${NGCP_HOSTNAME_PSSH}" "${NGCP_PEERNAME_PSSH}")
      PSSH_CHECK_DISRUPTION=true
      ;;
    all)
      # shellcheck disable=SC2206
      PSSH_HOSTS+=(${NGCP_HOSTS_PSSH})
      PSSH_CHECK_DISRUPTION=true
      ;;
    neighbours)
      # shellcheck disable=SC2206
      PSSH_HOSTS+=(${NGCP_NEIGHBOURS_PSSH})
      PSSH_CHECK_DISRUPTION=true
      ;;
    active)
      # shellcheck disable=SC2206
      PSSH_HOSTS+=(${NGCP_HOSTS_PSSH})
      PSSH_CMDS="if ngcp-check-active -q; then ${PSSH_CMDS}; else echo '[${SELF}] Skipping inactive node.'; fi"
      PSSH_CHECK_DISRUPTION=true
      ;;
    inactive)
      # shellcheck disable=SC2206
      PSSH_HOSTS+=(${NGCP_HOSTS_PSSH})
      PSSH_CMDS="if ! ngcp-check-active -q; then ${PSSH_CMDS}; else echo '[${SELF}] Skipping active node.'; fi"
      ;;
    *)
      PSSH_HOSTS+=("${host}:${PSSH_PORT}")
      ;;
  esac
done

if [ ${#PSSH_HOSTS[@]} -eq 0 ]
then
  echo "[${SELF}] No hosts specified, skipping."
  exit 0
fi

if ! ${PSSH_FORCE} && ${PSSH_CHECK_DISRUPTION}; then
  for cmd in "${PSSH_CMDS[@]}"; do
    case "${cmd}" in
      *shutdown*|*poweroff*|*halt*|*reboot*|*hibernate*|*suspend*)
        error "Disruptive operation requested for active nodes, aborting."
        ;;
    esac
  done
fi

PSSH=(parallel-ssh)
PSSH+=(-O "BatchMode=yes")
PSSH+=("${PSSH_OPTS[@]}")
PSSH+=(-H "${PSSH_HOSTS[*]}")
PSSH+=(-i)
PSSH+=("${PSSH_CMDS[*]}")

debug "${PSSH[*]}"
"${PSSH[@]}"
