#!/usr/bin/env bash
# author: joe.zheng
# version: 24.04.02

set -e

# @self {
# AUTO-GENERATED, DO NOT EDIT!

SELF="$(basename $0)"
SELF_VERSION="$(sed -n '1,4s/# version: \(.*\)/\1/p' $0)"

# @self }
NAME="${SELF#*-}"
NAME_CTL="virtctl"

VERSION="0.58.0"      # version to deploy
RESET=n               # reset the deployment
UNINSTALL=n           # reset the deployment and uninstall components

REPO_URL="https://github.com/kubevirt/kubevirt"
VARIANT="$(uname -s | tr A-Z a-z)-$(uname -m | sed 's/x86_64/amd64/')"
INSTALL_DIR_BIN="/usr/local/bin"

TIMEOUT="300s"

CDI_VERSION="${CDI_VERSION:-1.56.0}"
CDI_REPO_URL="https://github.com/kubevirt/containerized-data-importer"

# @dry-run {
# AUTO-GENERATED, DO NOT EDIT!

DRY_RUN="${DRY_RUN:-n}"        # "y" to enable
RUN=''                         # command prefix for dry run
if [[ $DRY_RUN == 'y' ]]; then
  RUN='echo'
fi

# @dry-run }

function usage() {
  cat <<EOF
Usage: $SELF [-m <type>] [-v <ver>] [-R] [-U] [-n] [-h]

  Deploy KubeVirt in Kubernetes cluster the easy way

  -m <type>: use mirror [$MIRRORS], default: $MIRROR
  -v <ver>:  target version, default: $VERSION
  -R:        reset the deployment, default: $RESET
  -U:        reset the deployment and uninstall components, default: $UNINSTALL
  -n:        print information only, default: $DRY_RUN
  -h:        print the usage message

Cache:

  To speed up deployment, the required files will be cached at "$CACHE"

Environment variables:

  MIRROR_URL:  mirror to use, default: $MIRROR_URL
  OFFLINE:     force into offline mode [y n], default: $OFFLINE

Examples:

  1. Deploy KubeVirt
     $SELF

  2. Reset the deployment
     $SELF -R

  3. Deploy KubeVirt with specific version
     $SELF -v $VERSION

  4. Reset the deployment and uninstall
     $SELF -U

Version: $SELF_VERSION
EOF

}

