#!/bin/bash

# Copyright UiPath 2021
#
# =================
# LICENSE AGREEMENT
# -----------------
#   Use of paid UiPath products and services is subject to the licensing agreement
#   executed between you and UiPath. Unless otherwise indicated by UiPath, use of free
#   UiPath products is subject to the associated licensing agreement available here:
#   https://www.uipath.com/legal/trust-and-security/legal-terms (or successor website).
#   You must not use this file separately from the product it is a part of or is associated with.

YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m'
CYAN='\033[0;36m'
GREEN='\033[0;32m'

function error() {
  echo -e "${RED}[ERROR][$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*${NC}\n" >&2
  exit 1
}

function info() {
  echo -e "${CYAN}[INFO] [$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*${NC}"
}

function warn() {
  echo -e "${YELLOW}[WARN] [$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*${NC}" >&2
}

function success() {
  echo -e "${GREEN}[SUCCESS] [$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*${NC}" >&2
}

function error_without_exit() {
  echo -e "${RED}[ERROR][$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*${NC}" >&2
}

function check_running_as_root() {
  if [ "$EUID" -ne 0 ]; then
    error "Please run script as root"
  fi
}

function display_usage() {
  echo
  echo "***************************************************************************************"
  echo
  echo "Utility to configure the disk for UiPath Automation Suite Installation."
  echo "Run this script to configure the disks on new machine or to extend the size of datadisk"
  echo
  echo "Arguments"
  echo "  -c|--cluster-disk-name          Device to host rancher and  kubelet. Ex: /dev/sdb."
  echo "  -e|--etcd-disk-name             Device to host etcd, Not required for agent node. Ex: /dev/sdb."
  echo "  -R|--ceph-raw-disk-name         Device to host ceph OSD, Not required for agent node. Ex: /dev/sdm"
  echo "  -v|--volume                     Device to host local PV, Not required for agent node. Ex: /dev/sdc."
  echo "  --monitoring                    Configure logical volume and mount point for monitoring stack."
  echo "                                    Used in conjunction with -v|--volume"
  echo "  --objectstore                   Configure logical volume and mount point for in-cluster objectstore: ceph."
  echo "                                    Used in conjunction with -v|--volume"
  echo "  --docker-registry               Configure logical volume and mount point for in-cluster OCI registry."
  echo "                                    Used in conjunction with -v|--volume"
  echo "  --insights                      Configure logical volume and mount point for Insights"
  echo "                                    Used in conjunction with -v|--volume"
  echo "  --robot-package-cache           Configure logical volume and mount point for robot package cache for non HA setup"
  echo "                                    Used in conjunction with -v|--volume"
  echo "  -b|--bundle-disk-name           Device to host the uipath bundle. "
  echo "                                    Only required for offline installation on 1st server node. "
  echo "  -s|--aicenter-disk-name         Device partition to host local volume provisioner. Ex: /dev/sdc1 "
  echo "                                    Only required for aicenter workloads on all nodes. "
  echo "  -se|--aicenter-storage-extend   Volume group extension for local volume provisioner. Ex: /dev/sdc2 "
  echo "                                    Only required for aicenter workloads on all nodes. "
  echo "  -k|--robot-package-disk-name    Device to host robot package cache folder"
  echo "                                    Only required for Automation Suite Robots dedicated node when 'packagecaching' is enabled"
  echo "  -P|--robot-package-path         Path to robot package cache folder"
  echo "                                    (defaults to '/uipath_asrobots_package_cache' if not set)"
  echo "  -r|--resize                     Resize logical volume to provided size in GB. If value is not provided,"
  echo "                                    LV will be resized to consume all free space in the VG"
  echo "  -d|--debug                      Run in debug."
  echo "  -h|--help                       Display help."
  echo
  echo "ExampleUsage:"
  echo
  echo "# Configure disk for server node with in-cluster objectstore ceph"
  echo "  $(basename "$0") --cluster-disk-name /dev/sdb --etcd-disk-name /dev/sdc \\"
  echo "    --volume /dev/sdd --objectstore"
  echo
  echo "# Resize file system used by monitoring stack to 60GB"
  echo "  $(basename "$0") --resize 60 --monitoring"
  echo
  echo "# Resize file system used by monitoring stack to all the space availbe in the datavg"
  echo "  $(basename "$0") --resize --monitoring"
  echo
  echo "***************************************************************************************"
  echo
}

UIPATH_VG="uipathvg"
DATADISK_VG="uipathdatavg"
ETCD_VG="uipathetcdvg"
BUNDLE_VG="uipathbundlevg"
ROBOT_PACKAGES_VG="robotpackagevg"
LOCAL_PV_VG="uipathstorevg"

