#!/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" # shellcheck disable=SC1090 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 export VERBOSITY=3 #============================================================================== # Local Functions #------------------------------------------------------------------------------ # Function: terminate function terminate { # Disable signal trapping. trap - EXIT SIGINT SIGTERM vvmsg "$THISSCRIPT: EXITCODE: $ErrorCount" # Stop logging. [[ "${P4U_LOG}" == off ]] || stoplog # Don't litter. cleanTrash # With the trap removed, exit. exit "$ErrorCount" } #------------------------------------------------------------------------------ # Rotate log file and compress with gzip. #------------------------------------------------------------------------------ rotate_log () { [[ "${P4U_LOG}" == off ]] && return 0 declare Timestamp= declare RotatedLog= if [[ -f "$P4U_LOG" ]]; then Timestamp="$(date +'%Y-%m-%d_%H-%M-%S')" RotatedLog="${P4U_LOG}.${Timestamp}" mv -f "$P4U_LOG" "$RotatedLog" ||\ bail "Failed to move log file aside with: mv -f $P4U_LOG $RotatedLog" gzip "$RotatedLog" ||\ warnmsg "Ignoring failure to gzip $RotatedLog." fi } #------------------------------------------------------------------------------ # 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 errmsg "Bad Usage: $errorMessage" fi echo "USAGE for $THISSCRIPT v$Version: $THISSCRIPT -i <instance> [-c] [-d] [-L <log>] [-si] [-v<n>] [-n] [-D] or $THISSCRIPT [-h|-man|-V] " if [[ $style == -man ]]; then echo -e " DESCRIPTION: This script wraps the 'p4 ldapsync' command, working with '-g' (group management) mode. It a will abort if this fails. The basic idea for this script is that all Perforce users must by in either or both of these two groups to have an account: * $LDAPP4UsersGroup * $NonLDAPP4UsersGroup This script does the following: * Calls 'p4 ldapsync -g' to update the Perforce group $LDAPP4UsersGroup from the LDAP group of the same name. * Calls 'p4 ldapsync -g' to update the Perforce group $LDAPP4AdminsGroup from the LDAP group of the same name. * Detects users missing from Perforce, i.e. if an account is listed in either of the two group noted above, but does not have a Perforce account. Missing users will be reported, and optionally added with '-c'. * Detects extra users in Perforce, i.e. if any exist in Perforce but are not listed in either of the two groups noted above. Extra users will be reported. If '-d' is specified, extra users will be deleted, along with all of their workspaces (according to the Owner field of the client spec). Workspace removal is done using a command like: p4 client -df -Fs <workspace_name> These flags will blast checkouts and deleted sheled files associated with the workspace. To preserve shelved files, they must be unshelved by another user in another workspace prior to running this script. Workspace removal can fail in some edge cases, such as if the user has files checked out to a workspace for which they are not the listed Owner. Manual corrective action is necessary in these cases. If workspace removal fails, user removal will fail. WORING AROUND A SPECIFIC LDAP INCOMPATIBILITY: This custom script is needed in scenarios where the command 'p4 ldapsync' does not work in '-u' (update) mode. This can happen if certain data elements in LDAP contain characters incompatible with Perforce (e.g. '#' chars). In cases where 'ldapsync -u' works as expected, this script's primary value is the admin communication emails and workflow related to notifying about users to be cleane up, or actively removing such users. OPTIONS: -i <instance> Specify the SDP instance name. If the '-i' flag is omitted, the value is derived from the \$SDP_INSTANCE environment variable. If \$SDP_INSTANCE is not defined, then '-i <instance>' is required. -c Create users that exist in either of the two user groups mentioned above but which do not exist in Perforce. Users will be added with an Email field value of <userid>@<domain>, where domain is determined from the MAILFROM setting, which evaluates to @$EmailDomain. The FullName field is set to the same value as the userid, and can be adjusted by the user manually. The AuthMethod will be set to whatever the default is (per the auth.default.method configurable). By default, without '-c', users to be added are reported, but no action is taken. As a safety feature, a maximum of $MaxUsersToAdd missing users will be added on any one invocation of this script. To add more users, call this script as many times as needed. -d Delete extra uses and any client specs (workspaces) for which they are the listed Owner. By default, without '-d', users to be added are reported, but no action is taken. As a safety feature, a maximum of $MaxUsersToDelete extra users will be deleted on any one invocation of this script. To delete more users, call this script as many times as needed. -v<n> Set verbosity 1-5 (-v1 = quiet, -v5 = highest). 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'. -si Operate silently. All output (stdout and stderr) is redirected to the log only; no output appears on the terminal. -n No-Op. Prints certain commands instead of running them. Some commands, such as the 'p4 ldapsync -g' command that does not affect data, are executd regardless of whether '-n' is used. Using '-n' will prevent creation and removal of users even if '-c' and/or '-d' are used. -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. EXAMPLES: Sample call from cron: manage_users_from_ldap.sh -i 1 -c " fi exit 1 } #============================================================================== # Command Line Processing export SDP_INSTANCE="${SDP_INSTANCE:-Unset}" declare LDAPP4AdminsGroup="AD-PerforceHelixAdmins" declare LDAPP4UsersGroup="AD-PerforceHelixUsers" declare NonLDAPP4UsersGroup="Non-LDAP-Users" declare DefaultMailFrom=${MAILFROM:-P4Admin@p4demo.com} declare EmailDomain=${DefaultMailFrom##*@} declare TmpFile= declare -i CreateMissingUsers=0 declare -i DeleteExtraUsers=0 declare -i MaxUsersToAdd=5 declare -i MaxUsersToDelete=5 declare -i UsersToAddCount=0 declare -i UsersToDeleteCount=0 declare -i ClientsToDeleteCount=0 declare -i UserAddErrorCount=0 declare -i UserDeleteErrorCount=0 declare -i ClientDeleteErrorCount=0 declare -i ErrorCount=0 declare -A P4Users declare -A LDAPUsers declare -a UsersToAdd declare -a UsersToDelete declare -i i=0 declare -i shiftArgs=0 set +u while [[ $# -gt 0 ]]; do case $1 in (-i) SDP_INSTANCE=$2; shiftArgs=1;; (-c) CreateMissingUsers=1;; (-d) DeleteExtraUsers=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;; (-si) SilentMode=1;; (-n) export NO_OP=1;; (-D) set -x;; # Debug; use '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 [[ "$SDP_INSTANCE" == Unset ]] && \ bail "The '-i <instance>' is required unless \$SDP_INSTANCE is defined." # shellcheck disable=SC1091 source /p4/common/bin/p4_vars "$SDP_INSTANCE" # Set variables that depend on SDP environment settings. export P4U_LOG="${LOGS}/manage_users_from_ldap.log" EmailDomain=${MAILFROM##*@} TmpFile=${P4TMP}/tmp.$$.$RANDOM #============================================================================== # Main Program trap terminate EXIT SIGINT SIGTERM if [[ "${P4U_LOG}" != off ]]; then rotate_log 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 msg "\nStarted ${0##*/} v$Version.\n" run "$P4BIN -s ldapsync -g $LDAPP4UsersGroup" \ "Updating Perforce group $LDAPP4UsersGroup from LDAP group of the same name." 1 1 ||\ bail "Failed to update group from LDAP." run "$P4BIN -s ldapsync -g $LDAPP4AdminsGroup" \ "Updating Perforce group $LDAPP4AdminsGroup from LDAP group of the same name." 1 1 ||\ bail "Failed to update group from LDAP." run "$P4BIN ldapsync -u -U adldap_reader" \ "Updating Perforce user data from LDAP." 1 1 ||\ bail "Failed to update user data from LDAP." for user in $($P4BIN -ztag group -o $LDAPP4UsersGroup | grep '^\.\.\. Users' | awk '{print $3}'); do LDAPUsers[$user]="$user" done for user in $($P4BIN -ztag group -o $NonLDAPP4UsersGroup | grep '^\.\.\. Users' | awk '{print $3}'); do LDAPUsers[$user]="$user" done for user in $($P4BIN -ztag -F %User% users); do P4Users[$user]="$user" done msg "${H2}\nFinding LDAP users to add to Perforce." i=0 for user in "${LDAPUsers[@]}"; do if [[ -z "${P4Users[$user]:-}" ]]; then UsersToAdd[$i]="$user" i+=1 fi done if [[ "$i" -eq 0 ]]; then msg "No users need to be added." else msg "Found $i users missing from Perforce." if [[ "$CreateMissingUsers" -eq 1 ]]; then if [[ "$i" -le "$MaxUsersToAdd" ]]; then msg "Attempting to add all $i missing users." else msg "Attempting to add $MaxUsersToAdd of the $i missing users." fi fi for user in "${UsersToAdd[@]}"; do UsersToAddCount+=1 if [[ "$CreateMissingUsers" -eq 1 && "$UsersToAddCount" -gt "$MaxUsersToAdd" ]]; then warnmsg "Created max of $MaxUsersToAdd. Only reporting remaining users to add." CreateMissingUsers=0 fi echo -e "User: $user\\n\\nEmail: ${user}@${EmailDomain}\\n\\nFullName: $user\\n\\nAuthMethod: ldap\\n\\n" > "$TmpFile" ||\ bail "Failed to write to temp file: $TmpFile\n" if [[ "$CreateMissingUsers" -eq 1 ]]; then # shellcheck disable=SC2086 run "$P4BIN -s user -f -i < $TmpFile" \ "Creating user with this generated user spec:\n$(cat $TmpFile)\n" \ 1 1 ' saved' if [[ "$CMDEXITCODE" -ne 0 ]]; then errmsg "Failed to add user: $user\n" UserAddErrorCount+=1 ErrorCount+=1 fi else msg "Add user: $user" fi done fi msg "${H2}\nFinding extra Perforce users to remove that do not exist in LDAP or the group $NonLDAPP4UsersGroup." i=0 for user in "${P4Users[@]}"; do if [[ -z "${LDAPUsers[$user]:-}" ]]; then UsersToDelete[$i]="$user" i+=1 fi done if [[ "$i" -eq 0 ]]; then msg "No users need to be removed." else msg "Found $i extra users in Perforce." if [[ "$DeleteExtraUsers" -eq 1 ]]; then if [[ "$i" -le "$MaxUsersToDelete" ]]; then msg "Attempting to delete all $i extra users." else msg "Attempting to delete $MaxUsersToDelete of the $i extra users." fi fi for user in "${UsersToDelete[@]}"; do msg "Delete user: $user" UsersToDeleteCount+=1 if [[ "$DeleteExtraUsers" -eq 1 && "$UsersToDeleteCount" -gt "$MaxUsersToDelete" ]]; then warnmsg "Deleted max of $MaxUsersToDelete. Only reporting remaining users to delete." DeleteExtraUsers=0 fi for client in $("$P4BIN" -ztag -F %client% clients -u "$user"); do ClientsToDeleteCount+=1 if [[ "$DeleteExtraUsers" -eq 1 ]]; then run "$P4BIN -s client -df -Fs $client" \ "\\n\\tDelete client: $client" 1 1 ' deleted' if [[ "$CMDEXITCODE" -ne 0 ]]; then errmsg "Failed to remove client: $client\n" ClientDeleteErrorCount+=1 ErrorCount+=1 fi else msg "\\n\\tDelete client: $client" fi done run "$P4BIN -s user -d -f $user" \ "Delete user: $user" 1 1 ' deleted' if [[ "$CMDEXITCODE" -ne 0 ]]; then errmsg "Failed to remove user: $user\\n" UserDeleteErrorCount+=1 ErrorCount+=1 fi done fi msg "${H2}\\nSummary:\\n\\tUsers to add: $UsersToAddCount\\n\\tUsers to remove: $UsersToDeleteCount\\n\\tClients to remove: $ClientsToDeleteCount\\n\\tUser Add Errors: $UserAddErrorCount\\n\\tUser Delete Errors: $UserDeleteErrorCount\\n\\tClient Delete Errors: $ClientDeleteErrorCount\\n\\tTotal Errors: $ErrorCount\\n\\n" if [[ $ErrorCount -eq 0 ]]; then msg "${H}\\nAll processing completed successfully.\\n" else msg "${H}\\nProcessing completed, but with $ErrorCount errors detected. Scan above output carefully.\\n" fi # Illustrate using $SECONDS to display runtime of a script. 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 $ErrorCount
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#9 | 25694 | C. Thomas Tyler | Adjusted automatic log removal to remove logs older than 7 days. | ||
#8 | 25693 | C. Thomas Tyler |
Fixed bug where a combination of errors and bad usage could cause users to be deleted even if '-d' weren't specified. Added '-gA' flag to sync all LDAP-connected groups. Significant documentation update. Shellcheck v0.6.0 compliant. |
||
#7 | 25616 | C. Thomas Tyler |
Static-analysis (shellcheck) driven tweaks, and doc tweaks. Changed default group name prefix from AD- to LDAP-. |
||
#6 | 25412 | C. Thomas Tyler |
Static analysis done with shellcheck, driving various improvements. No functional changes. |
||
#5 | 25411 | C. Thomas Tyler |
Added support for group of Admin users to update from AD in addition to the regular Users group. Updated docs. |
||
#4 | 25327 | C. Thomas Tyler |
Enhanced usage message by setting default value for email domain. Doc change; no functional change. |
||
#3 | 25326 | C. Thomas Tyler |
Documentation tweaks. No functional change. |
||
#2 | 24657 | C. Thomas Tyler | Fixed "call from cron with no environment" issue. | ||
#1 | 24646 | C. Thomas Tyler | Added ldap script. |