#!/bin/bash
#
# Copyright (c) 2020 Red Hat, Inc.
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
#   Red Hat, Inc. - initial API and implementation
#
# Scripts to prepare OLM(operator lifecycle manager) and install che-operator package
# with specific version using OLM.

BASE_DIR=$(dirname $(readlink -f "${BASH_SOURCE[0]}"))
ROOT_DIR=$(dirname "${BASE_DIR}")

source ${ROOT_DIR}/olm/check-yq.sh

function getPackageName() {
  platform="${1}"
  if [ -z "${1}" ]; then
      echo "[ERROR] Please specify first argument: 'platform'"
      exit 1
  fi

  echo "eclipse-che-preview-${platform}"
}

function getBundlePath() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  channel="${2}"
  if [ -z "${channel}" ]; then
    echo "[ERROR] Please specify second argument: 'channel'"
    exit 1
  fi

  echo "${ROOT_DIR}/bundle/${channel}/$(getPackageName "${platform}")"
}

createCatalogSource() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  CATALOG_IMAGENAME="${3}"
  if [ -z "${CATALOG_IMAGENAME}" ]; then
    echo "[ERROR] Please specify third argument: 'catalog image'"
    exit 1
  fi
  packageName=$(getPackageName "${platform}")

  kubectl apply -f - <<EOF
apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata:
  name: ${packageName}
  namespace: ${namespace}
spec:
  sourceType: grpc
  image: ${CATALOG_IMAGENAME}
  updateStrategy:
    registryPoll:
      interval: 5m
EOF
}

# Create catalog source to communicate with OLM using google rpc protocol.
createRpcCatalogSource() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  indexIP="${3}"
  if [ -z "${indexIP}" ]; then
    echo "[ERROR] Please specify third argument: 'index IP'"
    exit 1
  fi

  packageName=$(getPackageName "${platform}")

cat <<EOF | oc apply -n "${namespace}" -f - || return $?
apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata:
  name: ${packageName}
spec:
  address: "${indexIP}:50051"
  displayName: "Serverless Operator"
  publisher: Red Hat
  sourceType: grpc
EOF
}

buildBundleImage() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  CATALOG_BUNDLE_IMAGE_NAME_LOCAL="${2}"
  if [ -z "${CATALOG_BUNDLE_IMAGE_NAME_LOCAL}" ]; then
    echo "[ERROR] Please specify second argument: 'opm bundle'"
    exit 1
  fi
  channel="${3}"
  if [ -z "${channel}" ]; then
    echo "[ERROR] Please specify third argument: 'channel'"
    exit 1
  fi
  imageTool="${4}"
  if [ -z "${imageTool}" ]; then
    echo "[ERROR] Please specify fourth argument: 'image tool'"
    exit 1
  fi

  echo "[INFO] build bundle image"

  pushd "${ROOT_DIR}" || exit

  make bundle-build bundle-push DEFAULT_CHANNEL="${channel}" BUNDLE_IMG="${CATALOG_BUNDLE_IMAGE_NAME_LOCAL}" platform="${platform}" IMAGE_TOOL="${imageTool}"
  popd || exit
}