RANCHER_LV="rancherlv"
KUBELET_LV="kubeletlv"
INSTALLER_LV="installerlv"
DATADISK_LV="longhornlv"
CEPH_MON_LV="objectstorelv"
PROMETHEUS_LV="monitoringlv"
REGISTRY_LV=registrylv
INSIGHTS_LV=insightslv
ETCD_LV="etcdlv"
BUNDLE_LV="bundlelv"
ROBOT_PACKAGES_LV="robotpackagelv"


RANCHER_LV_SIZE=${RANCHER_LV_SIZE:-"190"}
KUBELET_LV_SIZE=${KUBELET_LV_SIZE:-"56"}
INSTALLER_LV_SIZE=${INSTALLER_LV_SIZE:-"10"}
LOCAL_PV_LV_SIZE=${LOCAL_PV_LV_SIZE:-"256"}
PROMETHEUS_LV_SIZE_GB="${PROMETHEUS_LV_SIZE_GB:-50}"
CEPH_MON_LV_SIZE_GB="${CEPH_MON_LV_SIZE_GB:-10}"
REGISTRY_LV_SIZE_GB="${REGISTRY_LV_SIZE_GB:-200}"
INSIGHTS_LV_SIZE_GB="${INSIGHTS_LV_SIZE_GB:-15}"

CONFIGURE_PROMETHEUS_LV=0
CONFIGURE_OBJECTSTORE_LV=0
CONFIGURE_REGISTRY_LV=0
CONFIGURE_INSIGHTS_LV=0
CONFIGURE_ROBOT_PACKAGE_CACHE_LV=0
LV_EXTEND_ARGS=(-r)
RESIZE=0
CLUSTER_DISK_SIZE=${CLUSTER_DISK_SIZE:-"$((RANCHER_LV_SIZE + KUBELET_LV_SIZE + INSTALLER_LV_SIZE))"}
BASIC_SUITE_DATADISK_SIZE=${BASIC_SUITE_DATADISK_SIZE:-"512"}
ETCD_DISK_SIZE=${ETCD_DISK_SIZE:-"16"}
BUNDLE_DISK_SIZE=${BUNDLE_DISK_SIZE:-"512"}
ROBOT_PACKAGES_DISK_SIZE=${ROBOT_PACKAGES_DISK_SIZE:-"10"}

# Required for ceph raw device configuration
CEPH_DEV_NAME=""
export PATH=/var/lib/rancher/rke2/bin:/usr/local/bin:$PATH
[[ -f "/var/lib/rancher/rke2/agent/kubelet.kubeconfig" ]] && export KUBECONFIG="/var/lib/rancher/rke2/agent/kubelet.kubeconfig"
[[ -f "/etc/rancher/rke2/rke2.yaml" ]] && export KUBECONFIG="/etc/rancher/rke2/rke2.yaml"
CEPH_CONFIG_FILE="/etc/udev/rules.d/99-ceph-raw-osd.rules"
SYMLINK_PATH="uipath/ceph"

