---
title: Git post-receive hook for Puppet control repo updates
author: james
type: post
date: 2017-04-05T19:24:28+00:00
url: /2017/04/git-post-receive-hook-for-puppet-control-repo-updates/
categories:
  - Hacks
  - Tech

---
I made a fairly simple post-receive hook setup to automatically update my [Puppet][1] master when I push changes to my control repo. I keep the repo in gitolite, so I wanted to use a regular git hook rather than web hook magic (or even magicer Puppet Enterprise Code Manager magic).

My control repo itself is based on [the puppetlabs control-repo on github][2]. Essentially the idea is that every branch in the repo becomes a Puppet environment on your master, complete with automatically updated modules based on a Puppetfile. The [r10k][3] tool takes care of the heavy lifting here, and its documentation explains how it works in some detail.

But we don't have the patience for that! First, install r10k on your Puppet master and configure it to be able to yank your control repo in `/etc/puppetlabs/r10k/r10k.yaml`, something like this:

{{< highlight yaml >}}
:cachedir: '/var/cache/r10k'

:sources:
  :myrepo:
    remote: 'gitolite3@git.example.com:puppet'
    basedir: '/etc/puppetlabs/code/environments'
{{< / highlight >}}

Make sure you have SSH keys relationships set up so that you can pull the repo. Running `r10k deploy environment --verbose info` should let you see what&#8217;s going on. Once it works, continue on.

Create an SSH key on your git server as the user that runs git. In my case on Debian, that user is `gitolite3`, but just wherever you have your repo running.

Copy the public key and install it in your puppet master&#8217;s `/root/.ssh/authorized_keys`:

{{< highlight sh >}}
command="/usr/local/sbin/puppet-update.sh" ssh-ed25519 AAASOMEKEYMATERIAL gitolite3@git.example.com
{{< / highlight >}}

What's this `puppet-update.sh`, I hear you cry? I&#8217;m glad you asked:

{{< highlight sh >}}
#!/bin/bash

/usr/local/bin/r10k deploy environment $SSH_ORIGINAL_COMMAND
{{< / highlight >}}

Pretty straightforward. Obviously you&#8217;ll want to point to wherever you have r10k installed, and make sure the script is executable. This setup takes whatever command you try to run over ssh with this key and appends it to `/usr/local/bin/r10k deploy environment`. The security conscious may want to do some other sanity checks too. I did say it was simple!

The meat of the matter is the post-receive hook itself. This should go on your git server, inside the puppet control repo&#8217;s hooks directory. In my case this is `~gitolite3/repositories/puppet.git/hooks/post-receive`. It, too, should be executable.

{{< highlight sh >}}
#!/bin/bash

# post-receive hook to trigger r10k over ssh on updates to puppet control repo

# Set SSHTARGET in ~/.config/puppet-update to eg:
#   root@puppet.example.com

# Caveat: If you push a branch deletion and an updated Puppetfile in the same
# push command, the updates to the Puppetfile will not be deployed. You'll have
# to run manually or make another change to the Puppetfile.

CONFIGFILE=~/.config/puppet-update

if [ ! -r $CONFIGFILE ]; then
  echo "$CONFIGFILE doesn't exist, not updating" &gt;&2
  exit 1
fi

source $CONFIGFILE

update() {
  ssh $SSHTARGET -- --verbose info $@
  if [ $? -ne 0 ]; then
    echo "WARNING: Update had errors: puppet may not be completely updated" &gt;&2
    exit 1
  fi
}

while read oldref newref refname
do
  refname=$(basename $refname)
  updateargs=
  if echo ${newref} | egrep -vq '[^0]'
  then
    echo "Branch $refname is being deleted, updating all to trigger cleanup"
    update
    break
  fi
  # If this isn't a new branch and the Puppetfile has been changed, add --puppetfile
  # The new branch check is needed because diff won't work in that case; new
  # environments get Puppetfile deployed by r10k automatically (could also use ||)
  if echo ${oldref} | egrep -q '[^0]' && \
    git diff --name-only ${oldref}..${newref} | grep -q Puppetfile
  then
    updateargs="--puppetfile"
  fi
  update ${refname} ${updateargs}
done
{{< / highlight >}}

Like the comment says, you&#8217;ll want to make a `~/.config/puppet-update` to tell it where your Puppet master lives.

{{< highlight sh >}}
SSHTARGET=root@puppet.example.com
{{< / highlight >}}

Now, make a commit to one of your branches and push it. You should see r10k working away in the `git push` output. Yay! Pushing any changes to any branches will update those environments. If you add or delete branches, it will deploy new environments or clean up. In a handy bit of time saving, it will only deploy the modules from the Puppetfile if the Puppetfile has actually been changed.

This works rather nicely for me, but I&#8217;d be interested to hear how it works for other people, or what changes you made.

 [1]: https://puppet.com/
 [2]: https://github.com/puppetlabs/control-repo
 [3]: https://github.com/puppetlabs/r10k
