#!/bin/bash
#==============================================================================
set -u

#==============================================================================
# Declarations and Environment

# Version ID Block. Relies on +k filetype modifier.
#------------------------------------------------------------------------------
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/dev_c2s/Unsupported/Maintenance/import_users_and_groups.sh#2 $ $Change: 31472 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"

declare -i ErrorCount=0
declare -i ImportUserErrorCount=0
declare -i ImportUserCount=0
declare -i ImportUserOKCount=0
declare -i ImportGroupErrorCount=0
declare -i ImportGroupCount=0
declare -i ImportGroupOKCount=0
declare -i SkipUserByTypeCount=0
declare -i SkipExistingUserCount=0
declare -i NoOp=0
declare -i Debug=0
declare -i i=0
declare ThisScript=${0##*/}
declare CmdLine="$0 $*"
declare ThisUser=
declare UserSpecFile=
declare GroupSpecFile=
declare TmpDir=
declare ThisHost="${HOSTNAME%%.*}"
declare SDPInstance=
declare -A ExistingUsers
declare TargetServerPort=
declare TmpFile=
declare User=
declare Type=
declare Group=
declare FoundUsers=
declare FoundGroups=
declare P4CBin=/p4/common/bin
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare Log=
declare OldLog=

#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function dbg () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "${2:-1}"; }

#------------------------------------------------------------------------------
# Function: terminate
function terminate
{
   # Disable signal trapping.
   trap - EXIT SIGINT SIGTERM

   [[ "$Log" == "off" ]] || msg "Log is: $Log\\n${H1}"

   # With the trap removed, exit.
   exit "${ErrorCount}"
}

#------------------------------------------------------------------------------
# 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
# error, usually $1 should be -h so that the longer usage message doesn't
# obscure 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
      msg "\\n\\nUsage Error:\\n\\n$errorMessage\\n\\n"
   fi

   msg "USAGE for $ThisScript version $Version:

$ThisScript -t <TargetPort> [-i <SDPInstance>] [-L <log>] [-n] [-D]

or

$ThisScript [-h|-man|-V]
"
   if [[ $style == -man ]]; then
      msg "
DESCRIPTION:
	This script targets a designated server and pulls user and group
	spec data from that, and loads them into the local server.

	Existing users are skipped.  Groups are always re-added to capture
	any group membership changes.

	This script might be useful after a perfmerge is done in which
	the servers to be merged were using a P4AUTH server, and after
	which the P4AUTH is to be deprected.  This script can be used
	following the perfmerge to target the P4AUTH server running from
	the environment of the post-perfmerge server.

OPTIONS:
 -t <TargetPort>
	Specify the target server port.  If SSL is enabled, this server
	must be trusted, with a valid trust enrtry in the P4TRUST file
	defined for the local SDP environment.  A valid login ticket must
	be available in the P4TICKETS file defined for the local SDP
	environment.

	This paraemter is required.

 -i <SDPInstance>
	Specify the local SDP instance to which user and group specs
	will be imported.  This is required unless the SDP_INSTANCE
	environment variable is defined; it will be
	if the standard SDP shell environment is loaded.

 -L <log>
	Specify the path to a log file, or the special value 'off' to disable
	logging.  By default, all output (stdout and stderr) goes to:

	/p4/N/logs/${ThisScript%.sh}.log

	(where N is the SDP instance name).

	If a log by that name exists from prior runs, it is rotated to:
	/p4/N/logs/${ThisScript%.sh}.<timestamp>.log

	NOTE: This script is self-logging.  That is, output displayed on the screen
	is simultaneously captured in the log file.  Do not run this script with
	redirection operators like '> log' or '2>&1', and do not use 'tee.'

 -n	No-Op.  Prints commands instead of running them.

 -d     Enable debugging messages.

 -D     Set extreme debugging verbosity. Implies '-d'.

HELP OPTIONS:
 -h	Display short help message
 -man	Display man-style help message

EXAMPLES:
	Get a quick usage message:
	${ThisScript} -h

	Get the full manual page:
	${ThisScript} -man

	Normal Usage is with no arguments:
	${ThisScript}
"

   fi

   exit 1
}

#==============================================================================
# Command Line Processing

