#!/bin/bash
# SPDX-License-Identifier: MIT
#
# packages - collect state of installed packages.
#
# Copyright IBM Corp. 2023
#
# Usage: packages [<datafile>]
#
# Get version of packages specified in datafile. Filter out packages that are
# not installed. Map package names according to rules in file 'packages.alias'.
#
# Example input (datafile):
#
#   packages:
#     bash:
#     perl:
#     unknown:
#
# Example output (stdout):
#
#   packages:
#     bash: 4.0
#     perl: 5.8
#

function die() {
	echo "Error: $*" >&2
	exit 1
}

DATAFILE="$1"

# Compare versions a and b using comparison operator op (< > <= >= == !=).
function cmp_ver() {
	local ver_a="$1" ver_b="$2" op="$3" comp_a comp_b a b i

	# Split OS version
	IFS=. read -ra comp_a <<<"$ver_a"
	IFS=. read -ra comp_b <<<"$ver_b"

	# Compare version components
	for (( i=0; i < ${#comp_a[@]} || i < ${#comp_b[@]}; i++)) ; do
		a="${comp_a[$i]:-0}"
		b="${comp_b[$i]:-0}"

		[[ "$a" != "$b" ]] && break
	done

	# Force base 10 comparison to handle leading zeroes
	eval "(( 10#$a $op 10#$b ))"
}

function match_os() {
	local spec="$1" os ver op="=="

	IFS=- read -r os ver <<<"$spec"
	if [[ "$ver" =~ \+$ ]] ; then
		op=">="
		ver="${ver%+}"
	fi

	# Match OS ID
	[[ "$TELA_OS_ID" == "$os" ]] || return 1
	[[ -z "$ver" ]] && return 0

	cmp_ver "$TELA_OS_VERSION" "$ver" "$op"
}

# Adjust package name for this OS
function get_alias() {
	local IFS var="$1" name="$2" pkg list entry os alt

	while read -r pkg list ; do
		[[ "$pkg" == "$name:" ]] || continue
		for entry in $list ; do
			os=${entry%%:*}
			alt=${entry#*:}
			alt=${alt//|/ }
			if match_os "$os" ; then
				eval "$var=($alt)"
				return
			fi
		done
	done < packages.alias

	eval "$var=$name"
}

# Check status and version of specified package using RPM
function check_rpm() {
	local IFS name="$1" name2 pkg buffer

	# Get alternate name for package on this OS
	get_alias name2 $name

	#  Get package version
	for pkg in "${name2[@]}"; do
		read buffer < <(rpm --query --queryformat '%{VERSION}\n' \
				$pkg 2>/dev/null) || continue
		if ! [[ "$buffer" =~ "not installed" ]] ; then
			# Emit result as YAML
			echo "  $name: $buffer"
			return
		fi
	done
}

# Check status and version of specified package using DPKG
function check_dpkg() {
	local IFS name="$1" name2 pkg buffer

	# Get alternate name for package on this OS
	get_alias name2 $name

	#  Get package version
	for pkg in "${name2[@]}"; do
		read buffer < <(dpkg-query --show --showformat '${Version}\n' \
					${pkg} 2>/dev/null) || continue
		if [[ -n "$buffer" ]] ; then
			# Emit result as YAML
			echo "  $name: $buffer"
			return
		fi
	done
}

function get_state() {
	local check line id

	if [ -z "$TELA_OS_ID" ] ; then
		local ostool
		ostool="$(dirname "$0")/../os"
		if [ -x "$ostool" ] ; then
			while read -r line ; do
				case "$line" in
				"    id:"*)	 export TELA_OS_ID="${line#*: }" ;;
				"    version:"*) export TELA_OS_VERSION="${line#*: }" ;;
				esac
			done < <("$ostool")
		fi
	fi

	# Determine package manager
	check=
	case "$TELA_OS_ID" in
		rhel|sles|sled|caasp|fedora|linuxonzdriver|opensuse*|suse*)
			check=check_rpm
			;;
		ubuntu|debian)
			check=check_dpkg
			;;
		*)
			if command -v rpm >/dev/null 2>&1 ; then
				check=check_rpm
			elif command -v dpkg-query >/dev/null 2>&1 ; then
				check=check_dpkg
			else
				die "Unsupported OS '$TELA_OS_ID'"
			fi
			;;
	esac

	IFS=$'\n'

	# Header
	read line
	[[ -n "$line" ]] && echo "$line"

	# Check each package
	while read -r line ; do
		id=${line:2}
		id="${id%%:*}"

		$check $id
	done
}

if [ -n "$DATAFILE" ] ; then
	# Get state for resources specified in datafile
	get_state <"$DATAFILE"
fi

exit 0
