#!/usr/bin/env bash

# This script verifies that the cluster meets the prerequisites to perform a clean installation of Aspen Mesh.
# The script does not require user input. 
# Before running the script, make sure it's executable:
# chmod +x verify-installation-prerequisites.sh
# Then run using the command:
# ./verify-installation-prerequisites.sh

# This script queries kubectl for general cluster information and does not access containers
# or images in the cluster.

# Additional information on requirements is provided through links to Aspen Mesh documentation and third-party documentation.
# Dependencies on host: kubectl, jq

set -Eeuo pipefail

red='\x1B[0;31m'
green='\x1B[0;32m'
yellow='\x1B[0;33m'
clr='\x1B[0m'

# Verify that kubectl and jq are installed.
function verdeps() {
	if ! command -v kubectl --help &> /dev/null
	then
		echo -e "${red}kubectl is not installed. Install kubectl to run this script.${clr}"
		exit 1
	fi

	if ! command -v jq --help &> /dev/null
	then
		echo -e "${red}jq is not installed. Install jq to run this script.${clr}"
		exit 1
	fi

	echo -e "${green}All script dependencies are installed.${clr}"
}

#Verify that the cluster has enough worker nodes to run Aspen Mesh.
function verWorkerNodes() {
	workerNodes=$(kubectl get node --selector='!node-role.kubernetes.io/control-plane' | wc -l)
	if [ "$workerNodes" -lt "5" ]
	then
		echo -e "${red}The cluster has fewer than 5 worker nodes. Increase the number of worker nodes to at least 5.${clr}"
	else
		echo -e "${green}Cluster has at least 5 worker nodes.${clr}"
	fi
}

#Verify that the nodes have enough CPU units to run Aspen Mesh.
function verNodeCpu() {
	nodes=$(kubectl get nodes --template '{{range .items}}{{.metadata.name}}={{.status.capacity.cpu}} {{end}}')
	insufficient_cpu=false
	for node in $nodes
	do
		name=$(echo "$node" | cut -d "=" -f 1)
		cpu=$(echo "$node" | cut -d "=" -f 2)
		if [ "$cpu" -lt 4 ]
		then
			insufficient_cpu=true
			echo -e "${yellow}Node $name only has $cpu CPU units. Make sure each node has 4 CPU units.${clr}"
		fi
	done

	if [ "$insufficient_cpu" = false ]
	then
		echo -e "${green}All nodes have at least 4 CPU units.${clr}"
	fi
}

#Verify that the nodes have enough memory to run Aspen Mesh.
function verNodeMem() {
	nodeMem=$(kubectl get nodes --template '{{range .items}}{{.metadata.name}}={{.status.capacity.memory}} {{end}}')
	insufficient_mem=false
	for node in $nodeMem
	do
		name=$(echo "$node" | cut -d "=" -f 1)
		mem=$(echo "$node" | cut -d "=" -f 2 | cut -d "K" -f 1)
	# 8 GiB is equal to 8392000 KiB, so we want to check that the values being returned (in KiB) are less than 8 GiB
		if [ "$mem" -lt 8392000 ]
		then
			insufficient_mem=true
			echo -e "${yellow}Node $name has only $mem KiB of memory. Increase the memory of this node to at least 8 GiB (8392000 KiB).${clr}"
		fi
	done

	if [ "$insufficient_mem" = false ]
	then
		echo -e "${green}All nodes have at least 8 GiB of memory.${clr}"
	fi	
}

#Verify that the admissioregistration.k8s.io/v1 API is enabled.
function verAdmission() {
	runTimeConfig=$(kubectl get pod "$(kubectl get pods -n kube-system | \
	awk '/kube-apiserver/ {print $1; exit}')" -n kube-system -o yaml | \
	grep admissionregistration.k8s.io/v1=false || [ $? == 1 ] )

	if [ "$runTimeConfig" != "" ]
	then
		echo -e "${red}The admissionregistration.k8s.io/v1 API is disabled. \
Enable this API.${clr}"
	else
		echo -e "${green}The admissionregistration.k8s.io/v1 is not disabled.${clr}"
	fi
}

#Verify that the necessary admission controllers (plugins) are enabled.
function verControllers() {
	#Check if on a supported version of Kubernetes
	version=$(kubectl version --short | grep 'Server Version' | awk '{print substr($3, 2, 4)}')

	isVersionValid=$(echo "$version >= 1.18" | bc)

	if [ "$isVersionValid" = 1 ]
	then
		mutatingWebhook=$(kubectl get pod "$(kubectl get pods -n kube-system | awk '/kube-apiserver/ {print $1; exit}')" -n kube-system -o yaml | \
		grep "disable-admissions-plugins" | \
		grep "MutatingAdmissionWebhook" || [ $? == 1 ])
	
		validatingWebhook=$(kubectl get pod "$(kubectl get pods -n kube-system | awk '/kube-apiserver/ {print $1; exit}')" -n kube-system -o yaml | \
		grep "disable-admissions-plugins" | \
		grep "ValidatingAdmissionWebhook" || [ $? == 1 ])

		if [ "$mutatingWebhook" != "" ] || [ "$validatingWebhook" != "" ]
		then
			if [ "$mutatingWebhook" != "" ]
			then
				echo -e "${red}MutatingAdmissionWebhook admission controller is disabled in kube-apiserver. \
	Please enable to allow Aspen Mesh to run.${clr}"
			fi

			if [ "$validatingWebhook" != "" ]
			then
				echo -e "${red}ValidatingAdmissionWebhook admission controller is disabled in kube-apiserver. \
	Please enable to allow Aspen Mesh to run.${clr}"
			fi
			echo -e "${yellow}(For more information, see: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)${clr}"
		else
			echo -e "${green}The MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controllers (plugins) are enabled.${clr}"
		fi
	else
		echo -e "${red}The Kubernetes version is earlier than 1.18. Upgrade to Kubernetes 1.18 or later.${clr}"
	fi
}

