#!/bin/bash

# Copyright 2021 Kinvolk GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eu

IMAGE=${RACKER_IMAGE:-"quay.io/kinvolk/racker"}
VERSION=$(cat /opt/racker/RACKER_VERSION 2> /dev/null || true)
if [ "${VERSION}" = "" ]; then
  VERSION="latest"
fi

run_racker() {
  local VERSION=${2:-"latest"}
  local IMAGE="$1:$VERSION"

  echo "Running $IMAGE"

  sudo docker run --rm --privileged --pid host "$IMAGE"
}

pull_image() {
  local VERSION=${2:-"latest"}
  local IMAGE="$1:$VERSION"

  echo "Getting $IMAGE"

  sudo docker pull "$IMAGE"
}

pull_n_run_racker() {
  pull_image $1 $2
  run_racker $1 $2
}

is_help_arg() {
  if [ "$1" = "-h" ] || [ "$1" = "-help" ]; then
    return 0
  fi
  return 1
}

usage() {
  args=(
    "bootstrap [-provision (flatcar|lokomotive)] [-h] ... : guided setup for Lokomotive Kubernetes or individual Flatcar Container Linux instances managed with Ignition"
    "status [-full]: show a summary of the cluster (use --full to print the details for all servers)"
    "update: install updates for the current racker version"
    "upgrade: migrate to a newer racker version"
    "get VERSION: install a particular racker version"
    "factory: preparation procedure to configure the hardware"
    "version: print racker version"
    "help: show usage"
  )

  echo "Usage: $0 COMMAND"
  echo "Commands:"
  for i in "${args[@]}"; do
    IFS=':' read arg desc <<< "${i}"
    printf " %-20s: " "$arg"
    echo "${desc}"
  done
  exit 1
}