function parse_long_args() {
  while (("$#")); do
    case "$1" in
    -d | --debug)
      info "Running in debug"
      shift
      set -x
      ;;

    -R | --ceph-raw-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        CEPH_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -c | --cluster-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        CLUSTER_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -e | --etcd-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        ETCD_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -l | --data-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        DATADISK_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -b | --bundle-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        BUNDLE_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -v | --volume)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        LOCAL_DISK_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    --monitoring)
      CONFIGURE_PROMETHEUS_LV=1
      shift
      ;;

    --objectstore)
      CONFIGURE_OBJECTSTORE_LV=1
      shift
      ;;

    --insights)
      CONFIGURE_INSIGHTS_LV=1
      shift
      ;;

    --docker-registry)
      CONFIGURE_REGISTRY_LV=1
      shift
      ;;

    --robot-package-cache)
      CONFIGURE_ROBOT_PACKAGE_CACHE_LV=1
      shift
      ;;

    -s| --aicenter-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        AIC_DISK_DEV_NAME="$2"
        shift 2
      else
        error "Value for $1 is missing"
      fi
      ;;

    -se| --aicenter-storage-extend)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        AIC_ADDITIONAL_DISK_DEV_NAME="$2"
        shift 2
      else
	AIC_EXTEND_EXISTING_DISK="true"
	shift
      fi
      ;;

    -k | --robot-package-disk-name)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        ROBOT_PACKAGE_DEV_NAME="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -P | --robot-package-path)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        ROBOT_PACKAGE_PATH="$2"
        shift 2
      else
        shift 2
        error "Value for $1 is missing"
      fi
      ;;

    -r | --resize)
      RESIZE=1
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        LV_EXTEND_ARGS+=(-L "${2}G")
        shift
      else
        LV_EXTEND_ARGS+=(-l "+100%FREE")
      fi
      shift
      ;;

    -h | --help)
      display_usage
      shift
      exit 0
      ;;

    -* | *) # unsupported flags
      display_usage
      error "Error: Unsupported flag $1"
      ;;
    esac
  done

  [[ -z $ROBOT_PACKAGE_PATH ]] && ROBOT_PACKAGE_PATH="/uipath_asrobots_package_cache"

  # Configure local pv disk
  [[ $RESIZE -eq 1 ]] && {
    # Fail if multiple components are provided along with resize to 100 free space
    [[ $(( CONFIGURE_PROMETHEUS_LV + CONFIGURE_OBJECTSTORE_LV + CONFIGURE_INSIGHTS_LV + CONFIGURE_REGISTRY_LV + CONFIGURE_ROBOT_PACKAGE_CACHE_LV)) -gt 1 ]] && [[ "${LV_EXTEND_ARGS[*]}" == *"-l +100%FREE"* ]] &&
      error "Error: cannot resize multiple components to consume entire remaining space. Please specify value for --resize"
    # Fail resize without passing any component
    [[ $CONFIGURE_PROMETHEUS_LV -eq 0 && $CONFIGURE_OBJECTSTORE_LV -eq 0 && $CONFIGURE_INSIGHTS_LV -eq 0 && $CONFIGURE_REGISTRY_LV -eq 0 && $CONFIGURE_ROBOT_PACKAGE_CACHE_LV -eq 0 ]] &&
      error "Error: Required argument atleast one component argument(--monitoring|--objectstore|--insights|--docker-registry|--robot-package-cache)  with resize"
  }
}

function is_partition_exist() {
  local dev_name=$1
  lsblk  --noheadings --paths |
    grep -q "${dev_name}" ||
    error "Partition  $dev_name does not exist"
}

function create_lv() {
  local lv_name=$1
  local lv_size=$2
  local vg_name=$3

  dev_name="/dev/$vg_name/$lv_name"

  part_size=$(vgdisplay "$vg_name" -c | cut -d ":" -f13)

  parts=$((lv_size * 1024 * 1024 / part_size - 1))

  lvdisplay -c | cut -d ':' -f1 | grep -q "$dev_name" ||
    lvcreate -n "$lv_name" -l "$parts" "$vg_name" -y >/dev/null ||
    error "Unable to create logical volume for host partition: $dev_name"

  echo "$dev_name"
}

function create_vg() {
  local vg_name=$1
  local dev_name=$2

  vgdisplay -c | cut -d ':' -f1 | grep -q "$vg_name" ||
    vgcreate "$vg_name" "$dev_name" ||
    error "Unable to create volume group \"$vg_name\" from device \"$dev_name\""
}

function extend_vg() {
  local vg_name=$1
  local dev_name=$2

  if vgdisplay -c | cut -d ':' -f1 | grep -q "$vg_name"; then
    vgextend "$vg_name" "$dev_name"
  else
    error "Volume group \"$vg_name\" not found. Unable to extend volume from device \"$dev_name\""
  fi

}

function format_device() {
  local device_name=$1
  current_fs=$(lsblk -no FSTYPE "$device_name")
  [[ $current_fs == "xfs" ]] && info "$device_name is already formatted in xfs format, skipping"
  if [[ -z "${current_fs}" ]]; then
    mkfs.xfs -f "${device_name}" || error "Unable to format $device_name"
    info "$device_name formatted in xfs format"
  fi
}

function mount_partition() {
  local device_name=$1
  local mount_path=$2

  format_device "$device_name"

  if entry=$(grep -sE "(^|\s)$mount_path($|\s)" /proc/mounts); then
    mounted_dev_name=$(echo "$entry" | cut -d " " -f1)
    info "Partition: $mount_path is already mounted using Device: $mounted_dev_name, skipping"
  else
    mkdir -p "$mount_path"
    mount "${device_name}" "${mount_path}" || error "Unable to mount partition $mount_path under Device $device_name"
    success "Successfully mounted partition $mount_path using device $device_name"
  fi
}