# Verify that a storage class is available.
function verVolumes() {
	storageClass=$(kubectl get storageclass)

	if [ "$storageClass" = "" ]
	then
		echo -e "${red}A storage class is not available. \
Manually allocate a persistent volume and a bound persistent volume claim of at least 10 GiB in the istio-system namespace.${clr}"
	else
		echo -e "${green}A storage class is available. \
Kubernetes should be able to automatically allocate a persistent volume claim.${clr}"
	fi
}

# Verify that third party token projection is enabled.
function verJwt() {
	jwt=$(kubectl get --raw /api/v1 | jq '.resources[] | select(.name | index("serviceaccounts/token"))')

	if [ "$jwt" = "" ]
	then
		echo -e "${red}Third-party token projection is not enabled. Enable third-party token projection.${clr}"

		echo -e "\n${yellow}If third-party token projection is not properly enabled, all Aspen Mesh control-plane pods \
and workloads with sidecar proxies injected will remain in the Pending state.${clr}"

echo -e "\nThird-party token projection enables the Kubernetes API server to create scoped JWT \
tokens with configurable audience and expiration dates. This mechanism is more secure because the \
default service-account token generated by the Kubernetes API server has no expiration. \
In Aspen Mesh, third-party token projection is used to generate a token with the audience istio-ca, \
which is used by the sidecar proxy for identity validation to the Istio control plane."
	else
		echo -e "${green}Third-party token projection is enabled.${clr}"
		echo "$jwt"
	fi
}

#Begin script.
echo -e "Checking that the cluster meets the prerequisites to perform a clean installation of Aspen Mesh..."

echo -e "\nNote: To successfully deploy all the required features, \
the user or process performing the clean installation must have cluster administrator privileges."


echo -e "\n\n----- Script Check -----"
echo -e "\nVerifying that the script dependencies are installed..."
verdeps


echo -e "\n\n----- Minimum Requirements -----"
echo -e "\nVerifying that the cluster has enough worker nodes..."
verWorkerNodes

echo -e "\nVerifying that the nodes have enough CPU resources..."
verNodeCpu

echo -e "\nVerifying that the nodes have enough memory resources..."
verNodeMem

echo -e "\nIf you’re enabling tracing, running logging solutions, adding production workloads, \n\
or otherwise adding additional load, you must scale out your Kubernetes cluster appropriately."

echo -e "\nBe aware of the Kubernetes best practices (https://kubernetes.io/docs/setup/best-practices/cluster-large/)\
 for scaling master nodes alongside worker nodes.\n\
When using a managed Kubernetes offering, keep in mind any cloud-provider restrictions.\n\
For example, EKS Kubernetes limits the number of pods per node based on the EC2 instance type. \
(https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt)\n\
Aspen Mesh in its default configuration is composed of approximately 25 pods."


echo -e "\n\n----- Checking Kubernetes Configurations -----"
echo -e "\nVerifying that the admissionregistration.k8s.io/v1 API is not disabled..."
verAdmission

echo -e "\nVerifying that the necessary admission controllers (plugins) are enabled..."
verControllers


echo -e "\n\n----- Checking Dashboard Configurations -----"
# TODO: After the Aspen Mesh dashboard is no longer needed, remove PV, PVC and storage class checks and message.
echo -e "\nIf you’re running a distribution of Kubernetes that does not dynamically allocate persistent volumes \
(https://kubernetes.io/docs/concepts/storage/persistent-volumes/), \
a persistent volume and a bound persistent volume claim (https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) \
created in the istio-system namespace must be available prior to installation. \
The minimum recommended size of the persistent volume claim is 10 GiB. For more information on persistent volumes, \
see: https://my.aspenmesh.io/client/docs/1.11/operations/persistentvolumes/."

echo -e "\nVerifying a storage class is available..."
verVolumes


echo -e "\n\n----- Checking JWT Configurations -----"

echo -e "\nYou must have third-party token projection enabled to install Aspen Mesh."

echo -e "\nLearn how to determine whether third-party token projection is enabled for your cluster \
(https://my.aspenmesh.io/client/docs/1.11/operations/configuring-third-party-token-projection/)."

echo -e "\nVerifying that third party token projection is enabled..."
verJwt

