#!/bin/bash
# WF 2018-12-31
# test the installation

# global variables
IMAGE_PREFIX=mediawiki

#ansi colors
#http://www.csc.uvic.ca/~sae/seng265/fall04/tips/s265s047-tips/bash-using-colors.html
blue='\x1B[0;34m'
red='\x1B[0;31m'
green='\x1B[0;32m' # '\e[1;32m' is too bright for white bg.
endColor='\x1B[0m'

#
# a colored message
#   params:
#     1: l_color - the color of the message
#     2: l_msg - the message to display
#
color_msg() {
  local l_color="$1"
  local l_msg="$2"
  echo -e "${l_color}$l_msg${endColor}"
}

#
# error
#
#   show an error message and exit
#
#   params:
#     1: l_msg - the message to display
error() {
  local l_msg="$1"
  # use ansi red for error
  color_msg $red "Error: $l_msg" 1>&2
  exit 1
}

#
# show usage
#
usage() {
  echo "$0"
  echo ""
  echo "options: "
  echo "       -a|--all              : testall available mediawiki versions"
  echo "       -c|--clean            : clean - clean up docker containers and volumes (juse with caution)"
  echo "       -h|--help             : show this usage"
  echo "       -m VERSION            : test the given install script"
  exit 1
}

#
# get a time stamp
#
timestamp() {
  date "+%Y-%m-%d %H:%M:%S"
}

#
# wait for the given string to appear in the given file
# params
#   #1 l_text: the string to wait for
#   #2 l_file: the file to monitor
#   #3 l_interval: the interval in seconds to watch
#   #4 l_limit: how many intervals to wait as a maximum
#
dowaitfor() {
  local l_text="$1"
  local l_file="$2"
  local l_interval="$3"
  local l_limit="$4"
  local l_rounds=0
  # wait 20x  longer in travis environment
  case $USER in
    travis) l_limit=$(($l_limit*20))
  esac
  while true ; do
    l_dowaitfor_found=$(grep "$l_text" -m 1 "$l_file")
    if [ $? -eq 0 ]
    then
      echo "$l_dowaitfor_found"
      break
    fi
    sleep $l_interval
    l_rounds=$(($l_rounds+1))
    if [ $l_rounds -ge $l_limit ]
    then
      echo "timeout"
      break
    fi
  done
}

#
# wait for the given string to appear in the given file
# params
#   #1 l_text: the string to wait for
#   #2 l_file: the file to monitor
#   #3 l_interval: the interval in seconds to watch
#   #4 l_limit: how many intervals to wait as a maximum
#
waitfor() {
  local l_text="$1"
  local l_file="$2"
  local l_interval="$3"
  local l_limit="$4"
  l_waitfor_found=$(dowaitfor  "$l_text" "$l_file" "$l_interval" "$l_limit")
  if [ "$l_waitfor_found" = "timeout" ]
  then
    error "❌ $l_text not found in $l_file after trying $l_limit times for $l_interval secs"
  else
    color_msg $green "✅ $l_text → $l_waitfor_found"
  fi
}

#
# extra wait under a certain condition
# e.g. if Reading package lists...
# shows in the log this means that apt-get update is running and installs will probably follow
# that take some time
#
#   #1 l_text_if: the string to wait for as an if condition
#   #2 l_msg: the message to show if the if condition is true
#   #3 l_text_until: the string to wait for until
#   #4 l_msg_until: the message to show if the if condition is false
#   #5 l_file: the file to monitor
#   #6 l_interval: the interval in seconds to watch
#   #7 l_limit_if: how many intervals to wait as a maximum for the if condition
#   #8 l_limit how many intervals to wait as a maximum for the until condition
waitif(){
  local l_text_if="$1"
  local l_msg="$2"
  local l_text_until="$3"
  local l_msg_until="$4"
  local l_file="$5"
  local l_interval="$6"
  local l_limit_if="$7"
  local l_limit="$8"
  l_waitif_found=$(dowaitfor "$l_text_if" "$l_file" "$l_interval" "$l_limit_if")
  if [ "$l_waitif_found" = "timeout" ]
  then
    color_msg $green "✅ $l_text_if not found"
    color_msg $green "$l_msg_until"
  else
    color_msg $green "✅ $l_text_if → $l_waitif_found"
    color_msg $green "$l_msg"
    waitfor "$l_text_until" "$l_file" "$l_interval" "$l_limit"
  fi
}