function add_fstab_entry() {
  local dev_name=$1
  local mount_path=$2
  local part_name=$3

  uuid=$(blkid -o value -s UUID "${dev_name}")
  if [[ -z "${uuid}" ]]; then error "Unable to fetch UUID. Invalid DeviceName: $uuid"; fi

  if grep -q "${mount_path}" /etc/fstab; then
    info "${mount_path} entry already present in the fstab file"
    sed -i "\|${mount_path}|d" /etc/fstab
    sed -i "/uipath-${part_name}/d" /etc/fstab
  fi

  printf "# uipath-$part_name\nUUID=%s    %s    xfs    defaults,nofail    0    0\n" "${uuid}" "${mount_path}" >>/etc/fstab
}

function configure_cluster_disk() {
  local dev_name=$1
  local mount_path_rancher="/var/lib/rancher"
  local mount_path_kubelet="/var/lib/kubelet"
  local mount_path_installer="/opt/UiPathAutomationSuite"

  create_vg $UIPATH_VG "$dev_name"

  rancher_dev_name=$(create_lv $RANCHER_LV "${RANCHER_LV_SIZE}" $UIPATH_VG)
  mount_partition "$rancher_dev_name" $mount_path_rancher
  add_fstab_entry "$rancher_dev_name" $mount_path_rancher "rancher"
  info "$mount_path_rancher is configured"

  kubelet_dev_name=$(create_lv $KUBELET_LV "${KUBELET_LV_SIZE}" $UIPATH_VG)
  mount_partition "$kubelet_dev_name" $mount_path_kubelet
  add_fstab_entry "$kubelet_dev_name" $mount_path_kubelet "kubelet"
  info "$mount_path_kubelet is configured"

  installer_dev_name=$(create_lv $INSTALLER_LV "${INSTALLER_LV_SIZE}" $UIPATH_VG)
  mount_partition "$installer_dev_name" $mount_path_installer
  add_fstab_entry "$installer_dev_name" $mount_path_installer "installer"
  info "$mount_path_installer is configured"

  success "Cluster disk is configured successfully at dev location $dev_name"
}

function configure_data_disk() {
  local dev_name=$1
  local mount_path_datadisk="/datadisk"

  create_vg $DATADISK_VG "$dev_name"

  disk_size=$BASIC_SUITE_DATADISK_SIZE

  datadisk_dev_name=$(create_lv $DATADISK_LV "${disk_size}" $DATADISK_VG)
  mount_partition "$datadisk_dev_name" $mount_path_datadisk
  add_fstab_entry "$datadisk_dev_name" $mount_path_datadisk "datadisk"
  info "$mount_path_datadisk is configured"

  success "Data disk is configured successfully at dev location $dev_name"
  extend_data_disk "${dev_name}"
}


function configure_local_lv() {
  local lv_name="$1"
  local size="$2"
  local mount_path="$3"

  lv_full_path=$(create_lv "$lv_name" "${size}" "$DATADISK_VG")
  mount_partition "$lv_full_path" "$mount_path"
  add_fstab_entry "$lv_full_path" "$mount_path" "${lv_name}"
  info "$mount_path is configured"
  success "${lv_name} is configured successfully at dev location ${LOCAL_DISK_DEV_NAME}"
}

function configure_etcd_disk() {
  local dev_name=$1
  local mount_path_etcd="/var/lib/rancher/rke2/server/db"

  create_vg "$ETCD_VG" "$dev_name"

  etcd_dev_name=$(create_lv $ETCD_LV "${ETCD_DISK_SIZE}" $ETCD_VG)
  mount_partition "$etcd_dev_name" $mount_path_etcd
  add_fstab_entry "$etcd_dev_name" $mount_path_etcd "etcd"
  info "$mount_path_etcd is configured"

  success "etcd disk is configured successfully at dev location $dev_name"
}

function configure_robot_package_disk() {
  local dev_name=$1
  local mount_path_robot_packages=$2

  create_vg "$ROBOT_PACKAGES_VG" "$dev_name"

  robot_packages_dev_name=$(create_lv $ROBOT_PACKAGES_LV "${ROBOT_PACKAGES_DISK_SIZE}" $ROBOT_PACKAGES_VG)
  mount_partition "$robot_packages_dev_name" "$mount_path_robot_packages"
  add_fstab_entry "$robot_packages_dev_name" "$mount_path_robot_packages" "uipath-robotpackages"
  info "$mount_path_robot_packages is configured"

  success "robot package disk is configured successfully at dev location $dev_name"
}

