#!/bin/bash
#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
#------------------------------------------------------------------------------
#==============================================================================
# Declarations and Environment
# Allow override of P4U_HOME, which is set only when testing P4U scripts.
export P4U_HOME=${P4U_HOME:-/p4/common/bin}
export P4U_LIB=${P4U_LIB:-/p4/common/lib}
export P4U_ENV=$P4U_LIB/p4u_env.sh
export P4U_LOG=off
export VERBOSITY=${VERBOSITY:-3}
# Environment isolation. For stability and security reasons, prepend
# PATH to include dirs where known-good scripts exist.
# known/tested PATH and, by implication, executables on the PATH.
export PATH=$P4U_HOME:$PATH:~/bin:.
export P4CONFIG=${P4CONFIG:-.p4config}
[[ -r "$P4U_ENV" ]] || {
echo -e "\nError: Cannot load environment from: $P4U_ENV\n\n"
exit 1
}
declare BASH_LIBS=$P4U_ENV
BASH_LIBS+=" $P4U_LIB/libcore.sh"
BASH_LIBS+=" $P4U_LIB/libp4u.sh"
for bash_lib in $BASH_LIBS; do
source $bash_lib ||\
{ echo -e "\nFATAL: Failed to load bash lib [$bash_lib]. Aborting.\n"; exit 1; }
done
declare Version=1.4.0
declare -i SilentMode=0
declare TicketExpiration=
declare TicketStatus=
declare SuperAccessStatus=
declare SkipInstanceList=sample
declare NewInstanceList=
declare -i DoPreflight=1
declare -i PreflightAccessOK=1
declare -i PreflightEnvOK=1
declare -i Skipped=0
declare HTCfgFile=/p4/common/config/HelixTopology.cfg
declare SDPEnvFile=/p4/common/bin/p4_vars
declare InstanceEnvFile=
declare InstanceList=$(grep '^INSTANCE' "$HTCfgFile" | cut -d '|' -f 2)
declare Program=Unset
declare ProgramPath=
declare ProgramArgs=
export P4USER=$(echo $(source $SDPEnvFile hms; echo $P4USER))
export P4TICKETS=/p4/hms/.p4tickets
export P4TRUST=/p4/hms/.p4trust
export P4ENVIRO=/dev/null/.p4enviro
unset P4CONFIG
export VERBOSITY=3
#==============================================================================
# Local Functions
#------------------------------------------------------------------------------
# Function: terminate
function terminate
{
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
vvmsg "$THISSCRIPT: EXITCODE: $OverallReturnStatus"
# Stop logging.
[[ "${P4U_LOG}" == off ]] || stoplog
# Don't litter.
cleanTrash
# With the trap removed, exit.
exit $OverallReturnStatus
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional). Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1). If displaying an
# errror, usually $1 should be -h so that the longer usage message doesn't
# obsure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
declare style=${1:--h}
declare errorMessage=${2:-Unset}
if [[ $errorMessage != Unset ]]; then
echo -e "\n\nUsage Error:\n\n$errorMessage\n\n"
fi
echo "USAGE for $THISSCRIPT v$Version:
$THISSCRIPT [[-i <instance1>[,<instance2>,...]] [-sk <instance1>[,<instance2>,...]]] [-L <log>] [-si] [-v<n>] [-n] [-D] [<program> [<program arguments ...>]]
or
$THISSCRIPT [-h|-man|-V]
"
if [[ $style == -man ]]; then
echo -e "
DESCRIPTION:
This script runs a preflight check, or a command you specify, against
all Helix instances defined in the Helix Topology configuration file.
If no arguments are supplied, verifies that all instances can be
centrally managed with 'p4' commands from the current host. It does
this by verifying that a Perforce command can execute with super user
access and has a long-term ticket for each instance. The $P4TICKETS
and $P4TRUST files are effectively verified with the preflight
check.
OPTIONS:
-i <instance1>[,<instance2>,...]
Specify a comma-delimited list of instances to operate on.
By default, all instances defined in the Helix Topology configuration
file are operated on.
-sk <instance1>[,<instance2>,...]
Specify a comma-delimited list of instances to ignore for processing purposes.
By default, the instance named 'sample' is skipped, the equivalent of specifying
'-sk sample'. Specify '-i none' to to process all instances including the 'sample'
instance (if defined).
-v<n> Set verbosity 1-5 (-v1 = quiet, -v5 = highest).
-L <log>
Specify the path to a log file, or the special value 'off' to disable
logging. By default, output (stdout and stderr) are not captured.
When -L is used, this script is self-logging. That is, output displayed
on the screen is simultaneously captured in the log file. When -L is used,
do not run this script with redirection operators like '> log' or '2>&1',
and do not use 'tee.'
-si Operate silently. All output (stdout and stderr) is redirected to the log
only; no output appears on the terminal. This cannot be used with
'-L off'.
This is useful when running from cron, as it prevents automatic
email from being sent by cron directly, as it does when a script called
from cron generates any output.
-n No-Op. Prints commands instead of running them.
-D Set extreme debugging verbosity.
HELP OPTIONS:
-h Display short help message
-man Display man-style help message
-V Dispay version info for this script and its libraries.
FILES:
Helix Topology Config file:
$HTCfgFile
Cental tickets file:
$P4TICKETS
Cental trust file:
$P4TRUST
EXAMPLES:
Example 1: Preflight Chcek
With no arguments, do a quick preflight check to ensure that
all configured instances can be managed, and that shell envionment
files for all instances exist:
$THISSCRIPT
Example 2: Shell Envionment Check
Check the envionment for instance 1:
$THISSCRIPT -i 1 p4 set
WARNING: This script overrides environment settings for
P4PORT, P4TICKETS, P4TRUST, and P4ENVIRO, so settings for
these values may not match what is defined by setting the
SDP environment in the usual way, i.e.:
source $SDPEnvFile N # where N is the instance
Example 3: Server Version Check for all instances
$THISSCRIPT p4 -ztag -F %serverVersion% info -s
Example 4: Server Version Check for all instances, with
higher verbosity ('-v4') to display each instance as the command is run:
$THISSCRIPT p4 -v4 -ztag -F %serverVersion% info -s
"
fi
exit 1
}
#==============================================================================
# Command Line Processing
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-i) InstanceList="${2//,/ }"; shiftArgs=1;;
(-sk) SkipInstanceList="${2/,/ }"; shiftArgs=1;;
(-h) usage -h;;
(-man) usage -man;;
(-V) show_versions; exit 1;;
(-v1) export VERBOSITY=1;;
(-v2) export VERBOSITY=2;;
(-v3) export VERBOSITY=3;;
(-v4) export VERBOSITY=4;;
(-v5) export VERBOSITY=5;;
(-L) export P4U_LOG=$2; shiftArgs=1;;
(-si) SilentMode=1;;
(-n) export NO_OP=1;;
(-D) set -x;; # Debug; use 'set -x' mode.
(-*) usage -h "Unknown arg ($1).";;
(*)
Program=$1
shift
ProgramArgs=$@
break
;;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
#==============================================================================
# Command Line Verification
[[ $SilentMode -eq 1 && $P4U_LOG == off ]] && \
usage -h "Cannot use '-si' with '-L off'."
[[ "$Program" == Unset ]] || DoPreflight=0
# Determine the list of instances to process, accounting for any instances
# skipped.
if [[ $SkipInstanceList != "none" ]]; then
NewInstanceList=
for i in $InstanceList; do
Skipped=0
for s in $SkipInstanceList; do
if [[ "$s" == "$i" ]]; then
Skipped=1
break
fi
done
# If the instance isn't in the skip list, add it to the list of instances.
[[ $Skipped -eq 1 ]] || NewInstanceList+=" $i"
done
InstanceList="$(echo $NewInstanceList)"
vmsg "Instance List: $InstanceList (excluding $SkipInstanceList)"
fi
#==============================================================================
# Main Program
trap terminate EXIT SIGINT SIGTERM
declare -i OverallReturnStatus=0
declare -i ExitCode=0
if [[ "${P4U_LOG}" != off ]]; then
touch ${P4U_LOG} || bail "Couldn't touch log file [${P4U_LOG}]."
# Redirect stdout and stderr to a log file.
if [[ $SilentMode -eq 0 ]]; then
exec > >(tee ${P4U_LOG})
exec 2>&1
else
exec >${P4U_LOG}
exec 2>&1
fi
initlog
fi
if [[ $DoPreflight -eq 1 ]]; then
msg "${H1}\nPreflight Check for instances:\n$(echo $InstanceList)\n"
msg "Accesss Preflight Check\n${H2}"
printf "%-12s %-24s %-5s %-s\n" "Instance" "P4PORT" "Super" "Ticket Status"
printf "%-12s %-24s %-5s %-s\n" "------------" "------------------------" "-----" "-----------------------------------------"
for i in $InstanceList; do
host=$(grep "COMPONENT|$i|p4d-mc|" "$HTCfgFile"|cut -d '|' -f 5)
port=$(grep "COMPONENT|$i|p4d-mc|" "$HTCfgFile"|cut -d '|' -f 8)
if [[ $port == "ssl:"* ]]; then
export P4PORT="ssl:$host:${port#ssl:}"
else
export P4PORT="$host:$port"
fi
TicketExpiration=$(p4 -ztag -F %TicketExpiration% -p $P4PORT -u $P4USER login -s 2>/dev/null)
if [[ -n "$TicketExpiration" && "$TicketExpiration" -gt "$((60*60*24*31))" ]]; then
TicketStatus="OK with long-term ticket."
elif [[ -n "$TicketExpiration" && "$TicketExpiration" -gt "60" ]]; then
TicketStatus="OK with short-term ticket ($TicketExpiration seconds)."
else
TicketStatus="ERROR, ticket not available for $P4USER on $P4PORT."
PreflightAccessOK=0
fi
if [[ "$TicketStatus" == "OK"* ]]; then
ProtectCheck=$(p4 -ztag -F %Protections0% -u $P4USER -p $P4PORT protect -o 2>/dev/null)
if [[ -n "$ProtectCheck" ]]; then
SuperAccessStatus="OK"
else
SuperAccessStatus="Error"
PreflightAccessOK=0
fi
else
SuperAccessStatus="Unknown"
PreflightAccessOK=0
fi
printf "%-12s %-24s %-5s %-s\n" "$i" "$P4PORT" "$SuperAccessStatus" "$TicketStatus"
done
if [[ "$PreflightAccessOK" -eq 1 ]]; then
msg "\nVerified: Access OK. All instances can be accessed as super from this host."
else
errmsg "Preflight Access check failed. See results above."
OverallReturnStatus=1
fi
msg "Environment Preflight Check\n${H2}"
for i in $InstanceList; do
InstanceEnvFile=/p4/common/config/p4_${i}.vars
if [[ ! -r "$InstanceEnvFile" ]]; then
errmsg "Missing instance-specific environment file: $InstanceEnvFile"
PreflightEnvOK=0
fi
done
if [[ "$PreflightEnvOK" -eq 1 ]]; then
msg "\nVerified: Environment is OK, all instances have environment files."
else
errmsg "Preflight Environment check failed. See missing files listed above."
OverallReturnStatus=1
fi
fi
if [[ "$Program" != Unset ]]; then
if [[ $Program == /* || $Program == \.* ]]; then
# Non-path depedent, absolute or relative path specified.
ProgramPath=$Program
else
# Path-dependent path specified.
ProgramPath=$(which $Program)
fi
[[ -z "$ProgramPath" ]] && \
bail "The specified program [$Program] cannot be found. Aborting.\n"
[[ ! -r "$ProgramPath" ]] && \
bail "The specified program [$Program] cannot be found. Aborting.\n"
[[ ! -x "$ProgramPath" ]] && \
bail "The specified program [$Program] is not executable. Aborting.\n"
for i in $InstanceList; do
vmsg "For instance $i calling $Program $ProgramArgs"
InstanceEnvFile=/p4/common/config/p4_${i}.vars
if [[ -r "$InstanceEnvFile" ]]; then
if [[ $NO_OP -eq 0 ]]; then
source "$SDPEnvFile" "$i"
export P4ENVIRO=/dev/null/.p4enviro
export P4CONFIG=FileThatDoesNotExist
export P4TICKETS=/p4/hms/.p4tickets
export P4TRUST=/p4/hms/.p4trust
host=$(grep "COMPONENT|$i|p4d-mc|" "$HTCfgFile"|cut -d '|' -f 5)
port=$(grep "COMPONENT|$i|p4d-mc|" "$HTCfgFile"|cut -d '|' -f 8)
if [[ $port == "ssl:"* ]]; then
export P4PORT="ssl:$host:${port#ssl:}"
else
export P4PORT="$host:$port"
fi
$Program $ProgramArgs
ExitCode=$?
if [[ $ExitCode -ne 0 ]]; then
warnmsg "Non-zero exit code returned: $ExitCode"
OverallReturnStatus=1
fi
else
msg "NO_OP: For instance $i, would run: $Program $ProgramArgs"
fi
else
errmsg "Missing instance-specific environment file: $InstanceEnvFile"
OverallReturnStatus=1
fi
done
fi
if [[ $OverallReturnStatus -eq 0 ]]; then
msg "${H}\nAll processing completed successfully.\n"
else
msg "${H}\nProcessing completed, but with errors. Scan above output carefully.\n"
fi
msg "That took $(($SECONDS/3600)) hours $(($SECONDS%3600/60)) minutes $(($SECONDS%60)) seconds.\n"
# See the terminate() function, which is really where this script exits.
exit $OverallReturnStatus