#
# execute the given command and wait for the result
#
# params
#   #1 l_cmd: the command to execute
#   #2 l_expected: the results to wait for (subshell to call to get the results limited by newline)
#   #3 l_interval: the interval in seconds to watch
#   #4 l_limit: how many intervals to wait as a maximum
#
waitfor_cmd() {
  local l_cmd="$1"
  local l_expected="$2"
  local l_interval="$3"
  local l_limit="$4"
  local l_waitforcmdlog=/tmp/waitforcmd$$
  color_msg $blue "waiting for  $l_cmd"
  nohup $l_cmd &> $l_waitforcmdlog&
  local l_cmd_pid=$!
  sleep 0.2
  tail -f $l_waitforcmdlog&
  local l_tail_pid=$!
  color_msg $blue "tail process $l_tail_pid started"
  IFS=$'\n'       # make newlines the only separator
  for l_expect in $($l_expected)
  do
    # check the log (just once)
    waitfor "$l_expect" $l_waitforcmdlog $l_interval $l_limit
  done
  unset IFS
  kill -1 $l_tail_pid
  kill -1 $l_cmd_pid
  rm $l_waitforcmdlog
}

#
# check that the given url contains the given text
#
webcheck() {
  local l_url="$1"
  local l_text="$2"
  local l_sed="sed"
  os=`uname`
  case $os in
    Darwin*)
     l_sed="gsed"
     ;;
  esac
  l_found=$(curl -s "$l_url" | $l_sed 's/<[^>]*>/\n/g' | grep "$l_text")
  if [ $? -ne 0 ]
  then
    error "❌ $l_text not found at $l_url"
  else
    echo "✅ $l_url • $l_text → $l_found"
  fi
}

#
# get the mediawiki version suffix
# params
#   #1: l_wiki - the wiki name e.g. mw1_27_5
mwversion_suffix() {
  local l_wiki="$1"
  local l_version=""

  case $l_wiki in
    mw1_27_5) l_version=127;;
    mw1_30_1) l_version=130;;
    mw1_31_1) l_version=131;;
  esac
  echo $l_version
}

#
# get the container name for the given wiki and service name
# params
#   #1: l_wiki - the wiki name e.g. mw1_27_5
#   #2: l_service the service name e.g. db
# returns
#   the container name e.g. profiwiki127
#
container_name() {
  local l_wiki="$1"
  local l_service="$2"
  local l_prefix="$IMAGE_PREFIX"
  local l_version=$(mwversion_suffix "$l_wiki")
  echo ${l_prefix}${l_version}_${l_service}_1
}

# get all  volumes
volumes() {
  local l_version="$1"
  docker volume ls | grep -v "VOLUME" | grep "${IMAGE_PREFIX}${l_version}" | cut -c21-
}

# cleanup the docker enviroment
#   #1: l_wiki - the wiki name e.g. mw1_27_5
clean() {
  local l_wiki="$1"
  local l_version=$(mwversion_suffix "$l_wiki")
  local l_name=${IMAGE_PREFIX}${l_version}
  color_msg $blue "cleaning docker environment - stopping and removing containers and removing volumes for ${l_name}"
  for container in $(docker ps -q --filter="name=${l_name}")
  do
    color_msg $blue "stopping container $container"
    docker stop $container
  done
  for container in  $(docker ps -aq --filter="name=${l_name}")
  do
    color_msg $blue "removing container $container"
    docker rm $container
  done
  for volume in $(volumes $l_version)
  do
    color_msg $blue "removing volume $volume"
    docker volume rm $volume
  done
}

#
# check that the ports are not in use
#
# params: the list of ports to be checked
check_ports() {
  for port in $@
  do
	  color_msg $blue "checking that port $port is not in use"
	  # even netstat is incompatible on MacOS
	  # https://stackoverflow.com/a/4421674/1497139
	  os=`uname`
    case $os in
      Darwin*)
			 sudo lsof -nP -i4TCP:$port | grep LISTEN
			;;
			*)
       netstat -nlpt |grep $port
			;;
		esac
    if [ $? -eq 0 ]
    then
      error "$port is in use - you might want to stop the process that use it or reconfigure"
    fi
  done
}

#
# list the needed parts to be checked
#
needed() {
cat << EOF
package curl already installed
package dialog already installed
package graphviz already installed
module iconv already installed
module curl already installed
module gd already installed
module mysql already installed
module openssl already installed
module mbstring already installed
module xml already installed
EOF
}