function configure_bundle_disk() {
  local dev_name=$1
  local mount_path_bundle="/uipath"
  create_vg "$BUNDLE_VG" "$dev_name"

  bundle_dev_name=$(create_lv $BUNDLE_LV "${BUNDLE_DISK_SIZE}" $BUNDLE_VG)
  mount_partition "$bundle_dev_name" $mount_path_bundle
  add_fstab_entry "$bundle_dev_name" $mount_path_bundle "bundle"
  info "$mount_path_bundle is configured"

  success "UiPath bundle disk is configured successfully at dev location $dev_name"
}

function configure_aic_disk() {
  local dev_name=$1

  is_partition_exist "$dev_name"
  create_vg "$LOCAL_PV_VG" "$dev_name"
  success "UiPath local volume provisioner disk is configured successfully at dev location $dev_name"
}

function get_device_json(){
  local device="$1"
  local device_json
  device_json=$(lsblk --bytes --output-all "${device}" --json)
  echo "${device_json}"
}

function add_device_to_ceph_config() {
  local device="$1"
  [[ -f "$CEPH_CONFIG_FILE" ]] || touch "$CEPH_CONFIG_FILE"
  sed -i "s;KERNEL==\"$device\".*;KERNEL==\"$device\", SYMLINK+=\"${SYMLINK_PATH}/${device}\";" $CEPH_CONFIG_FILE
  if ! grep -q "KERNEL==\"$device\"" $CEPH_CONFIG_FILE; then
    echo "KERNEL==\"${device}\", SYMLINK+=\"${SYMLINK_PATH}/${device}\"" >> "${CEPH_CONFIG_FILE}"
  fi
  ## Trigger device refresh
  udevadm control --reload
  udevadm trigger
}

function restart_ceph_operator_if_possible() {
  if kubectl_output=$(kubectl -n rook-ceph get deploy rook-ceph-operator 2>&1); then
    kubectl -n rook-ceph rollout restart deploy/rook-ceph-operator && return 0
  else
    echo "Error: Failed to get the 'rook-ceph-operator' deployment in the 'rook-ceph' namespace: ${kubectl_output}"
  fi
  return 1
}

function is_ceph_cluster_using_raw_devices() {
  local exit_code=1
  if kubectl -n rook-ceph get cephcluster rook-ceph -o json 2> /dev/null | jq '.spec.storage.devicePathFilter!=null' | grep -q 'true'; then
    exit_code=0
  fi
  return "${exit_code}"
}

function wait_for_ceph_osd_to_bootstrap() {
  local devices=("$@")
  local ceph_device
  local device_json
  local timeout="300"

  for ceph_device in "${devices[@]}"; do
    start_time=$(date +"%s")
    status="failure"
    while true; do
      current_time=$(date +"%s")
      diff=$((current_time-start_time))
      [[ $diff -gt $timeout ]] && break
      device_json=$(get_device_json "${ceph_device}")
      [[ "$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].fstype')" == "LVM2_member" && "$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].children[0].name')" == "ceph--"* ]] && status="success" && break
      is_osd_on_partition "${ceph_device}" && status="success" && break
      sleep 5
    done
    if [[ $status == "success" ]]; then
      info "OSD bootstrapped successfully on device: '${ceph_device}'"
    else
      error_without_exit "Timeout waiting for ceph OSD to bootstrap on device: '${ceph_device}'"
    fi
  done
}

function wait_until_ceph_operator_restarted() {
  kubectl -n rook-ceph rollout status deploy/rook-ceph-operator
}

function fetch_hostname_lowercase() {
  local node_name
  node_name=$(xargs -0 < "/proc/$(pgrep kubelet)/cmdline" | tr ' ' '\n' | grep '\-\-hostname\-override' | cut -d'=' -f2)
  [[ -z "${node_name}" ]] && node_name=$(hostname | awk '{print tolower($0)}')
  echo "${node_name}"
}

function get_local_k8s_node_name(){
  local node_name
  node_name=$(xargs -0 < "/proc/$(pgrep kubelet)/cmdline" | tr ' ' '\n' | grep '\-\-hostname\-override' | cut -d'=' -f2)
  [[ -z "${node_name}" ]] && node_name=$(fetch_hostname_lowercase)
  [[ -z "${node_name}" ]] && warn "Unable to get kubernetes node name"
  echo "${node_name}"
}


function is_osd_on_partition(){
  local part_name="$1"
  local node_name
  local pod_name
  node_name=$(get_local_k8s_node_name)
  [[ -z "${node_name}" ]] && return 1
  pod_name=$(kubectl -n rook-ceph get pod -l app=rook-ceph-osd -o name --field-selector spec.nodeName="${node_name}" -o jsonpath='{.items[0].metadata.name}' 2> /dev/null)
  [[ -z "${pod_name}" ]] && return 1
  kubectl -n rook-ceph exec "$pod_name" -c osd -- ceph-volume inventory "${part_name}" --format json | jq -r '.rejected_reasons | index("Has BlueStore device label")' | grep -q -E '^[0-9]+$'
}