# Build catalog source image with index based on bundle image.
buildCatalogImage() {
  CATALOG_IMAGENAME="${1}"
  if [ -z "${CATALOG_IMAGENAME}" ]; then
    echo "[ERROR] Please specify first argument: 'catalog image'"
    exit 1
  fi

  CATALOG_BUNDLE_IMAGE_NAME_LOCAL="${2}"
  if [ -z "${CATALOG_BUNDLE_IMAGE_NAME_LOCAL}" ]; then
    echo "[ERROR] Please specify second argument: 'opm bundle image'"
    exit 1
  fi

  imageTool="${3}"
  if [ -z "${imageTool}" ]; then
    echo "[ERROR] Please specify third argument: 'image tool'"
    exit 1
  fi

  forceBuildAndPush="${4}"
  if [ -z "${forceBuildAndPush}" ]; then
    echo "[ERROR] Please specify fourth argument: 'force build and push: true or false'"
    exit 1
  fi

  # optional argument
  FROM_INDEX=${5:-""}
  BUILD_INDEX_IMAGE_ARG=""
  if [ ! "${FROM_INDEX}" == "" ]; then
    BUILD_INDEX_IMAGE_ARG=" --from-index ${FROM_INDEX}"
  fi

  SKIP_TLS_ARG=""
  SKIP_TLS_VERIFY=""
  if [ "${imageTool}" == "podman" ]; then
    SKIP_TLS_ARG=" --skip-tls"
    SKIP_TLS_VERIFY=" --tls-verify=false"
  fi

  pushd "${ROOT_DIR}" || exit

  INDEX_ADD_CMD="make catalog-build \
    CATALOG_IMG=\"${CATALOG_IMAGENAME}\" \
    BUNDLE_IMG=\"${CATALOG_BUNDLE_IMAGE_NAME_LOCAL}\" \
    IMAGE_TOOL=\"${imageTool}\" \
    FROM_INDEX_OPT=\"${BUILD_INDEX_IMAGE_ARG}\""

  exitCode=0
  # Execute command and store an error output to the variable for following handling.
  {
    output="$(eval "${INDEX_ADD_CMD}" 2>&1 1>&3 3>&-)"; } 3>&1 || \
    {
      exitCode="$?";
      echo "[INFO] ${exitCode}";
      true;
    }
    echo "${output}"
  if [[ "${output}" == *"already exists, Bundle already added that provides package and csv"* ]] && [[ "${forceBuildAndPush}" == "true" ]]; then
    echo "[INFO] Ignore error 'Bundle already added'"
    # Catalog bundle image contains bundle reference, continue without unnecessary push operation
    return
  else
    echo "[INFO] ${exitCode}"
    if [ "${exitCode}" != 0 ]; then
      exit "${exitCode}"
    fi
  fi

  make catalog-push CATALOG_IMG="${CATALOG_IMAGENAME}"

  popd || exit
}

# HACK. Unfortunately catalog source image bundle job has image pull policy "IfNotPresent".
# It makes troubles for test scripts, because image bundle could be outdated with
# such pull policy. That's why we launch job to fource image bundle pulling before Che installation.
forcePullingOlmImages() {
  namespace="${1}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify first argument: 'namespace'"
    exit 1
  fi
  CATALOG_BUNDLE_IMAGE_NAME_LOCAL="${2}"
  if [ -z "${CATALOG_BUNDLE_IMAGE_NAME_LOCAL}" ]; then
    echo "[ERROR] Please specify second argument: opm bundle image"
    exit 1
  fi

  yq -r "(.spec.template.spec.containers[0].image) = \"${CATALOG_BUNDLE_IMAGE_NAME_LOCAL}\"" "${BASE_DIR}/force-pulling-olm-images-job.yaml" | kubectl apply -f - -n "${namespace}"

  kubectl wait --for=condition=complete --timeout=30s job/force-pulling-olm-images-job -n "${namespace}"

  kubectl delete job/force-pulling-olm-images-job -n "${namespace}"
}

installOPM() {
  OPM_BINARY=$(command -v opm) || true
  if [[ ! -x $OPM_BINARY ]]; then
    OPM_TEMP_DIR="$(mktemp -q -d -t "OPM_XXXXXX" 2>/dev/null || mktemp -q -d)"
    pushd "${OPM_TEMP_DIR}" || exit

    echo "[INFO] Downloading 'opm' cli tool..."
    curl -sLo opm "$(curl -sL https://api.github.com/repos/operator-framework/operator-registry/releases/33432389 | jq -r '[.assets[] | select(.name == "linux-amd64-opm")] | first | .browser_download_url')"
    export OPM_BINARY="${OPM_TEMP_DIR}/opm"
    chmod +x "${OPM_BINARY}"
    echo "[INFO] Downloading completed!"
    echo "[INFO] 'opm' binary path: ${OPM_BINARY}"
    popd || exit
  fi
}

createNamespace() {
  namespace="${1}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify first argument: 'namespace'"
    exit 1
  fi

  kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: ${namespace}
EOF
}

installOperatorMarketPlace() {
  echo "[INFO] Installing test pre-requisistes"
  IFS=$'\n' read -d '' -r -a olmApiGroups < <( kubectl api-resources --api-group=operators.coreos.com -o name ) || true
  if [ -z "${olmApiGroups[*]}" ]; then
      OLM_VERSION=v0.17.0
      curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${OLM_VERSION}/install.sh | bash -s ${OLM_VERSION}
  fi
}

