From: John Wolfe Date: Tue, 21 Dec 2021 20:48:49 +0000 (-0800) Subject: Salt Stack Script Integration for Linux. X-Git-Tag: stable-12.0.0~49 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db3a1dc6c9cc771bf4c4d8a268b6747f167bcad7;p=thirdparty%2Fopen-vm-tools.git Salt Stack Script Integration for Linux. Added the "svtminion.sh" script which will be packaged for Linux to support SaltStack integration and management from the componentMgr plugin. --- diff --git a/open-vm-tools/services/plugins/componentMgr/svtminion.sh b/open-vm-tools/services/plugins/componentMgr/svtminion.sh new file mode 100644 index 000000000..aa7436590 --- /dev/null +++ b/open-vm-tools/services/plugins/componentMgr/svtminion.sh @@ -0,0 +1,1867 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021 VMware, Inc. All rights reserved. + +## Salt VMware Tools Integration script +## integration with Component Manager and GuestStore Helper + +## set -u +## set -xT +set -o functrace +set -o pipefail +## set -o errexit + +# using bash for now +# run this script as root, as needed to run salt + +readonly SCRIPT_VERSION='SCRIPT_VERSION_REPLACE' + +# definitions + +CURL_DOWNLOAD_RETRY_COUNT=5 + +## Repository locations and naming +readonly default_salt_url_version="3003.3-1" +readonly salt_name="salt" +salt_url_version="${default_salt_url_version}" +readonly base_url="https://repo.saltproject.io/salt/vmware-tools-onedir" +readonly repo_json_file="repo.json" +readonly base_url_json_file="${base_url}/${repo_json_file}" + +# Salt file and directory locations +readonly base_salt_location="/opt/saltstack" +readonly salt_dir="${base_salt_location}/${salt_name}" +readonly test_exists_file="${salt_dir}/run/run" + +readonly salt_conf_dir="/etc/salt" +readonly salt_minion_conf_name="minion" +readonly salt_minion_conf_file="${salt_conf_dir}/${salt_minion_conf_name}" +readonly salt_master_sign_dir="${salt_conf_dir}/pki/${salt_minion_conf_name}" + +readonly log_dir="/var/log" + +readonly list_file_dirs_to_remove="${base_salt_location} +/etc/salt +/var/run/salt +/var/cache/salt +/var/log/salt +/usr/bin/salt-* +/usr/lib/systemd/system/salt-minion.service +/etc/systemd/system/salt-minion.service +" +## /var/log/vmware-${SCRIPTNAME}-* + +readonly salt_dep_file_list="systemctl +curl +sha512sum +vmtoolsd +grep +awk +sed +cut +" + +readonly allowed_log_file_action_names="status +depend +install +clear +remove +default +" + +readonly salt_wrapper_file_list="minion +call +" + +readonly salt_minion_service_wrapper=\ +"# Copyright (c) 2021 VMware, Inc. All rights reserved. + +[Unit] +Description=The Salt Minion +Documentation=man:salt-minion(1) file:///usr/share/doc/salt/html/contents.html https://docs.saltproject.io/en/latest/contents.html +After=network.target +# After=ConnMgr.service ProcMgr.service sockets.target + +[Service] +KillMode=process +Type=notify +NotifyAccess=all +LimitNOFILE=8192 +MemoryLimit=250M +Nice=19 +ExecStart=/opt/saltstack/salt/run/run minion + +[Install] +WantedBy=multi-user.target +" + + +## VMware file and directory locations +readonly vmtools_base_dir_etc="/etc/vmware-tools" +readonly vmtools_conf_file="tools.conf" +readonly vmtools_salt_minion_section_name="salt_minion" + +## VMware guestVars file and directory locations +readonly guestvars_base_dir="guestinfo./vmware.components" +readonly \ +guestvars_salt_dir="${guestvars_base_dir}.${vmtools_salt_minion_section_name}" +readonly guestvars_salt_args="${guestvars_salt_dir}.args" +readonly guestvars_salt_desiredstate="${guestvars_salt_dir}.desiredstate" + + +# Array for minion configuration keys and values +# allows for updates from number of configuration sources before final +# write to /etc/salt/minion +declare -a m_cfg_keys +declare -a m_cfg_values + + +## Component Manager Installer/Script return/exit status codes +# return/exit Status codes +# 100 + 0 => installed +# 100 + 1 => installing +# 100 + 2 => notInstalled +# 100 + 3 => installFailed +# 100 + 4 => removing +# 100 + 5 => removeFailed +# 126 => scriptFailed +# 130 => scriptTerminated +declare -A STATUS_CODES_ARY +STATUS_CODES_ARY[installed]=100 +STATUS_CODES_ARY[installing]=101 +STATUS_CODES_ARY[notInstalled]=102 +STATUS_CODES_ARY[installFailed]=103 +STATUS_CODES_ARY[removing]=104 +STATUS_CODES_ARY[removeFailed]=105 +STATUS_CODES_ARY[scriptFailed]=126 +STATUS_CODES_ARY[scriptTerminated]=130 + +# log levels available for logging, order sensitive +readonly LOG_MODES_AVAILABLE=(silent error warning info debug) +declare -A LOG_LEVELS_ARY +LOG_LEVELS_ARY[silent]=0 +LOG_LEVELS_ARY[error]=1 +LOG_LEVELS_ARY[warning]=2 +LOG_LEVELS_ARY[info]=3 +LOG_LEVELS_ARY[debug]=4 + + +STATUS_CHK=0 +DEPS_CHK=0 +USAGE_HELP=0 +UNINSTALL_FLAG=0 +VERBOSE_FLAG=0 +VERSION_FLAG=0 + +CLEAR_ID_KEYS_FLAG=0 +CLEAR_ID_KEYS_PARAMS="" + +INSTALL_FLAG=0 +INSTALL_PARAMS="" + +MINION_VERSION_FLAG=0 +MINION_VERSION_PARAMS="" + +LOG_LEVEL_FLAG=0 +LOG_LEVEL_PARAMS="" + +#default logging level to errors, similar to Windows script +LOG_LEVEL=${LOG_LEVELS_ARY[warning]} + +# helper functions + +_timestamp() { + date -u "+%Y-%m-%d %H:%M:%S" +} + +_log() { + echo "$(_timestamp) $*" >> \ + "${log_dir}/vmware-${SCRIPTNAME}-${LOG_ACTION}-${logdate}.log" +} + +_display() { + if [[ ${VERBOSE_FLAG} -eq 1 ]]; then echo "$1"; fi + _log "$*" +} + +_error_log() { + if [[ ${LOG_LEVELS_ARY[error]} -le ${LOG_LEVEL} ]]; then + local log_file="" + log_file="${log_dir}/vmware-${SCRIPTNAME}-${LOG_ACTION}-${logdate}.log" + msg="ERROR: $*" + echo "$msg" 1>&2 + echo "$(_timestamp) $msg" >> "${log_file}" + echo "One or more errors found. See ${log_file} for details." 1>&2 + CURRENT_STATUS=${STATUS_CODES_ARY[scriptFailed]} + exit ${STATUS_CODES_ARY[scriptFailed]} + fi +} + +_info_log() { + if [[ ${LOG_LEVELS_ARY[info]} -le ${LOG_LEVEL} ]]; then + msg="INFO: $*" + _log "${msg}" + fi +} + +_warning_log() { + if [[ ${LOG_LEVELS_ARY[error]} -le ${LOG_LEVEL} ]]; then + msg="WARNING: $*" + _log "${msg}" + fi +} + +_debug_log() { + if [[ ${LOG_LEVELS_ARY[debug]} -le ${LOG_LEVEL} ]]; then + msg="DEBUG: $*" + _log "${msg}" + fi +} + +_yesno() { +read -r -p "Continue (y/n)?" choice +case "$choice" in + y|Y ) echo "yes";; + n|N ) echo "no";; + * ) echo "invalid";; +esac +} + + +# +# _usage +# +# Prints out help text +# + + _usage() { + echo "" + echo "usage: ${0}" + echo " [-c|--clear] [-d|--depend] [-h|--help] [-i|--install]" + echo " [-l|--loglevel] [-m|--minionversion] [-r|--remove]" + echo " [-s|--status] [-v|--version]" + echo "" + echo " -c, --clear clear previous minion identifer and keys," + echo " and set specified identifer if present" + echo " -d, --depend check dependencies required to run script exist" + echo " -h, --help this message" + echo " -i, --install install and activate salt-minion configuration" + echo " parameters key=value can also be passed on CLI" + echo " -l, --loglevel set log level for logging," + echo " silent error warning debug info" + echo " default loglevel is warning" + echo " -m, --minionversion install salt-minion version, default[latest]" + echo " -r, --remove deactivate and remove the salt-minion" + echo " -s, --status return status for this script" + echo " -v, --version version of this script" + echo "" + echo " salt-minion vmtools integration script" + echo " example: $0 --status" +} + + +# work functions + +# +# _cleanup +# +# Cleanups any running process and areas on control-C +# +# +# Results: +# Exits with hard-coded value 130 +# + +_cleanup() { + exit ${STATUS_CODES_ARY[scriptTerminated]} +} + +trap _cleanup INT + + +# cheap trim relying on echo to convert tabs to spaces and +# all multiple spaces to a single space +_trim() { + echo "$1" +} + + +# +# _set_log_level +# +# Set log_level for logging, +# log_level 'silent','error','warning','info','debug' +# default 'warning' +# +# Results: +# Returns with exit code +# + +_set_log_level() { + + _info_log "$0:${FUNCNAME[0]} processing setting set log_level for logging" + + local ip_level="" + local valid_level=0 + local old_log_level=${LOG_LEVEL} + + ip_level=$( echo "$1" | cut -d ' ' -f 1) + scam=${#LOG_MODES_AVAILABLE[@]} + for ((i=0; i "${vmtools_base_dir_etc}/${vmtools_conf_file}" + _warning_log "$0:${FUNCNAME[0]} creating empty configuration "\ + "file ${vmtools_base_dir_etc}/${vmtools_conf_file}" + else + # need to extract configuration for salt-minion + # find section name ${vmtools_salt_minion_section_name} + # read configuration till next section, output salt-minion conf file + + local salt_config_flag=0 + while IFS= read -r line + do + line_value=$(_trim "${line}") + if [[ -n "${line_value}" ]]; then + _debug_log "$0:${FUNCNAME[0]} processing tools.conf "\ + "line '${line}'" + if echo "${line_value}" | grep -q '^\[' ; then + if [[ ${salt_config_flag} -eq 1 ]]; then + # if new section after doing salt config, we are done + break; + fi + if [[ ${line_value} = \ + "[${vmtools_salt_minion_section_name}]" ]]; then + # have section, get configuration values, set flag and + # start fresh salt-minion configuration file + salt_config_flag=1 + fi + elif [[ ${salt_config_flag} -eq 1 ]]; then + # read config ahead of section check, better logic flow + cfg_key=$(echo "${line}" | cut -d '=' -f 1) + cfg_value=$(echo "${line}" | cut -d '=' -f 2) + _update_minion_conf_ary "${cfg_key}" "${cfg_value}" || { + _error_log "$0:${FUNCNAME[0]} error updating minion "\ + "configuration array with key '${cfg_key}' and "\ + "value '${cfg_value}', retcode '$?'"; + } + else + _debug_log "$0:${FUNCNAME[0]} skipping tools.conf "\ + "line '${line}'" + fi + fi + done < "${vmtools_base_dir_etc}/${vmtools_conf_file}" + fi + return ${_retn} +} + + +# +# _fetch_vmtools_salt_minion_conf_guestvars +# +# Retrieve the configuration for salt-minion from vmtools guest variables +# +# Results: +# salt-minion configuration file updated with configuration read +# from vmtools guest variables +# configuration file section for salt_minion +# + +_fetch_vmtools_salt_minion_conf_guestvars() { + # fetch the current configuration for section salt_minion + # from guest variables args + + local _retn=0 + local gvar_args="" + + gvar_args=$(vmtoolsd --cmd "info-get ${guestvars_salt_args}" 2>/dev/null)\ + || { _warning_log "$0:${FUNCNAME[0]} unable to retrieve arguments "\ + "from guest variables location ${guestvars_salt_args}, "\ + "retcode '$?'"; + } + + if [[ -z "${gvar_args}" ]]; then return ${_retn}; fi + + _debug_log "$0:${FUNCNAME[0]} processing arguments from guest variables "\ + "location ${guestvars_salt_args}" + + for idx in ${gvar_args} + do + cfg_key=$(echo "${idx}" | cut -d '=' -f 1) + cfg_value=$(echo "${idx}" | cut -d '=' -f 2) + _update_minion_conf_ary "${cfg_key}" "${cfg_value}" || { + _error_log "$0:${FUNCNAME[0]} error updating minion configuration"\ + "array with key '${cfg_key}' and value '${cfg_value}', "\ + "retcode '$?'"; + } + done + + return ${_retn} +} + + +# +# _fetch_vmtools_salt_minion_conf_cli_args +# +# Retrieve the configuration for salt-minion from any args '$@' passed +# on the command line +# +# Results: +# Exits with new vmtools configuration file if none found +# or salt-minion configuration file updated with configuration read +# from vmtools configuration file section for salt_minion +# + +_fetch_vmtools_salt_minion_conf_cli_args() { + local _retn=0 + local cli_args="" + local cli_no_args=0 + + cli_args="$*" + cli_no_args=$# + if [[ ${cli_no_args} -ne 0 ]]; then + _debug_log "$0:${FUNCNAME[0]} processing command line "\ + "arguments '${cli_args}'" + for idx in ${cli_args} + do + # check for start of next option, idx starts with '-' (covers '--') + if [[ "${idx}" = --* ]]; then + break + fi + cfg_key=$(echo "${idx}" | cut -d '=' -f 1) + cfg_value=$(echo "${idx}" | cut -d '=' -f 2) + _update_minion_conf_ary "${cfg_key}" "${cfg_value}" || { + _error_log "$0:${FUNCNAME[0]} error updating minion "\ + "configuration array with key '${cfg_key}' and "\ + "value '${cfg_value}', retcode '$?'"; + } + done + fi + return ${_retn} +} + + +# +# _randomize_minion_id +# +# Added 5 digit random number to input minion identifier +# +# Input: +# String to add random number to +# if no input, default string 'minion_' used +# +# Results: +# exit, return value etc +# + +_randomize_minion_id() { + + local ran_minion="" + local ip_string="$1" + + if [[ -z "${ip_string}" ]]; then + ran_minion="minion_${RANDOM:0:5}" + else + #provided input + ran_minion="${ip_string}_${RANDOM:0:5}" + fi + _debug_log "$0:${FUNCNAME[0]} generated randomized minion "\ + "identifier '${ran_minion}'" + echo "${ran_minion}" +} + + +# +# _fetch_vmtools_salt_minion_conf +# +# Retrieve the configuration for salt-minion +# precendence order: L -> H +# from VMware Tools guest Variables +# from VMware Tools configuration file tools.conf +# from any command line parameters +# +# Results: +# Exits with new salt-minion configuration file written +# + +_fetch_vmtools_salt_minion_conf() { + # fetch the current configuration for section salt_minion + # from vmtoolsd configuration file + + _debug_log "$0:${FUNCNAME[0]} retrieving minion configuration parameters" + _fetch_vmtools_salt_minion_conf_guestvars || { + _error_log "$0:${FUNCNAME[0]} failed to process guest variable "\ + "arguments, retcode '$?'"; + } + _fetch_vmtools_salt_minion_conf_tools_conf || { + _error_log "$0:${FUNCNAME[0]} failed to process tools.conf file, "\ + "retcode '$?'"; + } + _fetch_vmtools_salt_minion_conf_cli_args "$*" || { + _error_log "$0:${FUNCNAME[0]} failed to process command line "\ + "arguments, retcode '$?'"; + } + + # now write minion conf array to salt-minion configuration file + local mykey_ary_sz=${#m_cfg_keys[@]} + local myvalue_ary_sz=${#m_cfg_values[@]} + if [[ "${mykey_ary_sz}" -ne "${myvalue_ary_sz}" ]]; then + _error_log "$0:${FUNCNAME[0]} key '${mykey_ary_sz}' and "\ + "value '${myvalue_ary_sz}' array sizes for minion_conf "\ + "don't match" + else + mkdir -p "${salt_conf_dir}" + echo "# Minion configuration file - created by vmtools salt script"\ + > "${salt_minion_conf_file}" + echo "enable_fqdns_grains: False" >> "${salt_minion_conf_file}" + for ((chk_idx=0; chk_idx> "${salt_minion_conf_file}" + mkdir -p "/etc/salt/pki/minion" + cp -f "${m_cfg_values[${chk_idx}]}" \ + "${salt_master_sign_dir}/" + else + echo "${m_cfg_keys[${chk_idx}]}: ${m_cfg_values[${chk_idx}]}" \ + >> "${salt_minion_conf_file}" + fi + done + fi + + _info_log "$0:${FUNCNAME[0]} successfully retrieved the salt-minion "\ + "configuration from configuration sources" + return 0 +} + + +# +# _curl_download +# +# Retrieve file from specifed url to specific file +# +# Results: +# Exits with 0 or error code +# + +_curl_download() { + local file_name="$1" + local file_url="$2" + local download_retry_failed=1 # assume issues + local _retn=0 + + _info_log "$0:${FUNCNAME[0]} attempting download of file '${file_name}'" + + for ((i=0; i/dev/null + _retn=$? + if [[ ${_retn} -ne 0 ]]; then + CURRENT_STATUS=${STATUS_CODES_ARY[installFailed]} + _error_log "$0:${FUNCNAME[0]} tar xzf expansion of downloaded "\ + "file '${salt_pkg_name}' failed, return code '${_retn}'" + fi + if [[ ! -f ${test_exists_file} ]]; then + CURRENT_STATUS=${STATUS_CODES_ARY[installFailed]} + _error_log "$0:${FUNCNAME[0]} expansion of downloaded file "\ + "'${salt_url}' failed to provide critical file "\ + "'${test_exists_file}'" + fi + CURRENT_STATUS=${STATUS_CODES_ARY[installed]} + cd "${CURRDIR}" || return $? + + _info_log "$0:${FUNCNAME[0]} successfully retrieved salt-minion" + return 0 +} + + +# +# _check_multiple_script_running +# +# check if more than one version of the script is running +# +# Results: +# Echos the number of scripts running, allowing for forks etc +# from bash etc, a single instance of the script returns 3 +# + +_check_multiple_script_running() { + local count=0 + local procs_found="" + + _info_log "$0:${FUNCNAME[0]} checking how many versions of the "\ + "script are running" + + procs_found=$(pgrep -f "${SCRIPTNAME}") + count=$(echo "${procs_found}" | wc -l) + + _debug_log "$0:${FUNCNAME[0]} checking versions of script are running, "\ + "bashpid '${BASHPID}', processes found '${procs_found}', "\ + "and count '${count}'" + + echo "${count}" + return 0 +} + + +# +# _check_std_minion_install +# +# Check if standard salt-minion is installed for the OS +# for example: install salt-minion from rpm or deb package +# +# Results: +# 0 - No standard install found and empty string output +# !0 - Standard install found and Salt version found output +# + +_check_std_minion_install() { + + # checks for /usr/bin, then /usr/local/bin + # this catches 80% to 90% of the reqular cases + # if salt-call is there, then so is a salt-minion + # as they are installed together + + local _retn=0 + local max_file_sz=200 + local list_of_files_check=" +/usr/bin/salt-call +/usr/local/bin/salt-call +" + _info_log "$0:${FUNCNAME[0]} check if standard salt-minion installed" + + for idx in ${list_of_files_check} + do + if [[ -f "${idx}" ]]; then + #check size of file, if larger than 200, not script wrapper file + local file_sz=0 + file_sz=$(( $(wc -c < "${idx}") )) + _debug_log "$0:${FUNCNAME[0]} found file '${idx}', "\ + "size '${file_sz}'" + if [[ ${file_sz} -gt ${max_file_sz} ]]; then + # get salt-version + local s_ver="" + s_ver=$("${idx}" --local test.version |grep -v 'local:' |xargs) + _debug_log "$0:${FUNCNAME[0]} found standard salt-minion, "\ + "Salt version: '${s_ver}'" + echo "${s_ver}" + _retn=1 + break + fi + fi + done + echo "" + return ${_retn} +} + + +# +# _find_salt_pid +# +# finds the pid for the salt process +# +# Results: +# Echos ${salt_pid} which could be empty '' if salt process not found +# + +_find_salt_pid() { + # find the pid for salt-minion if active + local salt_pid=0 + salt_pid=$(pgrep -f "${salt_name}\/run\/run minion" | head -n 1 | + awk -F " " '{print $1}') + _debug_log "$0:${FUNCNAME[0]} checking for salt-minion process id, "\ + "found '${salt_pid}'" + echo "${salt_pid}" +} + +# +# _ensure_id_or_fqdn +# +# Ensures that a valid minion identifier has been specified, and if not a +# valid Fully Qualified Domain Name exists (not default Unknown.example.org) +# else generates a minion id to use. +# +# Note: this function should only be run before starting the salt-minion +# via systemd after it has been installed +# +# Side Effect: +# Updates salt-minion configuration file with generated identifer +# if no valid FQDN +# +# Results: +# salt-minion configuration contains a valid identifier or FQDN to use. +# Exits with 0 +# + +_ensure_id_or_fqdn () { + # ensure minion id or fqdn for salt-minion + + local minion_fqdn="" + + # quick check if id specified + if grep -q '^id:' < "${salt_minion_conf_file}"; then + _debug_log "$0:${FUNCNAME[0]} salt-minion identifier found, no "\ + "need to check further" + return 0 + fi + + _debug_log "$0:${FUNCNAME[0]} ensuring salt-minion identifier or "\ + "FQDN is specified for salt-minion configuration" + minion_fqdn=$(/usr/bin/salt-call --local grains.get fqdn | + grep -v 'local:' | xargs) + if [[ -n "${minion_fqdn}" && + "${minion_fqdn}" != "Unknown.example.org" ]]; then + _debug_log "$0:${FUNCNAME[0]} non-default salt-minion FQDN "\ + "'${minion_fqdn}' is specified for salt-minion configuration" + return 0 + fi + + # default FQDN, no id is specified, generate one and update conf file + local minion_genid="" + minion_genid=$(_generate_minion_id) + echo "id: ${minion_genid}" >> "${salt_minion_conf_file}" + _debug_log "$0:${FUNCNAME[0]} no salt-minion identifier found, "\ + "generated identifier '${minion_genid}'" + + return 0 +} + + +# +# _create_helper_scripts +# +# Create helper scripts for salt-call and salt-minion +# +# Example: _create_helper_scripts +# +# Results: +# Exits with 0 or error code +# + +_create_helper_scripts() { + + for idx in ${salt_wrapper_file_list} + do + local abs_filepath="" + abs_filepath="/usr/bin/salt-${idx}" + + _debug_log "$0:${FUNCNAME[0]} creating helper file 'salt-${idx}' "\ + "in directory /usr/bin" + + echo "#!/usr/bin/env bash + +# Copyright (c) 2021 VMware, Inc. All rights reserved. +" > "${abs_filepath}" || { + _error_log "$0:${FUNCNAME[0]} failed to create helper file "\ + "'salt-${idx}' in directory /usr/bin, retcode '$?'"; + } + { + echo -n "exec /opt/saltstack/salt/run/run ${idx} "; + echo -n "\"$"; + echo -n "{"; + echo -n "@"; + echo -n ":"; + echo -n "1}"; + echo -n "\""; + } >> "${abs_filepath}" || { + _error_log "$0:${FUNCNAME[0]} failed to finish creating helper "\ + "file 'salt-${idx}' in directory /usr/bin, retcode '$?'"; + } + echo "" >> "${abs_filepath}" + + # ensure executable + chmod 755 "${abs_filepath}" || { + _error_log "$0:${FUNCNAME[0]} failed to make helper file "\ + "'salt-${idx}' executable in directory /usr/bin, retcode '$?'"; + } + done + +} + + +# +# _status_fn +# +# discover and return the current status +# +# 0 => installed +# 1 => installing +# 2 => notInstalled +# 3 => installFailed +# 4 => removing +# 5 => removeFailed +# 126 => scriptFailed +# +# Side Effects: +# CURRENT_STATUS updated +# +# Results: +# Exits numerical status +# + +_status_fn() { + # return status + local _retn_status=${STATUS_CODES_ARY[notInstalled]} + local script_count=0 + + _info_log "$0:${FUNCNAME[0]} checking status for script" + script_count=$(_check_multiple_script_running) + if [[ ${script_count} -gt 3 ]]; then + _error_log "$0:${FUNCNAME[0]} failed to check status, " \ + "multiple versions of the script are running" + fi + + svpid=$(_find_salt_pid) + if [[ ! -f "${test_exists_file}" && -z ${svpid} ]]; then + # check not installed and no process id + CURRENT_STATUS=${STATUS_CODES_ARY[notInstalled]} + _retn_status=${STATUS_CODES_ARY[notInstalled]} + elif [[ -f "${test_exists_file}" ]]; then + # check installed + CURRENT_STATUS=${STATUS_CODES_ARY[installed]} + _retn_status=${STATUS_CODES_ARY[installed]} + # normal case but double-check + svpid=$(_find_salt_pid) + if [[ -z ${svpid} ]]; then + # Note: someone could have stopped the salt-minion, + # so installed but not running, + # status codes don't allow for that case + CURRENT_STATUS=${STATUS_CODES_ARY[installFailed]} + _retn_status=${STATUS_CODES_ARY[installFailed]} + fi + elif [[ -z ${svpid} ]]; then + # check no process id and main directory still left, then removeFailed + if [[ -f "${test_exists_file}" ]]; then + CURRENT_STATUS=${STATUS_CODES_ARY[removeFailed]} + _retn_status=${STATUS_CODES_ARY[removeFailed]} + fi + fi + + + return ${_retn_status} +} + + +# +# _deps_chk_fn +# +# Check dependencies for using salt-minion +# +# +# Side Effects: +# Results: +# Exits with 0 or error code +# +_deps_chk_fn() { + # return dependency check + local error_missing_deps="" + + _info_log "$0:${FUNCNAME[0]} checking script dependencies" + for idx in ${salt_dep_file_list} + do + command -v "${idx}" 1>/dev/null || { + if [[ -z "${error_missing_deps}" ]]; then + error_missing_deps="${idx}" + else + error_missing_deps="${error_missing_deps} ${idx}" + fi + } + done + if [[ -n "${error_missing_deps}" ]]; then + _error_log "$0:${FUNCNAME[0]} failed to find required "\ + "dependenices '${error_missing_deps}'"; + fi + return 0 +} + + +# +# _install_fn +# +# Executes scripts to install Salt from Salt repository +# and start the salt-minion using systemd +# +# Results: +# Exits with 0 or error code +# + +_install_fn () { + # execute install of Salt minion + local _retn=0 + local script_count=0 + local existing_chk="" + local found_salt_ver="" + + _info_log "$0:${FUNCNAME[0]} processing script install" + + script_count=$(_check_multiple_script_running) + if [[ ${script_count} -gt 3 ]]; then + _error_log "$0:${FUNCNAME[0]} failed to install, " \ + "multiple versions of the script are running" + fi + + found_salt_ver=$(_check_std_minion_install) + if [[ -n "${found_salt_ver}" ]]; then + _error_log "$0:${FUNCNAME[0]} failed to install, " \ + "existing Standard Salt Installation detected, "\ + "Salt version: '${found_salt_ver}'" + else + _debug_log "$0:${FUNCNAME[0]} no standardized install found" + fi + + # check if salt-minion or salt-master (salt-cloud etc req master) + # and log warning that they will be overwritten + existing_chk=$(pgrep -l "salt-minion|salt-master" | cut -d ' ' -f 2 | uniq) + if [[ -n "${existing_chk}" ]]; then + for idx in ${existing_chk} + do + local salt_fn="" + salt_fn="$(basename "${idx}")" + _warning_log "$0:${FUNCNAME[0]} existing salt functionality "\ + "${salt_fn} shall be stopped and replaced when new "\ + "salt-minion is installed" + done + fi + + # fetch salt-minion form repository + _fetch_salt_minion || { + _error_log "$0:${FUNCNAME[0]} failed to fetch salt-minion "\ + "from repository , retcode '$?'"; + } + + # get configuration for salt-minion + _fetch_vmtools_salt_minion_conf "$@" || { + _error_log "$0:${FUNCNAME[0]} failed , read configuration for "\ + "salt-minion, retcode '$?'"; + } + + if [[ ${_retn} -eq 0 && -f "${test_exists_file}" ]]; then + # create helper scripts for /usr/bin to ensure they are present + # before attempting to use them in _ensure_id_or_fqdn + _debug_log "$0:${FUNCNAME[0]} creating helper files salt-call "\ + "and salt-minion in directory /usr/bin" + _create_helper_scripts || { + _error_log "$0:${FUNCNAME[0]} failed to create helper files "\ + "salt-call or salt-minion in directory /usr/bin, retcode '$?'"; + } + fi + + # ensure minion id or fqdn for salt-minion + _ensure_id_or_fqdn + + if [[ ${_retn} -eq 0 && -f "${test_exists_file}" ]]; then + if [[ -n "${existing_chk}" ]]; then + # be nice and stop any current salt functionalty found + for idx in ${existing_chk} + do + local salt_fn="" + salt_fn="$(basename "${idx}")" + _warning_log "$0:${FUNCNAME[0]} stopping salt functionality"\ + " ${salt_fn} its replaced with new installed salt-minion" + systemctl stop "${salt_fn}" || { + _warning_log "$0:${FUNCNAME[0]} stopping existing salt "\ + "functionality ${salt_fn} encountered difficulties "\ + "using systemctl, it will be over-written with the "\ + "new installed salt-minion regarlessly, retcode '$?'"; + } + done + fi + + # install salt-minion systemd service script + _debug_log "$0:${FUNCNAME[0]} copying systemd service script "\ + "'salt-minion.service' to directory /usr/lib/systemd/system" + echo "${salt_minion_service_wrapper}" \ + > /usr/lib/systemd/system/salt-minion.service || { + _error_log "$0:${FUNCNAME[0]} failed to copy systemd service "\ + "file 'salt-minion.service' to directory "\ + "/usr/lib/systemd/system, retcode '$?'"; + } + cd /etc/systemd/system || return $? + rm -f "salt-minion.service" + ln -s "/usr/lib/systemd/system/salt-minion.service" \ + "salt-minion.service" || { + _error_log "$0:${FUNCNAME[0]} failed to symbolic link "\ + "systemd service file 'salt-minion.service' in "\ + "directory /etc/systemd/system, retcode '$?'"; + } + _debug_log "$0:${FUNCNAME[0]} symbolically linked systemd service "\ + "file 'salt-minion.service' in directory /etc/systemd/system" + cd "${CURRDIR}" || return $? + + # start the salt-minion using systemd + local name_service="salt-minion.service" + systemctl daemon-reload || { + _error_log "$0:${FUNCNAME[0]} reloading the systemd daemon "\ + "failed , retcode '$?'"; + } + _debug_log "$0:${FUNCNAME[0]} successfully executed systemctl "\ + "daemon-reload" + systemctl restart "${name_service}" || { + _error_log "$0:${FUNCNAME[0]} starting the salt-minion using "\ + "systemctl failed , retcode '$?'"; + } + _debug_log "$0:${FUNCNAME[0]} successfully executed systemctl "\ + "restart '${name_service}'" + systemctl enable "${name_service}" || { + _error_log "$0:${FUNCNAME[0]} enabling the salt-minion using "\ + "systemctl failed , retcode '$?'"; + } + _debug_log "$0:${FUNCNAME[0]} successfully executed systemctl "\ + "enable '${name_service}'" + fi + return ${_retn} +} + + +# +# _generate_minion_id +# +# Searchs salt-minion configuration file for current id, and disables it +# and generates a new id based from the existng id found, +# or an older commented out id, and provides it with a randomized 5 digit +# postpended to it, for example: myminion_12345 +# +# if no previous id found, a generated minion_ is output +# +# Side Effects: +# Disables any id found in minion configuration file +# +# Result: +# Outputs randomized minion id for use in a minion configuration file +# Exits with 0 or error code +# + +_generate_minion_id () { + + local salt_id_flag=0 + local minion_id="" + local cfg_value="" + local ifield="" + local tfields="" + + _debug_log "$0:${FUNCNAME[0]} generating a salt-minion identifier" + + # always comment out what was there + sed -i 's/^id:/# id:/g' "${salt_minion_conf_file}" + + while IFS= read -r line + do + line_value=$(_trim "${line}") + if [[ -n "${line_value}" ]]; then + if echo "${line_value}" | grep -q '^# id:' ; then + # get value and write out value_ + cfg_value=$(echo "${line_value}" | cut -d ' ' -f 3) + if [[ -n "${cfg_value}" ]]; then + salt_id_flag=1 + minion_id=$(_randomize_minion_id "${cfg_value}") + _debug_log "$0:${FUNCNAME[0]} found previously used id "\ + "field, randomizing it" + fi + elif echo "${line_value}" | grep -q -w 'id:' ; then + # might have commented out id, get value and + # write out value_ + tfields=$(echo "${line_value}"|awk -F ':' '{print $2}'|xargs) + ifield=$(echo "${tfields}" | cut -d ' ' -f 1) + if [[ -n ${ifield} ]]; then + minion_id=$(_randomize_minion_id "${ifield}") + salt_id_flag=1 + _debug_log "$0:${FUNCNAME[0]} found previously used "\ + "id field, randomizing it" + fi + else + _debug_log "$0:${FUNCNAME[0]} skipping line '${line}'" + fi + fi + done < "${salt_minion_conf_file}" + + if [[ ${salt_id_flag} -eq 0 ]]; then + # no id field found, write minion_> "${salt_minion_conf_file}" + _debug_log "$0:${FUNCNAME[0]} updated salt-minion identifer "\ + "'${minion_id}' in configuration file '${salt_minion_conf_file}'" + + if [[ ${salt_minion_pre_active_flag} -eq 1 ]]; then + # restart the stopped salt-minion using systemd + systemctl restart salt-minion || { + _error_log "$0:${FUNCNAME[0]} failed to restart salt-minion "\ + "using systemctl, retcode '$?'"; + } + + _debug_log "$0:${FUNCNAME[0]} successfully executed systemctl "\ + "restart salt-minion" + fi + + return ${_retn} +} + + +# +# _remove_installed_files_dirs +# +# Removes all Salt files and directories that may be used +# +# Results: +# Exits with 0 or error code +# + +_remove_installed_files_dirs() { + _debug_log "$0:${FUNCNAME[0]} removing directories and files "\ + "in '${list_file_dirs_to_remove}'" + for idx in ${list_file_dirs_to_remove} + do + rm -fR "${idx}" || { + _error_log "$0:${FUNCNAME[0]} failed to remove file or "\ + "directory '${idx}' , retcode '$?'"; + } + done + return 0 +} + + +# +# _uninstall_fn +# +# Executes scripts to uninstall Salt from system +# stopping the salt-minion using systemd +# +# Side Effects: +# CURRENT_STATUS updated +# +# Results: +# Exits with 0 or error code +# + +_uninstall_fn () { + # remove Salt minion + local _retn=0 + local script_count=0 + + _info_log "$0:${FUNCNAME[0]} processing script remove" + + script_count=$(_check_multiple_script_running) + if [[ ${script_count} -gt 3 ]]; then + _error_log "$0:${FUNCNAME[0]} failed to remove, " \ + "multiple versions of the script are running" + fi + + found_salt_ver=$(_check_std_minion_install) + if [[ -n "${found_salt_ver}" ]]; then + _error_log "$0:${FUNCNAME[0]} failed to remove, " \ + "existing Standard Salt Installation detected, "\ + "Salt version: '${found_salt_ver}'" + else + _debug_log "$0:${FUNCNAME[0]} no standardized install found" + fi + + if [[ ! -f "${test_exists_file}" ]]; then + CURRENT_STATUS=${STATUS_CODES_ARY[notInstalled]} + + # assumme rest is gone + # TBD enhancement, could loop thru and check all of files to remove + # and if salt_pid empty but we error out if issues when uninstalling, + # so safe for now. + _retn=0 + else + CURRENT_STATUS=${STATUS_CODES_ARY[removing]} + svpid=$(_find_salt_pid) + if [[ -n ${svpid} ]]; then + # stop the active salt-minion using systemd + # and give it a little time to stop + systemctl stop salt-minion || { + _error_log "$0:${FUNCNAME[0]} failed to stop salt-minion "\ + "using systemctl, retcode '$?'"; + } + _debug_log "$0:${FUNCNAME[0]} successfully executed systemctl "\ + "stop salt-minion" + systemctl disable salt-minion || { + _error_log "$0:${FUNCNAME[0]} disabling the salt-minion "\ + "using systemctl failed , retcode '$?'"; + } + _debug_log "$0:${FUNCNAME[0]} successfully executed systemctl "\ + "disable salt-minion" + fi + + if [[ ${_retn} -eq 0 ]]; then + svpid=$(_find_salt_pid) + if [[ -n ${svpid} ]]; then + _debug_log "$0:${FUNCNAME[0]} found salt-minion process "\ + "id '${salt_pid}', systemctl stop should have "\ + "eliminated it, killing it now" + kill "${svpid}" + ## given it a little time + sleep 5 + fi + svpid=$(_find_salt_pid) + if [[ -n ${svpid} ]]; then + CURRENT_STATUS=${STATUS_CODES_ARY[removeFailed]} + _error_log "$0:${FUNCNAME[0]} failed to kill the "\ + "salt-minion, pid '${svpid}' during uninstall" + else + _remove_installed_files_dirs || { + _error_log "$0:${FUNCNAME[0]} failed to remove all "\ + "installed salt-minion files and directories, "\ + "retcode '$?'"; + } + CURRENT_STATUS=${STATUS_CODES_ARY[notInstalled]} + fi + fi + fi + + _info_log "$0:${FUNCNAME[0]} successfuly removed salt-minion and "\ + "associated files and directories" + return ${_retn} +} + + +# +# _clean_up_log_files +# +# Limits number of log files by removing oldest log files which exceed +# limit LOG_FILE_NUMBER +# +# Results: +# Exits with 0 or error code +# +_clean_up_log_files() { + + _info_log "$0:${FUNCNAME[0]} removing and limiting log files" + for idx in ${allowed_log_file_action_names} + do + local count_f=0 + local found_f="" + local -a found_f_ary + found_f=$(ls -t "${log_dir}/vmware-${SCRIPTNAME}-${idx}"* 2>/dev/null) + count_f=$(echo "${found_f}" | wc | awk -F" " '{print $2}') + mapfile -t found_f_ary <<< "${found_f}" + + if [[ ${count_f} -gt ${LOG_FILE_NUMBER} ]]; then + # allow for org-0 + for ((i=count_f-1; i>=LOG_FILE_NUMBER; i--)); do + _debug_log "$0:${FUNCNAME[0]} removing log file "\ + "'${found_f_ary[i]}', for count '${i}', "\ + "limit '${LOG_FILE_NUMBER}'" + rm -f "${found_f_ary[i]}" || { + _error_log "$0:${FUNCNAME[0]} failed to remove file "\ + "'${found_f_ary[i]}', for count '${i}', "\ + "limit '${LOG_FILE_NUMBER}'" + } + done + else + _debug_log "$0:${FUNCNAME[0]} found '${count_f}' "\ + "log files starting with "\ + "${log_dir}/vmware-${SCRIPTNAME}-${idx}-"\ + ", limit '${LOG_FILE_NUMBER}'" + fi + done + return 0 +} + +################################### MAIN #################################### + +# static definitions + +CURRDIR=$(pwd) + +# default status is notInstalled +CURRENT_STATUS=${STATUS_CODES_ARY[notInstalled]} +export CURRENT_STATUS + +## build date-time tag used for logging UTC YYYYMMDDhhmmss +## YearMontDayHourMinuteSecondMicrosecond aka jid +logdate=$(date -u +%Y%m%d%H%M%S) + +# set logging infomation +LOG_FILE_NUMBER=5 +SCRIPTNAME=$(basename "$0") +mkdir -p "${log_dir}" + +# set to action e.g. 'remove', 'install' +# default is for any logging not associated with a specific action +# for example: debug logging and --version +LOG_ACTION="default" + +CLI_ACTION=0 + +while true; do + if [[ -z "$1" ]]; then break; fi + case "$1" in + -c | --clear ) + CLEAR_ID_KEYS_FLAG=1; + shift; + CLEAR_ID_KEYS_PARAMS=$*; + ;; + -d | --depend ) + DEPS_CHK=1; + shift; + ;; + -h | --help ) + USAGE_HELP=1; + shift; + ;; + -i | --install ) + INSTALL_FLAG=1; + shift; + INSTALL_PARAMS="$*"; + ;; + -l | --loglevel ) + LOG_LEVEL_FLAG=1; + shift; + LOG_LEVEL_PARAMS="$*"; + ;; + -m | --minionversion ) + MINION_VERSION_FLAG=1; + shift; + MINION_VERSION_PARAMS="$*"; + ;; + -r | --remove ) + UNINSTALL_FLAG=1; + shift; + ;; + -s | --status ) + STATUS_CHK=1; + shift; + ;; + -v | --version ) + VERSION_FLAG=1; + shift; + ;; + -- ) + shift; + break; + ;; + * ) + shift; + ;; + esac +done + +## check if want help, display usage and exit +if [[ ${USAGE_HELP} -eq 1 ]]; then + _usage + exit 0 +fi + + +## MAIN BODY OF SCRIPT + +retn=0 + +if [[ ${LOG_LEVEL_FLAG} -eq 1 ]]; then + # ensure logging level changes are processed before any actions + CLI_ACTION=1 + _set_log_level "${LOG_LEVEL_PARAMS}" + retn=$? +fi +if [[ ${STATUS_CHK} -eq 1 ]]; then + CLI_ACTION=1 + LOG_ACTION="status" + _status_fn + retn=$? +fi +if [[ ${DEPS_CHK} -eq 1 ]]; then + CLI_ACTION=1 + LOG_ACTION="depend" + _deps_chk_fn + retn=$? +fi +if [[ ${MINION_VERSION_FLAG} -eq 1 ]]; then + CLI_ACTION=1 + # ensure this is processed before install + _set_install_minion_version_fn "${MINION_VERSION_PARAMS}" + retn=$? +fi +if [[ ${INSTALL_FLAG} -eq 1 ]]; then + CLI_ACTION=1 + LOG_ACTION="install" + _install_fn "${INSTALL_PARAMS}" + retn=$? +fi +if [[ ${CLEAR_ID_KEYS_FLAG} -eq 1 ]]; then + CLI_ACTION=1 + LOG_ACTION="clear" + _clear_id_key_fn "${CLEAR_ID_KEYS_PARAMS}" + retn=$? +fi +if [[ ${UNINSTALL_FLAG} -eq 1 ]]; then + CLI_ACTION=1 + LOG_ACTION="remove" + _uninstall_fn + retn=$? +fi +if [[ ${VERSION_FLAG} -eq 1 ]]; then + CLI_ACTION=1 + echo "${SCRIPT_VERSION}" + retn=0 +fi + +if [[ ${CLI_ACTION} -eq 0 ]]; then + # check if guest variables have an action since none from CLI + # since none presented on the command line + gvar_action=$(vmtoolsd --cmd "info-get ${guestvars_salt_desiredstate}" \ + 2>/dev/null) || { + _warning_log "$0 unable to retrieve any action arguments from "\ + "guest variables ${guestvars_salt_desiredstate}, retcode '$?'"; + } + + if [[ -n "${gvar_action}" ]]; then + case "${gvar_action}" in + depend) + LOG_ACTION="depend" + _deps_chk_fn + retn=$? + ;; + present) + LOG_ACTION="install" + _install_fn + retn=$? + ;; + absent) + LOG_ACTION="remove" + _uninstall_fn + retn=$? + ;; + status) + LOG_ACTION="status" + _status_fn + retn=$? + ;; + *) + ;; + esac + fi +fi + +_clean_up_log_files + +exit ${retn}