function configure_ceph_raw_disk() {
  local device_list="$1"
  local device_list_json
  local ceph_device
  local device_json
  local base_device
  local rotational_status
  local fs_type
  local device_child
  local exit_code=0
  local timeout=30
  local is_operator_restart_required=false
  local size
  local is_device_child_present
  local eligible_device=()

  shopt -s nullglob
  which jq >> /dev/null || {
    echo "jq command not found. Please install jq and try again"
    echo "jq can be made available by adding UiPath Installer bin folder to PATH environment variable"
    exit 1
  }
  device_list_json=$(jq -c -n --arg devices "${device_list}" '[$devices | split(",") | .[] | gsub("^\\s+|\\s+$";"")]')
  for ceph_device in $(jq -r -n --argjson devices "$device_list_json" '$devices[]'); do
    # Check if valid block device
    [[ ! -b "${ceph_device}" ]] && error_without_exit "Provided device: '${ceph_device}' is not a valid block device" && exit_code=1 && continue
    device_json=$(get_device_json "${ceph_device}")
    base_device=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].name')
    [[ -z "${base_device}" ]] && error_without_exit "Empty base device for '${ceph_device}'" && exit_code=1 && continue

    # Check fs on device
    fs_type=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].fstype')
    if [[ $fs_type != "null" ]]; then
      if [[ $fs_type == "LVM2_member" ]]; then
        device_child=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].children[0].name')
        if [[ $device_child == "ceph--"* ]]; then
          info "Device: '${ceph_device}' is already bootstrapped for ceph OSD"
          add_device_to_ceph_config "${base_device}"
          continue
        else
          error_without_exit "Device: '${ceph_device}' is already part of non-ceph Logical Volume: '${device_child}'"
          exit_code=1
          continue
        fi
      else
        error_without_exit "Device: '${ceph_device}' contains filesystem: '${fs_type}' which is not supported for ceph OSD"
        exit_code=1
        continue
      fi
    else
      is_device_child_present=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].children!=null')
      if [[ $is_device_child_present == "true" ]]; then
        error_without_exit "Device: '${ceph_device}' with a child device is not supported for ceph OSD"
        exit_code=1
        continue
      fi
    fi

    # Check device type, support only disk partition
    device_type=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].type')
    if ! [[ $device_type == "disk" || $device_type == "part" ]]; then
      error_without_exit "Device: '${ceph_device}' is of type: '${device_type}' not supported. Only type: 'disk' or 'part' is supported"
      exit_code=1
      continue
    fi

    # Check if OSD already running on partition
    if [[ $device_type == "part" ]]; then
      if is_osd_on_partition "${ceph_device}"; then
        info "Device: '${ceph_device}' is already bootstrapped for ceph OSD"
        add_device_to_ceph_config "${base_device}"
        continue
      fi
    fi

    if [[ -e "/dev/${SYMLINK_PATH}/${base_device}" ]]; then
      info "Device: '${ceph_device}' already has required DevLink"
      add_device_to_ceph_config "${base_device}"
    else
      # Check if device rotational
      rotational_status=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].rota')
      [[ $rotational_status -ne 0 ]] && error_without_exit "Rotational device: '${ceph_device}' is not recommended for ceph OSD" && exit_code=1 && continue

      # Check if size > 5GB
      size=$(jq -r -n --argjson deviceJson "${device_json}" '$deviceJson.blockdevices[0].size')
      [[ $size -lt "5368709120" ]] && error_without_exit "Unsupported disk capacity: $(numfmt --to=iec-i "${size}") for device: '${ceph_device}'. Required > $(numfmt --to=iec-i 5368709120)" && exit_code=1 && continue

      add_device_to_ceph_config "${base_device}"
      info "Device: '${ceph_device}' has been added/updated in the config file"

      ## Check if device devlink is created
      if ! timeout $timeout bash -c "while ! [[ -L \"/dev/${SYMLINK_PATH}/${base_device}\" && -e \"/dev/${SYMLINK_PATH}/${base_device}\" && -b \"/dev/${SYMLINK_PATH}/${base_device}\" ]];do
        sleep 2
      done" ; then
        error_without_exit "Unable to configure DevLink at '/dev/${SYMLINK_PATH}/${base_device}' for Block Device: '${ceph_device}'"
        exit_code=1
        continue
      else
        info "Required DevLink have been configured for device: '${ceph_device}' successfully"
      fi
    fi
    eligible_device+=("${ceph_device}")
    is_operator_restart_required=true
  done

  if is_ceph_cluster_using_raw_devices && [[ $is_operator_restart_required == "true" ]]; then
    restart_ceph_operator_if_possible && wait_until_ceph_operator_restarted && wait_for_ceph_osd_to_bootstrap "${eligible_device[@]}"
  fi
  shopt -u nullglob
  [[ $exit_code -eq 0 ]] && success "Ceph disk is configured successfully at dev location $device_list"
  return $exit_code
}

