#!/bin/bash

# Copyright 2014,2015,2016,2017,2018,2019,2020,2021 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

if ! [ "$(id -u)" = 0 ]; then
   echo "This command must be run as root"
   exit 1
fi

display_help() {
cat <<HELP_USAGE

    $0  [-h] [-q|--quiet]

   -h             Show this message.
   -q|--quiet     Suppress the output and only return a
	              single status code for overall status
	0:Ok, 1:Error, 2:Starting/Pending, 99:Installing SO
HELP_USAGE
}

# Constants
QUIET=false
EXITCODE=0
SYSTEM_START_TIME=$(date -d "$(</proc/uptime awk '{print $1}') seconds ago" +%s)
# file populated by salt.lasthighstate state at end of successful highstate run
LAST_HIGHSTATE_END=$([ -e "/opt/so/log/salt/lasthighstate" ] && date -r /opt/so/log/salt/lasthighstate +%s || echo 0)
LAST_SOSETUP_LOG=$([ -e "/root/sosetup.log" ] && date -r /root/sosetup.log +%s || echo 0)
HIGHSTATE_RUNNING=$(salt-call --local saltutil.running --out=json | jq -r '.local[].fun' | grep -q 'state.highstate' && echo $?)
ERROR_STRING="ERROR"
SUCCESS_STRING="OK"
PENDING_STRING="PENDING"
MISSING_STRING='MISSING'
DISABLED_STRING='DISABLED'
WAIT_START_STRING='WAIT_START'
STARTING_STRING='STARTING'
CALLER=$(ps -o comm= $PPID)
declare -a BAD_STATUSES=("removing" "paused" "exited" "dead")
declare -a PENDING_STATUSES=("paused" "created" "restarting")
declare -a GOOD_STATUSES=("running")
declare -a DISABLED_CONTAINERS=()
mapfile -t DISABLED_CONTAINERS < <(sort -u /opt/so/conf/so-status/so-status.conf | grep "^\s*#" | tr -d "#")


declare -a temp_container_name_list=()
declare -a temp_container_state_list=()

declare -a container_name_list=()
declare -a container_state_list=()

declare -a expected_container_list=()

# {% raw %}