if [ $# -lt 1 ] || [ "$1" = "" ] || [ "$1" = "help" ]; then
    usage
fi

if [ "$1" = update ]; then
  pull_n_run_racker $IMAGE $VERSION
elif [ "$1" = upgrade ]; then
  pull_n_run_racker $IMAGE "latest"
  VERSION=$(cat /opt/racker/RACKER_VERSION 2> /dev/null || true)
  if [ "$VERSION" = "" ]; then
    echo "Could not detect new version to update the local image cache"
    exit 1
  fi
  pull_image $IMAGE $VERSION
elif [ "$1" = get ]; then
  VERSION=${2:-$VERSION}
  pull_n_run_racker $IMAGE $VERSION
elif [ "$1" = version ]; then
  echo $VERSION
elif [ "$1" = factory ]; then
  shift
  /opt/racker/bootstrap/racker-factory.sh "$@"
elif [ "$1" = status ]; then
  if [ "${2:-}" = "-full" ] || [ "${2:-}" = "--full" ]; then
    STATUS_ARG="--full"
  fi
  /opt/racker/bootstrap/status.sh ${STATUS_ARG:-}
elif [ "$1" = bootstrap ]; then
  if [ ! -f /opt/racker/RACKER_VERSION ]; then
    echo "Initial run, updating to the latest version"
    pull_n_run_racker $IMAGE $VERSION
    exec sh -c "racker bootstrap"
  fi

  # Discard the bootstrap as first arg
  shift

  node_list="$(tail -n +2 /usr/share/oem/nodes.csv | grep -v -f <(cat /sys/class/net/*/address))"
  # Find the list of available node types, excluding the management node
  node_types_list=$(echo "${node_list}" | cut -d , -f 4 | sed 's/ //g' | sort | uniq)
  # Format it as YAML entries, adding a "  - " prefix, and, for sed, a "\n" string as newline directive (but only between the elements)
  node_types=$(echo -n "${node_types_list}" | sed 's/^/  - /' | sed 's/$/\\/g' | tr '\n' n | sed 's/\\$//g')
  sec_mac_addrs=$(tail -n +2 /usr/share/oem/nodes.csv | grep -v -f <(cat /sys/class/net/*/address) | cut -d , -f 3 | sed 's/ //g')
  prefill_unquoted=$(cat /opt/racker/bootstrap/prefill.template; for mac in $sec_mac_addrs; do printf "[$mac]\nip_addr =\ngateway =\ndns =\n\n" ; done)
  prefill=$(echo -n "${prefill_unquoted}" | sed 's/^/    /' | sed 's/$/\\/g' | tr '\n' n | sed 's/\\$//g')
  # Ask for the configuration and save it in a temp file
  ARGS_FILE="$(mktemp)"

  if is_help_arg "${1:-}"; then
    args-wizard -show-help -config /opt/racker/bootstrap/provision-type.yaml
    exit 0
  fi

  args-wizard -config /opt/racker/bootstrap/provision-type.yaml > $ARGS_FILE -- "$@"

  # Exit on -h
  if [ "$(wc -l $ARGS_FILE | cut -d " " -f 1)" = "0" ]; then
    exit 0
  fi
  # Load the variables into context
  set -a
  . $ARGS_FILE
  set +a

  # We need to check here if the help argument has been used because
  # we need to ignore the cluster directory checks depending on it.
  using_help_arg=false
  for i in "$@"; do
    if is_help_arg "$i"; then
      using_help_arg=true
      break
    fi
  done

  if ! $using_help_arg; then
    # any existing directory needs to be removed first (and both directories can't coexist)
    LOKOMOTIVE_DIR="$HOME/lokomotive"
    FLATCAR_DIR="$HOME/flatcar-container-linux"
    # gracefully remove empty directories, otherwise guide the user
    if [ -d "${LOKOMOTIVE_DIR}" ]; then
      rmdir "${LOKOMOTIVE_DIR}" || { echo "A Lokomotive configuration already exists."
      echo "To modify the settings you can directly change the lokomotive/baremetal.lokocfg config file or the CLC snippet files lokomotive/cl/*yaml and run:"
      echo "  cd lokomotive; lokoctl cluster|component apply"
      echo "Only if you want to bootstrap a new cluster, remove the ${LOKOMOTIVE_DIR} folder first and then run racker bootstrap."
      exit 1 ; }
    fi
    if [ -d "${FLATCAR_DIR}" ]; then
      rmdir "${FLATCAR_DIR}" || { echo "A Flatcar configuration already exists."
      echo "To modify the settings you can directly change the flatcar-container-linux/flatcar.tf config file or the CLC snippet files flatcar-container-linux/cl/*yaml and run:"
      echo "  cd flatcar-container-linux; terraform apply"
      echo "Only if you want to bootstrap a new cluster, remove the ${FLATCAR_DIR} folder first and then run racker bootstrap."
      exit 1 ; }
    fi
    # after ensuring that there will only be a single (new, empty) directory, create it
    if [ "$PROVISION_TYPE" == "lokomotive" ]; then
      mkdir "${LOKOMOTIVE_DIR}"
      cd "${LOKOMOTIVE_DIR}"
    elif [ "$PROVISION_TYPE" == "flatcar" ]; then
      mkdir "${FLATCAR_DIR}"
      cd "${FLATCAR_DIR}"
    else
      echo "Unknown provision type value: $PROVISION_TYPE"
      exit 1
    fi
  fi

  provision_args_file=""
  if [ "$PROVISION_TYPE" == "lokomotive" ]; then
    provision_args_file="/opt/racker/bootstrap/args.yaml"
  elif [ "$PROVISION_TYPE" == "flatcar" ]; then
    provision_args_file="/opt/racker/bootstrap/flatcar/args.yaml"
  else
    echo "Unknown provision type value: $PROVISION_TYPE"
    exit 1
  fi

  # Ensure we ask about type of cluster (HA or not) only if we have enough
  # nodes for it.
  ha_node_types=""
  default_num_controllers=1
  for node in ${node_types_list[@]}; do
    num_nodes=$(echo "${node_list}" | grep "[ ,]$node"|wc -l)
    # If there are at least 3 nodes of this node type, then set the
    # next prompt as the one that asks for the controller amount
    # (otherwise the controller amount prompt is skipped by default).
    if [ $num_nodes -gt 2 ]; then
      ha_node_types+="  - ${node}
"
    fi
  done
  first_cluster_type_prompt="num-controllers"
  if [ ! -n "${ha_node_types}" ]; then
    # We have no HA nodes available, so let's not prompt about that and skip
    # directly into the simple nodes choosing
    first_cluster_type_prompt="controller-type"
  else
    ha_node_types=$(echo -n "${ha_node_types}"|sed 's/$/\\/g' | tr '\n' n | sed 's/\\$//g')
    default_num_controllers=3
  fi

  EDITOR_TMP=/tmp/racker
  mkdir -p $EDITOR_TMP

  TMPDIR=$EDITOR_TMP EDITOR="docker run --rm -it -v $TMPDIR:$TMPDIR:Z $IMAGE:$VERSION nano" args-wizard -config <(sed -e 's@${default_num_controllers}@'"${default_num_controllers}@g" -e 's@${first_cluster_type_prompt}@'"${first_cluster_type_prompt}@g" -e 's@    ${prefill}@'"${prefill}@g" -e 's@  - ${ha_node_types}@'"${ha_node_types}@g" -e 's@  - ${node_types}@'"${node_types}@g" $provision_args_file) > $ARGS_FILE -- "$@"

  # Exit on -h
  if [ "$(wc -l $ARGS_FILE | cut -d " " -f 1)" = "0" ]; then
    exit 0
  fi
  # Load the variables into context
  set -a
  . $ARGS_FILE
  set +a

  LOCKSMITH_ENABLED=$(systemctl is-active --quiet locksmithd && echo true || echo false)
  if $LOCKSMITH_ENABLED; then
    # Stop locksmith to prevent a possible reboot while bootstrapping
    sudo systemctl stop locksmithd
  fi
  ret=0
  USE_QEMU=0 /opt/racker/bootstrap/prepare.sh create || ret=$?

  # Start locksmith again, if it was enabled before
  if $LOCKSMITH_ENABLED; then
    sudo systemctl start locksmithd || true
  fi

  exit $ret
else
  echo "Error: Unrecognized option \"$1\"" >&2
  echo
  usage
fi