function extend_data_disk() {
  local dev_name=$1
  local pvList
  local iter

  [[ "$RESIZE" == "0" ]] && extend_vg $DATADISK_VG "$dev_name"
  lv_dev_name="/dev/$DATADISK_VG/$DATADISK_LV"

  vg_pv_resize "${DATADISK_VG}"

  lvextend_output=$(lvextend -r -l +100%FREE "$lv_dev_name")
  exitCode="$?"
  if [[ $exitCode -eq 0 ]]; then
    success "Successfully extended partition /dev/$DATADISK_VG/$DATADISK_LV"
  else
    if [[ "${lvextend_output}" =~ "matches existing size" ]]; then
      success "${lvextend_output}"
    else
      error "unable to extend the Datadisk volume: $lv_dev_name"
    fi
  fi
}

function vg_pv_resize() {
  local vg_name="$1"

  pvList=$(pvdisplay --select vg_name="${vg_name}" --columns --options 'pv_name' --reportformat basic | awk 'NR!=1')
  for iter in $pvList ; do
    pvresize --yes --quiet "${iter}"
  done
}

function lv_extend() {
  local lv_full_path="$1"

  if lvextend "${LV_EXTEND_ARGS[@]}" "$lv_full_path"; then
    success "Successfully extended ${lv_full_path}"
  else
    error "unable to extend : $lv_full_path"
  fi
}

function detect_os() {
  # shellcheck disable=SC1091
  echo "$(source /etc/os-release ; echo "$ID")"
}

function check_package_installed_in_debian() {
    status="$(dpkg-query -W --showformat='${db:Status-Status}' "$1" &>/dev/null)" && return 1
    [[ ! "$status" == installed ]] && return 1

    return 0
}

function check_package_installed_in_rhel() {
    rpm -q "lvm2" &>/dev/null && return 0 || return 1
}

function is_lvm_exported_in_path() {
  lvm version &> /dev/null || error "lvm is not exported in environment variable"
}

function label_local_storage_nodes() {
  local vg_name=$1
  local local_volume_node_label="local-volume-provisioner=true"
  local label_file="/etc/default/k8s-node-labels"
  vgdisplay -c | cut -d ':' -f1 | grep -q "$vg_name" || return
  node_name=$(get_local_k8s_node_name)
  [[ -z "${node_name}" ]] && return 1
  kubectl label node "${node_name}"  "${local_volume_node_label}" --overwrite >> /dev/null 2>&1 && return
  warn "Unable to label node '${node_name}' for local volume provisioner, it will be labeled during Infra Install"
  EXTRA_K8S_NODE_LABELS=""
  # shellcheck source=/dev/null
  [[ -f "${label_file}" ]] && source "${label_file}"
  [[ -n ${EXTRA_K8S_NODE_LABELS} ]] && EXTRA_K8S_NODE_LABELS="${EXTRA_K8S_NODE_LABELS},"
  EXTRA_K8S_NODE_LABELS="${EXTRA_K8S_NODE_LABELS}${local_volume_node_label}"
  echo "EXTRA_K8S_NODE_LABELS=\"${EXTRA_K8S_NODE_LABELS}\"" > /etc/default/k8s-node-labels
}

function extend_aic_disk() {
  local dev_name=$1
  extend_vg $LOCAL_PV_VG "$dev_name"
}

function extend_aic_existing_disk() {
  for disk in $(pvs |grep uipathstorevg |awk -F ' ' '{print $1}'); do
    pvresize "${disk}"
  done
}

function is_lvm_installed() {
  OS_ID="$(detect_os)"
  if [[ "${OS_ID}" == "rhel" ]]; then
    check_package_installed_in_rhel "lvm2" || error "lvm is not installed on machine"
  else
    check_package_installed_in_debian "lvm2" || error "lvm is not installed on machine"
  fi
}

