From 96e0669264e8ed545f52eccd5f8b46d45762a586 Mon Sep 17 00:00:00 2001
From: Reid Wahl <30487349+nrwahl2@users.noreply.github.com>
Date: Fri, 27 Nov 2020 03:34:25 -0800
Subject: [PATCH] ocf-distro: Improve robustness and specificity (#1558)

* ocf-distro: Add SUSE check to get_os_ver()
* ocf-distro: Reduce code duplication, functionalize version-get
* ocf-distro: Add is_debian_based() and is_suse_based() functions
* azure-lb: Use is_suse_based() when setting OCF_RESKEY_nc_default
* apache: Use is_suse_based() and is_debian_based()
* ocf-distro: Try os-release first to get release ID
* ocf-distro: Make `uname -s` the final fallback for release ID
* ocf-distro: Normalize OS release IDs in get_release_id()
* ocf-distro: Functionalize getting OS from legacy source
* ocf-distro: Try os-release first to get OS version
* ocf-distro: Functionalize getting OS version from legacy source
* ocf-distro: Functionalize processing of retrieved values
---
 heartbeat/apache     |  17 ++--
 heartbeat/azure-lb   |  12 +--
 heartbeat/ocf-distro | 220 +++++++++++++++++++++++++++++++++++++------
 3 files changed, 206 insertions(+), 43 deletions(-)

diff --git a/heartbeat/apache b/heartbeat/apache
index 02334047..7b5add9c 100755
--- a/heartbeat/apache
+++ b/heartbeat/apache
@@ -167,16 +167,13 @@ silent_status() {
 
 # May be useful to add other distros in future
 validate_default_config() {
-	case "$( get_release_id )" in
-                *SUSE)
-                        validate_default_suse_config
-                        ;;
-                Debian)
-                        validate_default_debian_config
-                        ;;
-                *)
-                        return 0
-        esac
+	if is_suse_based; then
+		validate_default_suse_config
+	elif is_debian_based; then
+		validate_default_debian_config
+	else
+		return 0
+	fi
 }
 
 # When using the default /etc/apache2/httpd.conf on SUSE, the file
diff --git a/heartbeat/azure-lb b/heartbeat/azure-lb
index 05c13451..793bcdfb 100755
--- a/heartbeat/azure-lb
+++ b/heartbeat/azure-lb
@@ -17,12 +17,12 @@
 . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
 
 # Defaults
-OCF_RESKEY_nc_default="/usr/bin/nc"
-case "$( get_release_id )" in
-	*SUSE)
-		OCF_RESKEY_nc_default="/usr/bin/socat"
-	;;
-esac
+if is_suse_based; then
+	OCF_RESKEY_nc_default="/usr/bin/socat"
+else
+	OCF_RESKEY_nc_default="/usr/bin/nc"
+fi
+
 OCF_RESKEY_port_default="61000"
 
 : ${OCF_RESKEY_nc=${OCF_RESKEY_nc_default}}
diff --git a/heartbeat/ocf-distro b/heartbeat/ocf-distro
index f69910c9..9ccb6731 100644
--- a/heartbeat/ocf-distro
+++ b/heartbeat/ocf-distro
@@ -3,45 +3,211 @@
 # 
 # Currently needed for the nfsserver RA which has some already
 # released RH specific stuff (/etc/sysconfig/nfs editing)
+#
+# These functions are intended to be POSIX-compliant for portability.
 # 
 
+# systemd-based systems should all have an os-release file.
+_ETC_OS_RELEASE_FILE="/etc/os-release"
+_USR_OS_RELEASE_FILE="/usr/lib/os-release"
+
+# Legacy distro-specific files
 _DEBIAN_VERSION_FILE="/etc/debian_version"
 _REDHAT_RELEASE_FILE="/etc/redhat-release"
 _SUSE_RELEASE_FILE="/etc/SuSE-release"
-_RELEASE_FILES="/etc/*-release"
-_REDHAT_BASED_DISTROS_RE='red *hat|fedora|centos|scientific linux'
 
