worrbase

CFEngine SVN pre-commit hook

2012-07-11

I use CFEngine on my personal site to manage my configuration. I have an SVN repo on one box that I can push configuration changes to, and then from there, the changes get pushed out to all of my boxes.

One problem that I encountered is that changes to my CFEngine configurations that had syntax errors would be pushed out to all of my machines, and cause them to fallback to the failsafe configuration, which is less than ideal.

My solution was to write a pre-commit hook for my SVN repo that would prevent me from checking anything in that would break my configuration.

#!/bin/bash

REPOS=$1
TXN=$2
LOG="/home/svn/commit.log"
CHANGES=0

if [[ "$1" = "" && "$2" = "" ]]; then
  echo "Most be called as $0 REPOS TXN" >> "$LOG"
  exit 1
fi

AWK="/bin/awk"
GREP="/bin/grep"
RM="/bin/rm"
ECHO="/bin/echo"
SED="/bin/sed"
SVN="/usr/bin/svn"
SVNLOOK="/usr/bin/svnlook"
SVN_URL="file:///home/svn/cfengine/trunk"
CF_PROMISES="/usr/sbin/cf-promises"

PROMISEDIR="/home/svn/.cfagent/inputs/"
MODULESIDR="/home/svn/.cfagent/modules/"

"${ECHO}" "REPOS ${REPOS}" >> "$LOG"
"${ECHO}" "TXN ${TXN}" >> "$LOG"

"${ECHO}" "Checking out working copy from $REPOS ...." >> "$LOG"
"${SVN}" co --depth immediates "${SVN_URL}/cf-agent_policies" "${PROMISEDIR}" > /dev/null
if [[ $? -ne 0 ]]; then
  "${ECHO}" "Failed to get fresh working copy" >> "$LOG"
  exit 1
fi

"${SVN}" co --depth immediates "${SVN_URL}/cf-agent_modules" "${MODULESIDR}" > /dev/null
if [[ $? -ne 0 ]]; then
  "${ECHO}" "Failed to get fresh working copy" >> "$LOG"
  exit 1
fi

# Figure out what directories have changed using svnlook.
FILES=`${SVNLOOK} changed ${REPOS} -t ${TXN} | ${AWK} '{ print $2 }'` > /dev/null

for FILE in $FILES; do
  "${ECHO}" "file changed ${FILE}" >> "${LOG}"
  if [[ $("${ECHO}" "${FILE}" | ${GREP} cfengine/trunk) != "" ]]; then
    if [[ $("${ECHO}" "${FILE}" | ${GREP} generic_cf-agent_policies) != "" ]]; then
      if [[ $("${ECHO}" "${FILE}" | "${SED}" -e 's/home\/svn\/cfengine\/trunk\/cf-agent_policies\///g') != "" ]]; then
        CHANGES=1
        "${SVNLOOK}" cat "${REPOS}" "${FILE}" -t "${TXN}" > ${PROMISEDIR}/$("${ECHO}" "${FILE}" | "${SED}" -e 's/home\/cfengine\/trunk\/cf-agent_policies\///g')
      fi
    fi
  fi
done

if [[ ${CHANGES} -eq 0 ]]; then
  exit 0
fi

"${CF_PROMISES}" --dry-run > /dev/null
if [[ $? -ne 0 ]]; then
  "${ECHO}" "Syntax error! Aborting commit!" >> "${LOG}"

  "${RM}" -rf "${PROMISEDIR}"
  "${RM}" -rf "${MODULESIDR}"

  exit 1
fi

"${RM}" -rf "${PROMISEDIR}"
"${RM}" -rf "${MODULESIDR}"

This should be the pre-commit file in the hooks directory of your SVN repo. If that file exits with a non-zero code, the commit will fail. The file must be executable by the svn user.