declare -i shiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-h) usage -h;;
      (-man) usage -man;;
      (-t) TargetServerPort="$2"; shiftArgs=1;;
      (-i) SDPInstance="$2"; shiftArgs=1;;
      (-L) Log="$2"; shiftArgs=1;;
      (-n) NoOp=1;;
      (-d) Debug=1;;
      (-D) Debug=1; set -x;; # Extreme Debug; use bash 'set -x' mode.
      (*) usage -h "Unknown arg ($1).";;
   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

[[ -n "$TargetServerPort" ]] ||\
   usage -h "Missing required '-t <TargetServerPort>' parameter."

if [[ -z "$SDPInstance" ]]; then
   if [[ -n "${SDP_INSTANCE:-}" ]]; then
      SDPInstance="$SDP_INSTANCE"
   else
      bail "The '-i <SDPInstance>' option is required if the SDP shell environmnt s not loaded (and SDP_INSTANCE is not set)."
   fi
fi
[[ -z "$Log" ]] && Log="/p4/${SDPInstance}/logs/${ThisScript%.sh}.log"

#==============================================================================
# Main Program

trap terminate EXIT SIGINT SIGTERM

if [[ "$Log" != off ]]; then
   if [[ -e "$Log" ]]; then
      # shellcheck disable=SC2012
      OldLogTimestamp="$(ls -l --time-style +'%Y%m%d-%H%M%S' "$Log" | awk '{print $6}')"
      OldLog="/p4/${SDPInstance}/logs/${ThisScript%.sh}.$OldLogTimestamp.log"
      mv -f "$Log" "$OldLog" || bail "Could not do: mv -f \"$Log\" \"$OldLog\""
      msg "Rotated $Log to $OldLog"
   fi

   touch "$Log" || bail "Couldn't touch log file [$Log]."

   exec > >(tee "$Log")
   exec 2>&1

   msg "${H1}\\nLog is: $Log\\n"
fi

ThisUser=$(id -n -u)
msg "Started $ThisScript version $Version as $ThisUser@$ThisHost at $(date)\\nCommand Line: $CmdLine"

msg "${H2}\\nStarting Preflight Checks."

msg "Loading SDP Environment for instance $SDPInstance."
# shellcheck disable=SC1091
source "$P4CBin/p4_vars" "$SDPInstance" ||\
   errmsg "Failed to load SDP shell environment for instance $SDPInstance."

msg "Checking access to target server, P4PORT=$TargetServerPort."
if p4 -p "$TargetServerPort" login -s; then
   msg "Verified: Login to target server is valid."
else
   errmsg "Login to target server could not be verified."
fi

if p4 login -s; then
   msg "Verified: Login to local server is valid."
else
   errmsg "Login to local server could be verified."
fi

if [[ "$ErrorCount" -eq 0 ]]; then
   msg "Preflight checks OK."
else
   bail "Aborted due to failed preflight checks."
fi

msg "${H1}\\nImport Users"

TmpFile=$(mktemp)
dbg "Getting list of local users for instance $SDPInstance with:\\np4 -ztag -F %User% users -a .LT. $TmpFile"
if p4 -ztag -F %User% users -a > "$TmpFile"; then
   if [[ -s "$TmpFile" ]]; then
      i=0
      while read -r User; do
         ExistingUsers[$User]=1
         i+=1
      done < "$TmpFile"
   else
      bail "Found no existing users on local instance $SDPInstance server."
   fi
else
   bail "Could not get list of users for local instance $SDPInstance server."
fi

msg "Found ${#ExistingUsers[@]} existing users on server."

dbg "Getting list of users from P4AUTH server with:\\np4 -p $TargetServerPort -ztag -F %Type%:%User% users .GT. $TmpFile"
if p4 -p "$TargetServerPort" -ztag -F %Type%:%User% users > "$TmpFile"; then
   if [[ -s "$TmpFile" ]]; then
      FoundUsers=$(grep -c '^standard:' "$TmpFile")
      msg "Found $FoundUsers standard users on target server."
   else
      bail "Found no users on target server to import."
   fi
else
   bail "Could not get list of users and types from target server."
fi

TmpDir=$(mktemp -d)
if [[ ! -d "$TmpDir" ]]; then
   msg "Creating temp dir [$TmpDir]"
   mkdir -d "$TmpDir" || bail "Could not create temp dir."
fi