-get_release_id() {
-	if which lsb_release >/dev/null 2>&1; then
-		lsb_release -si
-	elif [ -e $_DEBIAN_VERSION_FILE ]; then
-		echo Debian
-	elif [ -e $_SUSE_RELEASE_FILE ]; then
-		echo SUSE
-	elif [ -e $_REDHAT_RELEASE_FILE ]; then
-		echo Redhat
-	else # FIXME not exactly the id here, but will do for our purpose
-		cat $_RELEASE_FILES 2>/dev/null
+_DEBIAN_BASED_DISTROS_RE="debian|ubuntu"
+_REDHAT_BASED_DISTROS_RE="red *hat|rhel|fedora|centos|scientific"
+_SUSE_BASED_DISTROS_RE="sles|suse"
+
+
+# Converts OS release ID to a standard form regardless of source.
+_process_os_release_id() {
+	_os="$1"
+
+	# Convert to lowercase, isolate distro name, remove whitespace
+	_os=$(echo "$_os" \
+		| tr "[:upper:]" "[:lower:]" \
+		| sed -e "s|\(gnu/\)*linux||" -e "s/server//" \
+			-e "s/release.*//" -e "s/[[:digit:]].*//" \
+			-e "s/[[:blank:]]//")
+
+	# Normalize known distros to os-release names
+	case "$_os" in
+	*centos*)
+		_os="centos"
+		;;
+	*debian*)
+		_os="debian"
+		;;
+	*fedora*)
+		_os="fedora"
+		;;
+	*redhat*|*rhel*|*scientific*)
+		_os="rhel"
+		;;
+	*opensuse*)
+		_os="opensuse"
+		;;
+	*suseenterprise*)
+		_os="sles"
+		;;
+	*ubuntu*)
+		_os="ubuntu"
+		;;
+	esac
+
+	echo "$_os"
+}
+
+# Converts OS version ID to a form that ocf_version_cmp() can handle.
+# Strips any garbage.
+_process_os_version_id() {
+	_ver="$1"
+
+	# Discard everything except the version.
+	# Use ocf_version_cmp() format if present, or bare integer
+	# otherwise.
+	# Append ".0" for integers so that ocf_version_cmp() doesn't
+	# fail.
+	_ver_cmp_fmt="[[:digit:]][[:digit:].-]*[[:digit:]]"
+	_ver=$(echo "$_ver" \
+		| sed -n -e "s/[^[:digit:]]*\(${_ver_cmp_fmt}\).*/\1/p" \
+			-e "s/[^[:digit:]]*\([[:digit:]]*\).*/\1.0/p" \
+		| head -n 1)
+
+	echo "$_ver"
+}
+
+# Gets OS release ID (i.e., distro) or version ID from os-release file.
+# $_ETC_OS_RELEASE_FILE takes precedence over $_USR_OS_RELEASE_FILE.
+_get_val_from_os_release_file() {
+	_key=""
+	_value=""
+	_func=""
+
+	case "$1" in
+	id)
+		_key="ID"
+		_func="_process_os_release_id"
+		;;
+	version_id)
+		_key="VERSION_ID"
+		_func="_process_os_version_id"
+		;;
+	esac
+
+	if [ -n "$_key" ]; then
+		if [ -f "$_ETC_OS_RELEASE_FILE" ]; then
+			_value=$(awk -F "=" -v k="$_key" '$1 == k {print $2}' \
+					"$_ETC_OS_RELEASE_FILE" | tr -d \")
+		fi
+
+		if [ -z "$_value" ] && [ -f "$_USR_OS_RELEASE_FILE" ]; then
+			_value=$(awk -F "=" -v k="$_key" '$1 == k {print $2}' \
+					"$_USR_OS_RELEASE_FILE" | tr -d \")
+		fi
 	fi
+
+	# Ensure the value is in the correct format
+	[ -n "$_func" ] && _value=$("$_func" "$_value")
+
+	echo "$_value"
 }
 
-is_redhat_based() {
-	get_release_id | egrep -qsi "$_REDHAT_BASED_DISTROS_RE"
+# Gets OS release ID from lsb_release command or legacy *-release files
+_get_os_from_legacy_source() {
+	_os=""
+
+	if which lsb_release >/dev/null 2>&1; then
+		_os=$(lsb_release -si)
+
+	elif [ -f "$_DEBIAN_VERSION_FILE" ]; then
+		_os="debian"
+
+	elif [ -f "$_REDHAT_RELEASE_FILE" ]; then
+		_os=$(head -n 1 "$_REDHAT_RELEASE_FILE")
+
+	elif [ -f "$_SUSE_RELEASE_FILE" ]; then
+		_os=$(head -n 1 "$_SUSE_RELEASE_FILE")
+
+	else
+		_os=$(uname -s)
+	fi
+
+	_process_os_release_id "$_os"
 }
 
-# get_os_ver() is currently unused
-get_os_ver() {
+# Gets OS version from lsb_release command or legacy *-release files
+_get_version_from_legacy_source() {
+	_ver=""
+
 	if which lsb_release >/dev/null 2>&1; then
-		OS=`lsb_release -si`
-		VER=`lsb_release -sr`
-	elif [ -f $_DEBIAN_VERSION_FILE ]; then
-		OS=Debian
-		VER=$(cat $_DEBIAN_VERSION_FILE)
-	elif [ -f $_REDHAT_RELEASE_FILE ]; then
-		OS=RedHat  # redhat or similar
-		VER=$(sed "s/.* release \([^ ]\+\).*/\1/" $_REDHAT_RELEASE_FILE)
+		_ver=$(lsb_release -sr)
+
+	elif [ -f "$_DEBIAN_VERSION_FILE" ]; then
+		_ver=$(cat "$_DEBIAN_VERSION_FILE")
+
+	elif [ -f "$_REDHAT_RELEASE_FILE" ]; then
+		_ver=$(head -1 "$_REDHAT_RELEASE_FILE")
+
+	elif [ -f "$_SUSE_RELEASE_FILE" ]; then
+		_ver=$(awk '$1 == "VERSION" {print $3}' "$_SUSE_RELEASE_FILE")
+		_patchlevel=$(awk '$1 == "PATCHLEVEL" {print $3}' \
+				"$_SUSE_RELEASE_FILE")
+
+		[ -n "$_patchlevel" ] && _ver="${_ver}.${_patchlevel}"
+
 	else
-		OS=$(uname -s)
-		VER=$(uname -r)
+		_ver=$(uname -r)
 	fi
+
+	_process_os_version_id "$_ver"
+}
+
+# Prints OS release ID (i.e., distro name)
+get_release_id() {
+	_os=$(_get_val_from_os_release_file id)
+
+	if [ -z "$_os" ]; then
+		_os=$(_get_os_from_legacy_source)
+	fi
+
+	echo "$_os"
+}
+
+# Prints OS version ID
+get_os_version_id() {
+	_ver=$(_get_val_from_os_release_file version_id)
+
+	if [ -z "$_ver" ] || [ "$(get_release_id)" = "debian" ]; then
+		# Debian only includes major release in os-release.
+		# $_DEBIAN_VERSION_FILE has ${major}.${minor}.
+		_ver=$(_get_version_from_legacy_source)
+	fi
+
+	echo "$_ver"
+}
+
+# Returns true if the OS is Debian-based, otherwise false
+is_debian_based() {
+	get_release_id | grep -Eqi "$_DEBIAN_BASED_DISTROS_RE"
+}
+
+# Returns true if the OS is Red Hat-based, otherwise false
+is_redhat_based() {
+	get_release_id | grep -Eqi "$_REDHAT_BASED_DISTROS_RE"
+}
+
+# Returns true if the OS is SUSE-based, otherwise false
+is_suse_based() {
+	get_release_id | grep -Eqi "$_SUSE_BASED_DISTROS_RE"
+}
+
+# Sets global variables OS and VER.
+# get_os_ver() is currently unused upstream; maintained for backwards
+# compatibility.
+get_os_ver() {
+	OS=$(get_release_id)
+	VER=$(get_os_version_id)
 }
-- 
2.32.0

