James McDonald

Git post-receive hook for Puppet control repo updates

I made a fairly simple post-receive hook setup to automatically update my Puppet 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. 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 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:

:cachedir: '/var/cache/r10k'

:sources:
  :myrepo:
    remote: '[email protected]:puppet'
    basedir: '/etc/puppetlabs/code/environments'

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’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’s /root/.ssh/authorized_keys:

command="/usr/local/sbin/puppet-update.sh" ssh-ed25519 AAASOMEKEYMATERIAL [email protected]

What’s this puppet-update.sh, I hear you cry? I’m glad you asked:

#!/bin/bash

/usr/local/bin/r10k deploy environment $SSH_ORIGINAL_COMMAND

Pretty straightforward. Obviously you’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’s hooks directory. In my case this is ~gitolite3/repositories/puppet.git/hooks/post-receive. It, too, should be executable.

#!/bin/bash

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

# Set SSHTARGET in ~/.config/puppet-update to eg:
#   [email protected]

# 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" >&2
  exit 1
fi

source $CONFIGFILE

update() {
  ssh $SSHTARGET -- --verbose info [email protected]
  if [ $? -ne 0 ]; then
    echo "WARNING: Update had errors: puppet may not be completely updated" >&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

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

[email protected]

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’d be interested to hear how it works for other people, or what changes you made.


Share

comments powered by Disqus