while read -r TypeAndUser; do
   Type=${TypeAndUser%%:*}
   User=${TypeAndUser#*:}

   if [[ "$Type" != "standard" ]]; then
      msg "Skipping user $User of non-standard type [$Type]."
      SkipUserByTypeCount+=1
      continue
   fi

   if [[ "${ExistingUsers[$User]:-}" == "1" ]]; then
      msg "Skipping existing user $User."
      SkipExistingUserCount+=1
      continue
   fi

   msg "Adding user $User."
   ImportUserCount+=1
   UserSpecFile="$TmpDir/$User.user.p4s"
   p4 -p "$TargetServerPort" user -o "$User" | grep -E -v '^(#|Access:|Update:)' > "$UserSpecFile"
   if grep -q ^User: "$UserSpecFile"; then
      dbg "BEGIN USER SPEC FILE $UserSpecFile:\\n${H2}\\n$(cat "$UserSpecFile")\\n${H2}"
      if [[ "$NoOp" -eq 0 ]]; then
         if p4 user -f -i < "$UserSpecFile"; then
            ImportUserOKCount+=1
         else
            errmsg "Failed to add user $User with this spec:\\nBEGIN USER SPEC FILE $UserSpecFile:\\n${H2}\\n$(cat "$UserSpecFile")\\n${H2}"
            ImportUserErrorCount+=1
         fi
      else
         msg "NO_OP: Would add user $User"
         ImportUserOKCount+=1
      fi
   else
      errmsg "Could not generate user spec for user $User."
   fi

done < "$TmpFile"

dbg "Getting list of groups from P4AUTH server with:\\np4 -p $TargetServerPort -ztag -F %group% groups .GT. $TmpFile"
if p4 -p "$TargetServerPort" -ztag -F %group% groups > "$TmpFile"; then
   if [[ -s "$TmpFile" ]]; then
      if mv -f "$TmpFile" "${TmpFile}.2"; then
         if sort -u "${TmpFile}.2" > "$TmpFile"; then
            FoundGroups=$(wc -l "$TmpFile" | awk '{print $1}')
            msg "Found $FoundGroups groups on target server."
         else
            bail "Could not sort group spec list file with: sort -u ${TmpFile}.2 .GT. $TmpFile"
         fi
      else
         bail "Could not move group spec list file with: mv -f $TmpFile ${TmpFile}.2"
      fi
   else
      bail "Found no groups on target server to import."
   fi
else
   bail "Could not get list of groups from target server."
fi

while read -r Group; do
   msg "Adding group $Group."
   ImportGroupCount+=1
   GroupSpecFile="$TmpDir/$Group.group.p4s"
   p4 -p "$TargetServerPort" group -o "$Group" | grep -E -v '^(#|Access:|Update:)' > "$GroupSpecFile"
   if grep -q ^Group: "$GroupSpecFile"; then
      dbg "BEGIN GROUP SPEC FILE $GroupSpecFile:\\n${H2}\\n$(cat "$GroupSpecFile")\\n${H2}"
      if [[ "$NoOp" -eq 0 ]]; then
         if p4 group -i < "$GroupSpecFile"; then
            ImportGroupOKCount+=1
         else
            errmsg "Failed to add group $Group with this spec:\\nBEGIN USER SPEC FILE $GroupSpecFile:\\n${H2}\\n$(cat "$GroupSpecFile")\\n${H2}"
            ImportGroupErrorCount+=1
         fi
      else
         msg "NO_OP: Would add group $Group"
         ImportGroupOKCount+=1
      fi
   else
      errmsg "Could not generate group spec for group $Group."
   fi

done < "$TmpFile"


rm -rf "$TmpDir"

msg "${H2}\\nSummary:
   User Adds Attempted:        $ImportUserCount
   Users Added OK              $ImportUserOKCount
   User Add Errors:            $ImportUserErrorCount
   Group Updates Attempted:    $ImportGroupCount
   Group Updates Attempted OK: $ImportGroupOKCount
   Group Update Errors:        $ImportGroupErrorCount
   Total Errors:               $ErrorCount
   Non-standard users skipped: $SkipUserByTypeCount
   Pre-existing users skipped: $SkipExistingUserCount
"

if [[ "$ErrorCount" -eq 0 ]]; then
   msg "All processing completed successfully."
else
   errmsg "Processing complete, but $ErrorCount errors."
fi

msg "\\nTotal $ThisScript processing took $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\\n"

exit "${ErrorCount}"