function current_version() {
  local version=
  if [[ -n $(which $NAME_CTL) ]]; then
    version="$($NAME_CTL version | head -n1 | sed -n 's/.*GitVersion:"\([^"]*\).*/\1/p')"
    version=${version#v}
  fi
  echo $version
}

function main() {
  # adjust default version if already deployed
  CUR_VER="$(current_version)"
  VERSION="${CUR_VER:-$VERSION}"

  while getopts ":m:v:hnRU" opt
  do
    case $opt in
      m ) MIRROR=$OPTARG
          if echo $MIRRORS | grep -v -w $MIRROR >/dev/null 2>&1; then
            err "invalid mirror option $MIRROR"
            usage && exit 1
          fi
          ;;
      v ) VERSION=${OPTARG#v};;
      R ) RESET=y;;
      U ) RESET=y && UNINSTALL=y;;
      n ) DRY_RUN=y && RUN='echo';;
      h ) usage && exit;;
      * ) usage && echo "invalid option: -$OPTARG" && exit 1;;
    esac
  done
  shift $((OPTIND-1))

  for v in CACHE DRY_RUN MIRROR MIRROR_URL OFFLINE RESET UNINSTALL VERSION CUR_VER CDI_VERSION
  do
    eval echo "$v: \${$v}"
  done

  [[ $DRY_RUN == "y" ]] && exit

  validate_sudo
  check_prerequisites
  check_network
  check_mirror
  ensure_workdir

  local manifest_dir="$CACHE_MANIFESTS/kubevirt-$VERSION"
  local manifest_list="kubevirt-operator.yaml kubevirt-cr.yaml"
  local cdi_manifest_dir="$CACHE_MANIFESTS/cdi-$CDI_VERSION"
  local cdi_manifest_list="cdi-operator.yaml cdi-cr.yaml"
  local demo_manifest_dir="$cdi_manifest_dir/demo"
  local package_dir="$CACHE_PACKAGES/common/kubevirt-$VERSION"
  local package_list="$NAME_CTL"

  mkdir -p $manifest_dir $cdi_manifest_dir $demo_manifest_dir $package_dir

  if [[ $OFFLINE == "y" ]]; then
    msg "WARNING: offline mode, cache must be ready"
  else
    msg "download manifests"
    for n in $manifest_list; do
      local u="$REPO_URL/releases/download/v$VERSION/$n"
      download_file "$(mirror_url $u)" "$manifest_dir/$n"
    done
    for n in $cdi_manifest_list; do
      local u="$CDI_REPO_URL/releases/download/v$CDI_VERSION/$n"
      download_file "$(mirror_url $u)" "$cdi_manifest_dir/$n"
    done

    msg "download manifests for demo"
    for n in vm.yaml; do
      local u="https://kubevirt.io/labs/manifests/$n"
      download_file "$(mirror_url $u)" "$demo_manifest_dir/$n"
    done
    for n in registry-image-datavolume.yaml import-kubevirt-datavolume.yaml \
             import-block-pv.yaml clone-datavolume.yaml vm-dv.yaml; do
      local u="$CDI_REPO_URL/raw/v$CDI_VERSION/manifests/example/$n"
      download_file "$(mirror_url $u)" "$demo_manifest_dir/$n"
    done
    # use the default storage class
    sed -i '/storageClassName:/d' "$demo_manifest_dir/vm-dv.yaml"

    msg "download packages"
    for n in $package_list; do
      local u="$REPO_URL/releases/download/v$VERSION/$n-v$VERSION-$VARIANT"
      download_file "$(mirror_url $u)" "$package_dir/$n"
    done
  fi

  if [[ $RESET == "y" ]]; then
    # https://kubevirt.io/user-guide/operations/updating_and_deletion/#deleting-kubevirt
    local kd="kubectl delete --ignore-not-found"
    msg "delete kubevirt deployment (if any)"
    $kd -n kubevirt kubevirt kubevirt --wait=true || true
    $kd apiservices v1.subresources.kubevirt.io
    $kd mutatingwebhookconfigurations virt-api-mutator
    $kd validatingwebhookconfigurations virt-operator-validator
    $kd validatingwebhookconfigurations virt-api-validator
    $kd -f $manifest_dir/kubevirt-operator.yaml
    msg "delete cdi deployment (if any)"
    $kd cdi cdi --wait=true || true
    $kd -f $cdi_manifest_dir/
  else
    msg "deploy kubevirt"
    for n in $manifest_list; do
      local f=$manifest_dir/$n
      msg "apply $f"
      kubectl apply -f $f
    done
    msg "deploy cdi"
    for n in $cdi_manifest_list; do
      local f=$cdi_manifest_dir/$n
      msg "apply $f"
      kubectl apply -f $f
    done
  fi

  for n in $package_list; do
    if [[ $UNINSTALL == "y" ]]; then
      msg "uninstall $n"
      $SUDO rm -f $INSTALL_DIR_BIN/$n
    else
      msg "install $n"
      $SUDO install $package_dir/$n $INSTALL_DIR_BIN
    fi
  done

  if [[ $RESET != "y" ]]; then
    msg "wait until ready (timeout: $TIMEOUT)"
    kubectl -n kubevirt wait kv/kubevirt --timeout $TIMEOUT --for condition=Available

    cat <<EOF

# here are some tips to follow:
# configure
kubectl edit cdi/cdi

# deploy a sample VM
# see other samples in $demo_manifest_dir
kubectl apply -f $demo_manifest_dir/vm.yaml

# get the deployed VM
kubectl get vms

# start the VM
virtctl start testvm

# check instance status
kubectl get vmis

# access console
virtctl console testvm
EOF
  fi

  msg "done"
}

function check_prerequisites() {
  msg "check prerequisites"
  if [[ -z $(which kubelet) ]]; then
    err "kubelet is not available"
    exit 1
  fi
  if [[ -z $(which curl) ]]; then
    err "curl is not available"
    exit 1
  fi
  if ! kubectl version >/dev/null 2>&1; then
    err "can't access k8s cluster"
    exit 1
  fi
}