compare_lists() {
    local found=0

    create_expected_container_list

    if [[ ${#expected_container_list[@]} = 0 ]]; then
        container_name_list="${temp_container_name_list[*]}"
        container_state_list="${temp_container_state_list[*]}"
        return 1
    fi

    for intended_item in "${expected_container_list[@]}"; do
        found=0
        for i in "${!temp_container_name_list[@]}"; do
            [[ ${temp_container_name_list[$i]} = "$intended_item" ]] \
                && found=1 \
                && container_name_list+=("${temp_container_name_list[$i]}") \
                && container_state_list+=("${temp_container_state_list[$i]}") \
                && break
        done
        if [[ $found = 0 ]]; then
            container_name_list+=("$intended_item")
            container_state_list+=("missing")
        fi
    done
}

# {% endraw %}

create_expected_container_list() {

    mapfile -t expected_container_list < <(sort -u /opt/so/conf/so-status/so-status.conf | tr -d "#")

}

populate_container_lists() {
    systemctl is-active --quiet docker

    if [[ $? = 0 ]]; then
        mapfile -t docker_raw_list < <(curl -s --unix-socket /var/run/docker.sock http:/v1.40/containers/json?all=1 \
            | jq -c '.[] | { Name: .Names[0], State: .State }' \
            | tr -d '/{"}')
    else
        exit 1
    fi

    local container_name=""
    local container_state=""

    for line in "${docker_raw_list[@]}"; do
        container_name="$( echo $line | sed -e 's/Name:\(.*\),State:\(.*\)/\1/' )" # Get value in the first search group (container names)
        container_state="$( echo $line | sed -e 's/Name:\(.*\),State:\(.*\)/\2/' )" # Get value in the second search group (container states)

        temp_container_name_list+=( "${container_name}" )
        temp_container_state_list+=( "${container_state}" )
    done

    compare_lists
}

parse_status() {
    local service_name=${1}
    local container_state=${2}

    for state in "${GOOD_STATUSES[@]}"; do
        [[ $container_state = "$state" ]] && [[ $QUIET = "false" ]] && printf $SUCCESS_STRING && return 0 || [[ $container_state = "$state" ]] && return 0
    done

    for state in "${BAD_STATUSES[@]}"; do
        [[ " ${DISABLED_CONTAINERS[@]} " =~ " ${service_name} " ]] && [[ $QUIET = "false" ]] && printf $DISABLED_STRING && return 0 || [[ " ${DISABLED_CONTAINERS[@]} " =~ " ${service_name} " ]] && return 0
    done

    # if a highstate has finished running since the system has started
    # then the containers should be running so let's check the status
    if [ $LAST_HIGHSTATE_END -ge $SYSTEM_START_TIME ]; then

        [[ $container_state = "missing" ]] && [[ $QUIET = "false" ]] && printf $MISSING_STRING && return 1 || [[ $container_state = "missing" ]] && [[ "$EXITCODE" -lt 2 ]] && EXITCODE=1  && return 1

        for state in "${PENDING_STATUSES[@]}"; do
            [[ $container_state = "$state" ]] && [[ $QUIET = "false" ]] && printf $PENDING_STRING && return 0
        done

        # This is technically not needed since the default is error state
        for state in "${BAD_STATUSES[@]}"; do
            [[ $container_state = "$state" ]] && [[ $QUIET = "false" ]] && printf $ERROR_STRING && return 1 || [[ $container_state = "$state" ]] && [[ "$EXITCODE" -lt 2 ]] && EXITCODE=1  && return 1
        done

        [[ $QUIET = "false" ]] && printf $ERROR_STRING && return 1 || [[ "$EXITCODE" -lt 2 ]] && EXITCODE=1 && return 1

    # if a highstate has not run since system start time, but a highstate is currently running
    # then show that the containers are STARTING
    elif [[ "$HIGHSTATE_RUNNING" == 0 ]]; then
        [[ $QUIET = "false" ]] && printf $STARTING_STRING && return 2 || EXITCODE=2 && return 2

    # if a highstate has not finished running since system startup and isn't currently running
    # then just show that the containers are WAIT_START; waiting to be started
    else
        [[ $QUIET = "false" ]] && printf $WAIT_START_STRING && return 2 || EXITCODE=2 && return 2

    fi
}

# {% raw %}

print_line() {
    local service_name=${1}
    local service_state="$( parse_status ${1} ${2} )"
    local columns=$(tput cols)
    local state_color="\e[0m"

    local PADDING_CONSTANT=15

    if [[ $service_state = "$ERROR_STRING" ]] || [[ $service_state = "$MISSING_STRING" ]]; then
        state_color="\e[1;31m"
        if [[ "$EXITCODE" -eq 0 ]]; then
            EXITCODE=1
        fi
    elif [[ $service_state = "$SUCCESS_STRING" ]]; then
        state_color="\e[1;32m"
    elif [[ $service_state = "$PENDING_STRING" ]] || [[ $service_state = "$DISABLED_STRING" ]] || [[ $service_state = "$STARTING_STRING" ]] || [[ $service_state = "$WAIT_START_STRING" ]]; then
        state_color="\e[1;33m"
        EXITCODE=2
    fi

    printf "    $service_name "
    for i in $(seq 0 $(( $columns - $PADDING_CONSTANT - ${#service_name} - ${#service_state} ))); do
        printf "${state_color}%b\e[0m" "-"
    done
    printf " [ "
    printf "${state_color}%b\e[0m" "$service_state"
    printf "%s    \n" " ]"
}

non_term_print_line() {
    local service_name=${1}
    local service_state="$( parse_status ${1} ${2} )"

    if [[ $service_state = "$ERROR_STRING" ]] || [[ $service_state = "$MISSING_STRING" ]]; then
        if [[ "$EXITCODE" -eq 0 ]]; then
            EXITCODE=1
        fi
    elif [[ $service_state = "$PENDING_STRING" ]] || [[ $service_state = "$DISABLED_STRING" ]] || [[ $service_state = "$STARTING_STRING" ]] || [[ $service_state = "$WAIT_START_STRING" ]]; then
        EXITCODE=2
    fi

    printf "    $service_name "
    for i in $(seq 0 $(( 35 - ${#service_name} - ${#service_state} ))); do
        printf "-"
    done
    printf " [ "
    printf "$service_state"
    printf "%s    \n" " ]"
}

main() {

    # if running from salt
    if [ "$CALLER" == 'salt-call' ] ||  [ "$CALLER" == 'salt-minion' ]; then
      printf "\n"
      printf "Checking Docker status\n\n"

      systemctl is-active --quiet docker
      if [[ $? = 0 ]]; then
          non_term_print_line "Docker" "running"
      else
          non_term_print_line "Docker" "exited"
      fi

      populate_container_lists

      printf "\n"
      printf "Checking container statuses\n\n"

      local num_containers=${#container_name_list[@]}

      for i in $(seq 0 $(($num_containers - 1 ))); do
          non_term_print_line ${container_name_list[$i]} ${container_state_list[$i]}
      done

      printf "\n"

    # else if running from a terminal
    else

      if [ "$QUIET" = true ]; then
          if [ $SYSTEM_START_TIME -lt $LAST_SOSETUP_LOG ]; then
            exit 99
          fi
          print_or_parse="parse_status"
      else
          print_or_parse="print_line"

          local focus_color="\e[1;34m"
          printf "\n"
          printf "${focus_color}%b\e[0m" "Checking Docker status\n\n"
      fi

      systemctl is-active --quiet docker
      if [[ $? = 0 ]]; then
          ${print_or_parse} "Docker" "running"
      else
          ${print_or_parse} "Docker" "exited"
      fi

      populate_container_lists

      if [ "$QUIET" = false ]; then
          printf "\n"
          printf "${focus_color}%b\e[0m" "Checking container statuses\n\n"
      fi

      local num_containers=${#container_name_list[@]}

      for i in $(seq 0 $(($num_containers - 1 ))); do
          ${print_or_parse} ${container_name_list[$i]} ${container_state_list[$i]}
      done

      if [ "$QUIET" = false ]; then
          printf "\n"
      fi
    fi
}

# {% endraw %}

while getopts ':hq' OPTION; do
  case "$OPTION" in
    h)
      display_help
      exit 0
      ;;
    q)
      QUIET=true
      ;;
    \?)
      display_help
      exit 0
      ;;
  esac
done

main

exit $EXITCODE