installCatalogSource() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  CATALOG_IMAGENAME=${3}
  if [ -z "${CATALOG_IMAGENAME}" ]; then
    echo "[ERROR] Please specify third argument: 'catalog image'"
    exit 1
  fi
  packageName=$(getPackageName "${platform}")

  createCatalogSource "${platform}" "${namespace}" "${CATALOG_IMAGENAME}"

  i=0
  while [ $i -le 240 ]
  do
    if kubectl get catalogsource/"${packageName}" -n "${namespace}"  >/dev/null 2>&1
    then
      break
    fi
    sleep 1
    ((i++))
  done

  if [ $i -gt 240 ]
  then
    echo "[ERROR] Catalog source not created after 4 minutes"
    exit 1
  fi
}

subscribeToInstallation() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  channel="${3}"
  if [ -z "${channel}" ]; then
    echo "[ERROR] Please specify third argument: 'channel'"
    exit 1
  fi

  # fourth argument is an optional
  CSV_NAME="${4-${CSV_NAME}}"
  if [ -n "${CSV_NAME}" ]; then
    echo "[INFO] Subscribing to the version: '${CSV_NAME}'"
  else
    echo "[INFO] Subscribing to latest version for channel: '${channel}'"
  fi

  packageName=$(getPackageName "${platform}")

  kubectl apply -f - <<EOF
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: operatorgroup
  namespace: ${namespace}
spec:
  targetNamespaces:
  - ${namespace}
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: ${packageName}
  namespace: ${namespace}
spec:
  channel: ${channel}
  installPlanApproval: Manual
  name: ${packageName}
  source: ${packageName}
  sourceNamespace: ${namespace}
  startingCSV: ${CSV_NAME}
EOF

  kubectl describe subscription/"${packageName}" -n "${namespace}"

  kubectl wait subscription/"${packageName}" -n "${namespace}" --for=condition=InstallPlanPending --timeout=240s
  if [ $? -ne 0 ]
  then
    echo "[ERROR] Subscription failed to install the operator"
    exit 1
  fi

  kubectl describe subscription/"${packageName}" -n "${namespace}"
}

installPackage() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  packageName=$(getPackageName "${platform}")

  echo "[INFO] Install operator package ${packageName} into namespace ${namespace}"
  installPlan=$(kubectl get subscription/"${packageName}" -n "${namespace}" -o jsonpath='{.status.installplan.name}')

  kubectl patch installplan/"${installPlan}" -n "${namespace}" --type=merge -p '{"spec":{"approved":true}}'

  kubectl wait installplan/"${installPlan}" -n "${namespace}" --for=condition=Installed --timeout=240s
  if [ $? -ne 0 ]
  then
    echo InstallPlan failed to install the operator
    exit 1
  fi
}

applyCRCheCluster() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  CSV_FILE="${3}"
  if [ -z "${CSV_FILE}" ]; then
    echo "[ERROR] Please specify third argument: 'CSV_FILE'"
    exit 1
  fi

  echo "[INFO] Creating Custom Resource"
  CRs=$(yq -r '.metadata.annotations["alm-examples"]' "${CSV_FILE}")
  CR=$(echo "$CRs" | yq -r ".[0]")
  CR=$(echo "$CR" | yq -r ".spec.devWorkspace.enable = ${DEV_WORKSPACE_ENABLE:-false}")
  CR=$(echo "$CR" | yq -r ".spec.server.serverExposureStrategy = \"${CHE_EXPOSURE_STRATEGY:-multi-host}\"")
  CR=$(echo "$CR" | yq -r ".spec.server.imagePuller.enable = ${IMAGE_PULLER_ENABLE:-false}")
  if [ "${platform}" == "kubernetes" ]
  then
    CR=$(echo "$CR" | yq -r ".spec.k8s.ingressDomain = \"$(minikube ip).nip.io\"")
  fi

  echo "$CR" | kubectl apply -n "${namespace}" --validate=false -f -
}