#
# expected output for profiwiki-install -i
#
installed() {
cat << EOF
no further installation asked for
EOF
}

#
# expected output for profiwiki-install -ismw
#
smw_installed() {
cat << EOF
finished installation of semantic mediawiki Version
EOF
}



# test the given installation
# param #1 start script for wiki installation
dotest_smwinstall() {
  local l_wiki="$1"
  local l_mw_container=$(container_name $l_wiki mw)
  color_msg $blue "testing $l_wiki"
  # run the install job in the background
  rm nohup.out
  # test with random password
  nohup ./$l_wiki -r&
  install_pid=$!
  # make sure the process has opened the log file
  sleep 0.2
  # show the progress
  tail -f nohup.out&
  tail_pid=$!
  timestamp
  color_msg $blue "started install with pid $install_pid log is shown with pid $tail_pid"
  waitif "Pulling from library/mediawiki" "Waiting until docker pull is finished" "Step 2/" "Docker download cached" nohup.out 0.2 25 1000
  timestamp
  # if docker has not cached the results yet wait
  waitif "Reading package lists..." "Waiting until docker build is finished" "Step 6/" "Docker build cached" nohup.out 0.2 25 1000
  timestamp
  # wait for e.g. chown to finish
  waitfor "Step 7/" nohup.out 0.2 1000
  # check that the Apache 2 Webserver is up
  # 30 times 0.2 secs = 6 secs timeout
  waitfor "apache2 -D FOREGROUND" nohup.out 0.2 30
  timestamp
  # check that the MariaDB is up
  # 50 times 0.2 secs = 10 secs timeout
  waitfor "port: 3306" nohup.out 0.2 50
  timestamp
  # check the current state of the website
  # no LocalSettings should be available at this time
  local l_mwsite="localhost:$mwport"
  webcheck $l_mwsite "LocalSettings.php not found"
  # check running containers
  docker ps
  # copy the phpinfo.php to the mediawiki docker container
  docker cp image/phpinfo.php ${l_mw_container}:/var/www/html
  # check the result - e.g. the PHP version should show
  webcheck $l_mwsite/phpinfo.php "PHP/7"
  # run the install needed check
  waitfor_cmd "docker exec  ${l_mw_container} /root/profiwiki-install.sh -n" needed 0.2 30
  # run the install.php script for automatic LocalSettings
  waitfor_cmd "docker exec  ${l_mw_container} /root/profiwiki-install.sh -i" installed 0.2 30
  # check that the main page appears
  #webcheck $l_mwsite "Login required"
  webcheck $l_mwsite/index.php/Main_Page "^Main Page$"
  webcheck $l_mwsite/index.php/Special:Version "Nuke"
  # run the composer install for semantic mediawiki
  waitfor_cmd "docker exec ${l_mw_container} /root/profiwiki-install.sh -ismw 3.0" smw_installed 0.2 800
  # check that SemanticMediaWiki is installed
  webcheck $l_mwsite/index.php/Special:Version "Semantic MediaWiki"
  # end the log output
  kill -1 $tail_pid
  # commit the container
  docker commit ${l_mw_container} bitplan/profiwiki_$l_wiki:0.0.1
}

# test the given installation
# param #1 start script for wiki installation
dotest() {
  local l_wiki="$1"
  mwport=$(grep MEDIAWIKI_PORT wiki-config.sh | cut -f2 -d"=")
  check_ports 3306 $mwport

  dotest_smwinstall "$l_wiki"
}

# test all wikis
testall() {
  # loop over the relevant wikis
  previouswiki=""
  for wiki in mw*
  do
    timestamp
    # we need to clean the wiki we just tested
    if [ "$previouswiki" != "" ]
    then
      clean "$previouswiki"
    fi
    # we need to clean the current wiki (if there are remnants from a previous call of this script)
    clean $l_wiki

    # we need to test the relevant wiki
    dotest $wiki
    previouswiki="$wiki"
    timestamp
  done
}

# stop the timing
timestamp
color_msg $blue "Testing for user $USER"
while test $# -gt 0
do
  case $1 in
    -a|--all)
      testall
    ;;

    -h|--help)
      usage
    ;;
    -c|--clean)
      if [ $# -lt 1 ]
      then
        usage
      fi
      wiki=$1
      clean $wiki
    ;;

    -m)
      shift
      if [ $# -lt 1 ]
      then
        usage
      fi
      wiki=$1
      dotest $wiki
      ;;
  esac
  shift
done
timestamp