function main() {
  is_lvm_installed && is_lvm_exported_in_path

  [[ -n "${CEPH_DEV_NAME}" ]] && configure_ceph_raw_disk "${CEPH_DEV_NAME}"

  [[ -n ${CLUSTER_DEV_NAME} ]] && configure_cluster_disk "$CLUSTER_DEV_NAME"

  # Configure LH disk on server nodes for backward compatibity
  # Keep this last as longhornlv will use all the remain capacity from the datavg
  [[ -n ${DATADISK_DEV_NAME} ]] && configure_data_disk "$DATADISK_DEV_NAME"

  [[ -n "${LOCAL_DISK_DEV_NAME}" ]] && {
    [[ $RESIZE -eq 0 ]] && {
      create_vg "${DATADISK_VG}" "${LOCAL_DISK_DEV_NAME}"
      # Configure prometheus PV implicitly on server node
      configure_local_lv "$PROMETHEUS_LV" "$PROMETHEUS_LV_SIZE_GB" "/datadisk/monitoring"
      # Configure insights PV implicitly on server node
      configure_local_lv "$INSIGHTS_LV" "$INSIGHTS_LV_SIZE_GB" "/datadisk/insights"
      # Create registry PV on offline environment or when the flag is passed
      [[ $CONFIGURE_REGISTRY_LV -eq 1 ]] &&
        configure_local_lv "$REGISTRY_LV" "$REGISTRY_LV_SIZE_GB" "/datadisk/registry"
      # configure objectstore PV
      [[ $CONFIGURE_OBJECTSTORE_LV -eq 1 ]] && configure_local_lv "$CEPH_MON_LV" "$CEPH_MON_LV_SIZE_GB" "/datadisk/objectstore"
      # Resize robot package cache PV
      [[ $CONFIGURE_ROBOT_PACKAGE_CACHE_LV -eq 1 ]] && configure_local_lv "$ROBOT_PACKAGES_LV" "$ROBOT_PACKAGES_DISK_SIZE" "$ROBOT_PACKAGE_PATH"
    }
    extend_vg "${DATADISK_VG}" "${LOCAL_DISK_DEV_NAME}"
    vg_pv_resize "${DATADISK_VG}"
    [[ $RESIZE -eq 1 ]] && {
      # Resize prometheus PV
      [[ $CONFIGURE_PROMETHEUS_LV -eq 1 ]] && lv_extend "/dev/${DATADISK_VG}/${PROMETHEUS_LV}"
      # Resize objectstore PV
      [[ $CONFIGURE_OBJECTSTORE_LV -eq 1 ]] && lv_extend "/dev/${DATADISK_VG}/${CEPH_MON_LV}"
      # Resize insights PV
      [[ $CONFIGURE_INSIGHTS_LV -eq 1 ]] && lv_extend "/dev/${DATADISK_VG}/${INSIGHTS_LV}"
      # Resize registry PV
      [[ $CONFIGURE_REGISTRY_LV -eq 1 ]] && lv_extend "/dev/${DATADISK_VG}/${REGISTRY_LV}"
      # Resize robot package cache PV
      [[ $CONFIGURE_ROBOT_PACKAGE_CACHE_LV -eq 1 ]] && lv_extend "/dev/${DATADISK_VG}/${ROBOT_PACKAGES_LV}"
    }
  }

  [[ -n "$ETCD_DEV_NAME" ]] && configure_etcd_disk "$ETCD_DEV_NAME"

  [[ -n "$BUNDLE_DEV_NAME" ]] && configure_bundle_disk "$BUNDLE_DEV_NAME"

  [[ -n "$ROBOT_PACKAGE_DEV_NAME" ]] && configure_robot_package_disk "$ROBOT_PACKAGE_DEV_NAME" "$ROBOT_PACKAGE_PATH"

  [[ -n "${AIC_DISK_DEV_NAME}" ]] && {
    configure_aic_disk "${AIC_DISK_DEV_NAME}" || error "Failure while configuring AiC local disk"
    label_local_storage_nodes "$LOCAL_PV_VG" || error "Failure while labeling AiC local disk node"
  }

  [[ -n "${AIC_ADDITIONAL_DISK_DEV_NAME}" ]] && {
    extend_aic_disk "${AIC_ADDITIONAL_DISK_DEV_NAME}" || error "Failure while extending AiC local disk"
  }

  [[ -n "${AIC_EXTEND_EXISTING_DISK}" && "${AIC_EXTEND_EXISTING_DISK}" == "true" ]] && {
    extend_aic_existing_disk || error "Failure while extending AiC local disk"
  }

}

check_running_as_root
parse_long_args "$@"
main
exit 0
