Apply or unapply a patch to remote Evergreen hosts
authorSteven Chan <schan@sitka.bclibraries.ca>
Tue, 4 Dec 2012 00:49:05 +0000 (16:49 -0800)
committerSteven Chan <schan@sitka.bclibraries.ca>
Tue, 4 Dec 2012 00:49:05 +0000 (16:49 -0800)
patching/README.txt [new file with mode: 0644]
patching/patch-eg.sh [new file with mode: 0755]

diff --git a/patching/README.txt b/patching/README.txt
new file mode 100644 (file)
index 0000000..ed5e3fe
--- /dev/null
@@ -0,0 +1,70 @@
+README.txt
+
+DESCRIPTION:
+Apply or unapply a patch to remote Evergreen hosts
+
+USAGE:
+./patch-eg.sh -p ID [ -b backup_label ] [ -P rule ] [ -y ] [ -R ] [ -h ] host1 host2...
+
+OPTIONS:
+    -p SHA1 ID of patch to apply (mandatory)
+    -b Backup label of patch
+       Defaults to patch ID
+    -P Patch rule (web, xul, perl) to determine patch directory, user, and strip level
+       Defaults to 'web'
+    -y Yes, apply the patch instead of doing a dry run
+    -R Reverse or undo the patch instead of applying it
+    -h Print this message and exit
+
+EXAMPLES:
+
+Use the script to apply and unapply a Perl patch on two of the production servers:
+
+1. Perform a dry run to see what it will do
+
+# patch-eg.sh -p 1234567 -b rt12345 -P perl app1-1 app1-2
+
+2. Rerun it using the -y option to actually apply the patch
+
+# patch-eg.sh -p 1234567 -b rt12345 -P perl -y app1-1 app1-2
+
+3. To unapply the patch, run the script with the -R option as well
+
+# patch-eg.sh -p 1234567 -b rt12345 -P perl -y -R app1-1 app1-2
+
+NOTES:
+
+The script logs in as 'sitkastaff' via SSH to each host, and then runs the
+patch command via sudo.  Consequently, the script can ask for an account
+password many times.  To avoid having to repeatedly type a password, run the
+script on the local server as 'sitkastaff' and have the account's public SSH
+key installed on the target servers.
+
+As a convenience, the above procedure has been done on 'sitkastaff@coconut', so
+that if the script is run on 'coconut' as 'sitkastaff', the script will only
+ask for a sudo password.  Moreover, requiring a sudo password could be avoided
+if the PAM module PAM_SSH_AGENT_AUTH is installed and configured on target
+hosts.
+
+A limitation to the script is that it cannot easily apply a patch that mixes up
+changes to Perl files, XUL files, or Javascript files, because they would be
+installed on directories differently from their directories in the source
+directory.
+
+TO DO:
+
+1. Handle duplicate hostnames specified on the command line. For example,
+
+# patch-eg.sh -p 1234567 app1-1 app1-1
+
+would apply the patch to app1-1 once.
+
+2. Perform filename expansion on hostnames. For example
+
+# patch-eg.sh -p 1234567 -P perl app[123]-[12]
+
+would apply the Perl patch to six servers, app1-1, app1-2, app2-1, app2-2,
+app3-1, app3-2.
+
+3. Is it possible to get location of patch repository from git configuration?
+
diff --git a/patching/patch-eg.sh b/patching/patch-eg.sh
new file mode 100755 (executable)
index 0000000..d236888
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/bash
+# Author: schan@sitka.bclibraries.ca
+
+Help() {
+       cat<<____
+
+DESCRIPTION:
+Apply or unapply a patch to remote Evergreen hosts
+
+USAGE:
+./patch-eg.sh -p ID [ -b backup_label ] [ -P rule ] [ -y ] [ -R ] [ -h ] host1 host2...
+
+OPTIONS:
+    -p SHA1 ID of patch to apply (mandatory)
+    -b Backup label of patch
+       Defaults to patch ID
+    -P Patch rule (web, xul, perl) to determine patch directory, user, and strip level
+       Defaults to 'web'
+    -y Yes, apply the patch instead of doing a dry run
+    -R Reverse or undo the patch instead of applying it
+    -h Print this message and exit
+____
+}
+
+# Parse command line for run-time options
+while getopts 'p:b:P:yRh' Option; do
+       case $Option in
+       (p) Patch_ID=$OPTARG;;
+       (b) Patch_Backup=$OPTARG;;
+       (P) Patch_Rule=$OPTARG;;
+       (y) Runit='';;
+       (R) Reverse='-R';;
+       (h) Help && exit 0;;
+       esac
+done
+shift $(( $OPTIND - 1 ))
+
+# Calculate default options
+Remote_Host=${1:?}
+Patch_ID=${Patch_ID:?}
+Patch_Backup=${Patch_Backup:-$Patch_ID}
+Patch_Rule=${Patch_Rule:-web}
+Runit=${Runit-'--dry-run'}
+Reverse=${Reverse-''}
+
+# Define function to echo the intended action for a host
+Echo_Intention() {
+       [[ $Runit == '--dry-run' ]] && echo -ne '\nDry run: '
+       if [[ $Reverse == '-R' ]]; then echo -n 'Reversing '; else echo -n 'Applying '; fi
+       echo -e "patch $Patch_ID to $1 and backing up as $Patch_Backup"
+}
+
+# Associate the patch rule to patch path, strip level, and sudo user
+# If patch rule is 'perl', the patch path will be dynamically determined from the host
+declare -A Rule
+case $Patch_Rule in
+    (web)  Rule=( [path]=/srv/openils/var             [level]=2 [sudo]=opensrf );;
+    (xul)  Rule=( [path]=/srv/openils/var/web/xul     [level]=4 [sudo]=opensrf );;
+    (perl) Rule=( [path]=/usr/local/share/perl/5.14.2 [level]=5 [sudo]=root    );;
+esac
+
+
+# Set local environment
+
+# Domain name of patch host (Evergreen)
+#declare -r SOURCE="git.evergreen-ils.org/?p=Evergreen.git"
+# Domain name of patch host (Sitka)
+declare -r SOURCE="git.sitka.bclibraries.ca/gitweb/?p=sitka/evergreen.git"
+# Domain name of target hosts
+declare -r TARGET=sitka.bclibraries.ca
+# Name of ssh and sudo user
+declare -r LOGIN=sitkastaff
+
+
+# Calculate command text strings
+declare -r SUDO="sudo -p '' -S -u ${Rule[sudo]}"
+declare -r Patch_URL="http://$SOURCE;a=patch;h=$Patch_ID"
+declare -r WGET="wget -q -O - '$Patch_URL'"
+declare -r Backup_Policy="-b -B .patch/$Patch_Backup/ -V simple"
+declare -r PATCH="patch $Runit $Reverse -r - $Backup_Policy -p${Rule[level]}"
+declare -r OPENILS_DIR="perl -MOpenILS -e 'print \$INC{qq(OpenILS.pm)}'"
+
+# Define function to find directory name of the OpenILS Perl module at a host
+Perl_Dir() { dirname $(ssh "$LOGIN@$1.$TARGET" $OPENILS_DIR); }
+
+# Define function to patch a directory at a host via ssh sudo
+Patch_Dir() { ssh "$LOGIN@$1.$TARGET" "$SUDO bash -c \"$WGET | $PATCH -d $2 \""; }
+
+# Ask the user once for sudo password
+read -s -p "$LOGIN's sudo password: " Password ; echo
+
+for Host; do
+       Echo_Intention $Host
+       [[ $Patch_Rule == 'perl' ]] && Rule[path]=$(Perl_Dir $Host)
+       Patch_Dir $Host ${Rule[path]} <<< $Password
+done