# @base {
# AUTO-GENERATED, DO NOT EDIT!

function msg {
  echo "> $@"
}

function err {
  echo "> $@" >&2
}

function has() {
  [[ -z "${1##*$2*}" ]] && [[ -z "$2" || -n "$1" ]]
}

# @base }

# @sudo {
# AUTO-GENERATED, DO NOT EDIT!

SUDO="sudo"
if [[ $(id -u) == "0" ]]; then
  SUDO=""
fi

function validate_sudo() {
  if [[ -n $SUDO ]]; then
    msg "validate sudo"
    sudo -v
  fi
}

# @sudo }

# @network {
# AUTO-GENERATED, DO NOT EDIT!

OFFLINE="${OFFLINE:-n}"                           # no external network
MIRROR="${MIRROR:-auto}"                          # mirror option to use
MIRRORS="yes no auto"                             # valid mirror options
USE_MIRROR="${USE_MIRROR:-n}"                     # need to use mirror
MIRROR_URL="${MIRROR_URL:-https://iffi.me/proxy}" # mirror or http proxy to use
URL_TO_CHECK="${URL_TO_CHECK:-https://raw.githubusercontent.com}"

# the target URL is reachable but the response may be 4xx or 5xx
function can_access() {
   curl -IL -s -m 5 $1 >/dev/null 2>&1
}

function check_network() {
  msg "check network"
  if [[ $OFFLINE != 'y' ]]; then
    if ! can_access example.com; then
      msg "can't access external network"
      OFFLINE="y"
    fi
  fi
  msg "offline mode: $OFFLINE"
}

function check_mirror() {
  msg "use mirror or not"

  local mirror;
  if [[ $MIRROR == "auto" ]]; then
    mirror="yes" # default as needed
    if [[ $OFFLINE == "y" ]]; then
      msg "offline mode, assume mirror is needed"
    else
      local target="$URL_TO_CHECK"
      msg "check whether we can access $target"
      if can_access $target; then
        msg "curl $target is OK"
        mirror="no"
      else
        msg "curl $target is FAILED"
      fi
    fi
  fi
  if [[ $MIRROR == "yes" || $mirror == "yes" ]]; then
    msg "mirror is needed"
    if can_access $MIRROR_URL; then
      USE_MIRROR="y"
    else
      err "failed to access mirror ($MIRROR_URL)"
    fi
  fi
  msg "use mirror: $USE_MIRROR"
}

function mirror_url() {
  local origin="${1:?argument missing}"

  if [[ $USE_MIRROR == "y" ]]; then
    echo -n "$MIRROR_URL/$origin"
  else
    echo -n "$origin"
  fi
}

# @network }

# @cache {
# AUTO-GENERATED, DO NOT EDIT!

CACHE="${CACHE:-cache}"                                    # the cache directory
CACHE_RUN="${CACHE_RUN:-$CACHE/run/$SELF}"                 # runtime generated files
CACHE_IMAGES="${CACHE_IMAGES:-$CACHE/images}"              # exported container images
CACHE_PACKAGES="${CACHE_PACKAGES:-$CACHE/packages}"        # software packages, e.g. .deb, .tar.gz
CACHE_MANIFESTS="${CACHE_MANIFESTS:-$CACHE/manifests}"     # manifest files in YAML format
CACHE_DOWNLOADING="${CACHE_DOWNLOADING:-$CACHE/.download}" # temporary directory for downloading

function ensure_workdir() {
  msg "ensure workdir"

  for d in $CACHE_RUN $CACHE_IMAGES $CACHE_PACKAGES $CACHE_MANIFESTS $CACHE_DOWNLOADING; do
    if [[ ! -d $d ]]; then
      msg "create dir: $d"
      mkdir -p $d
    fi
  done
}

function download_file() {
  local src="${1:?missing source url}"
  local dst="${2:?missing destination}"

  if [[ -f "$dst" ]]; then
    msg "already exist: $dst"
  else
    msg "download: $src"
    local tmp="$CACHE_DOWNLOADING/$(basename $dst)"
    $RUN curl -fsSL $src > $tmp
    $RUN mkdir -p $(dirname $dst) && mv $tmp $dst
    msg "saved at: $dst"
  fi
}

# @cache }

main "$@"