waitCheServerDeploy() {
  namespace="${1}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify first argument: 'namespace'"
    exit 1
  fi

  echo "[INFO] Waiting for Che server to be deployed"
  set +e -x

  i=0
  while [[ $i -le 480 ]]
  do
    status=$(kubectl get checluster/eclipse-che -n "${namespace}" -o jsonpath={.status.cheClusterRunning})
    kubectl get pods -n "${namespace}"
    if [ "${status:-UNAVAILABLE}" == "Available" ]
    then
      break
    fi
    sleep 10
    ((i++))
  done

  if [ $i -gt 480 ]
  then
    echo "[ERROR] Che server did't start after 8 minutes"
    exit 1
  fi
}

waitCatalogSourcePod() {
  CURRENT_TIME=$(date +%s)
  ENDTIME=$(($CURRENT_TIME + 300))
  CATALOG_POD=$(kubectl get pods -n "${namespace}" -o yaml | yq -r ".items[] | select(.metadata.name | startswith(\"${packageName}\")) | .metadata.name")
  while [ $(date +%s) -lt $ENDTIME ]; do
    if [[ -z "$CATALOG_POD" ]]
    then
        CATALOG_POD=$(kubectl get pods -n "${namespace}" -o yaml | yq -r ".items[] | select(.metadata.name | startswith(\"${packageName}\")) | .metadata.name")
        sleep 10
    else
        kubectl wait --for=condition=ready pod/"${CATALOG_POD}" -n "${namespace}" --timeout=180s
        break
    fi
  done
}

getBundleListFromCatalogSource() {
  platform="${1}"
  if [ -z "${platform}" ]; then
    echo "[ERROR] Please specify first argument: 'platform'"
    exit 1
  fi
  namespace="${2}"
  if [ -z "${namespace}" ]; then
    echo "[ERROR] Please specify second argument: 'namespace'"
    exit 1
  fi
  packageName=$(getPackageName "${platform}")
  # Wait until catalog pod is created in cluster
  waitCatalogSourcePod

  CATALOG_SERVICE=$(kubectl get service "${packageName}" -n "${namespace}" -o yaml)
  CATALOG_IP=$(echo "${CATALOG_SERVICE}" | yq -r ".spec.clusterIP")
  CATALOG_PORT=$(echo "${CATALOG_SERVICE}" | yq -r ".spec.ports[0].targetPort")

  LIST_BUNDLES=$(kubectl run grpcurl-query -n "${namespace}" \
  --rm=true \
  --restart=Never \
  --attach=true \
  --image=docker.io/fullstorydev/grpcurl:v1.7.0 \
  --  -plaintext "${CATALOG_IP}:${CATALOG_PORT}" api.Registry.ListBundles
  )

  LIST_BUNDLES=$(echo "${LIST_BUNDLES}" | head -n -1)

  echo "${LIST_BUNDLES}"
}

getPreviousCSVInfo() {
  channel="${1}"
  if [ -z "${channel}" ]; then
    echo "[ERROR] Please specify first argument: 'channel'"
    exit 1
  fi

  previousBundle=$(echo "${LIST_BUNDLES}" | jq -s '.' | jq ". | map(. | select(.channelName == \"${channel}\"))" | yq -r '. |=sort_by(.csvName) | .[length - 2]')
  PREVIOUS_CSV_NAME=$(echo "${previousBundle}" | yq -r ".csvName")
  if [ "${PREVIOUS_CSV_NAME}" == "null" ]; then
    echo "[ERROR] Catalog source image hasn't got previous bundle."
    exit 1
  fi
  export PREVIOUS_CSV_NAME
  PREVIOUS_CSV_BUNDLE_IMAGE=$(echo "${previousBundle}" | yq -r ".bundlePath")
  export PREVIOUS_CSV_BUNDLE_IMAGE
}

getLatestCSVInfo() {
  channel="${1}"
  if [ -z "${channel}" ]; then
    echo "[ERROR] Please specify first argument: 'channel'"
    exit 1
  fi

  latestBundle=$(echo "${LIST_BUNDLES}" | jq -s '.' | jq ". | map(. | select(.channelName == \"${channel}\"))" | yq -r '. |=sort_by(.csvName) | .[length - 1]')
  LATEST_CSV_NAME=$(echo "${latestBundle}" | yq -r ".csvName")
  export LATEST_CSV_NAME
  LATEST_CSV_BUNDLE_IMAGE=$(echo "${latestBundle}" | yq -r ".bundlePath")
  export LATEST_CSV_BUNDLE_IMAGE
}
