PostReceive Hooks
Git has the concept of hooks
These are basically BASH scripts that are run on certain events. The one we are talking about here is called the post receive hook.
As these hook files contain valuable code, we version them like anything else. These are tracked in a repo called post-receive-hooks
and are checked out in the /home/ec
folder in the gitBareRepos
container
Writing Post-Receive Hooks¶
The post receive hook is like any other BASH script with the following needing to be born in mind:
The script does not take arguments, only STDIN¶
The post receive hook does not receive numbered arguments like a normal BASH script. Instead we have to read standard input. The established best practice for this looks like:
#!/usr/bin/env bash
# example post receive hook
while read oldrev newrev refname
do
echo "Processing Branch: $refname"
# do stuff here
done
The script is run from the repo root¶
When pushing to a bare repository for example, the workding directory is the main repo folder, i.e the folder that contains the hooks folder that in turn contains your post-receive hook.
Using a lock file¶
For expensive operations, you might want to use a lock file to ensure that multiple process are not kicked off.
For a good example of how to do this, see this SO answer and this gist
Running Tasks in Parallel and in the Background¶
In order to make git pushes quick and painless, especially if you have multiple processes being kicked off, then it makes sense to background those processes.
The way to do that is use the nohup {cmd} &> /dev/null &
pattern. This means the script will continue executing once we have "hung up" from the process. The process output is all redirected to /dev/null
, i.e totally discared and the process is disowned.
This will allow the process to be started, but then detatched from the post receive hook execution itself, so that the terminal for the developer who pushed is released very quickly.
For example, this post receive hook kicks off three separate actions:
#!/usr/bin/env bash
pwd
repoName="$(pwd | grep -P -o '(?<=/repos/)(.+)')"
bitbucketSlug="git@bitbucket.org:edmondscommerce/${repoName////-}.git"
hooksDir=/home/ec/post-receive-hooks
echo "
Mirror to Bitbucket: ${bitbucketSlug}
";
nohup bash "${hooksDir}/bitbucket-sync.bash" "$bitbucketSlug" &> /dev/null &
isMaster=no
while read oldrev newrev refname
do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "master" == "$branch" ]; then
isMaster=yes
fi
done
if [[ "yes" == "$isMaster" ]]
then
echo "
Triggering CI Build for $repoName
"
nohup bash "${hooksDir}/ci-runner.bash" "$repoName" "192.168.236.130" &> /dev/null &
else
echo "
branch $branch has not triggered a CI build
"
fi
echo "
Updating Satis
"
nohup bash "${hooksDir}/edmonds/satis-update.bash" &> /dev/null &
The output when git pushing to a repo with this hook looks something like:
✔ /var/www/vhosts/dev [master ↑·1|✔]
09:33 $ git push
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (11/11), 1.32 KiB | 0 bytes/s, done.
Total 11 (delta 6), reused 0 (delta 0)
remote: /home/ec/repos/edmonds/dev
remote:
remote: Mirror to Bitbucket: git@bitbucket.org:edmondscommerce/edmonds-dev.git
remote:
remote:
remote: Triggering CI Build for edmonds/dev
remote:
remote:
remote: Updating Satis
remote:
To ssh://gitBare/home/ec/repos/edmonds/dev
004905d..182a532 master -> master
The above message is output almost instantaneously.
When doing this approach, it makes sense to confirm success by sending a Slack message
Example Post Receive Hooks¶
Bitbucket Sync¶
This hook script will push the gitBare repo to it's corresponding Bitbucket repo
Here is the usage:
✔ /var/www/vhosts/post-receive-hooks [master L|✚ 1]
10:10 $ bash bitbucket-sync.bash
===========================================
bitbucket-sync.bash
===========================================
BitBucket Sync
Usage:
./bitbucket-sync.bash [bitBucketSlug]
Example:
./bitbucket-sync.bash git@bitbucket.org/orgname/repo-name.git
CI Runner¶
This is hook will trigger the CI process in your specified container
You must pass in the repo name (path from ~/repos
), for example edmonds/dev
which is located in /home/ec/repos/edmonds/dev
on gitBareRepos
The second parameter is the IP address of the container which you want the CI process to run.
Third (optional) parameter is the path that the CI process should take place. This defaults to /var/www/vhosts/ci
Usage¶
ec@gitBareRepos bash ci-runner.bash
===========================================
gitBareRepos ci-runner.bash
===========================================
Edmonds CI
Usage:
./ci-runner.bash [repoName] [containerIP] (slackChannel - defaults to edmonds_marketing) (ciWorkDir - defaults to /var/www/vhosts/ci)
- repoName is the path from ~/repos in gitBareRepos
- containerIP is the internal IP, eg 192.168.236.130
eg:
./ci-runner.bash edmonds/dev 192.168.236.130
ci.bash¶
The CI Runner works on the assumption that there is a bash script in the root of your project called ci.bash
This should be a generic task runner that should be totally silent (actions should log to file) apart from a final success or failure message
That message will then be echoed out on slack
Here is an example ci.bash script:
#!/usr/bin/env bash
mkdir -p var
echo -n "QA Process Started at $(date)\n\n" > var/qa.log
composer install &>> var/qa.log
bin/qa &>> var/qa.log
qaExitCode=$?
if [[ "0" == "$qaExitCode" ]]
then
echo "CI Success :)"
exit 0
fi
echo "CI Fail :'("
tail var/qa.log
You can see that this script is:
- Making the var dir (a git ignored directory for transient stuff)
- Truncating a
qa.log
file and then setting the top to be the date - Running
composer install
- redirecting all output to the log file - Running the full QA process, again redirecting all output to the log file
- If the QA process exited with
0
, eg success, then echoing a success messsage - If the QA process failed, echoing a failure message and also the tail of the log fileG
Note
The message sent to Slack will have the "repoName" prepended, so you don't need to add that
Slack Ouptut¶
Here is an example slack message:
Limitations¶
The CI process currently only runs for pushes that update the master
branch
The tests are only run against master
It is possible to extend this to support other branches in the future shoudl we decide that we need it
Warning
Please read the above!
Slack Message Sender¶
To allow you to send Slack messages, there is another script in the post-receive-hooks
repo that handles this.
This script is located in the root of the post-receive-hooks
.
Here is the usage:
✔ /var/www/vhosts/post-receive-hooks [master L|✔]
09:56 $ bash slack-message-sender.bash
===========================================
slack-message-sender.bash
===========================================
Slack Message Sender
Usage:
./slack-message-sender.bash [message] [channel]
eg:
./slack-message-sender.bash 'slack, baby' 'general'
Note - you SHOULD NOT use the # in the channel name
Simply enough, this will post a slack message.
As it is expected that this will often be used for outputting CLI output, the message is wrapped with ``` for you already, so you do not need to do this.