--- a/bin/update Tue Mar 20 14:00:33 2012 +0200
+++ b/bin/update Tue Mar 20 14:12:11 2012 +0200
@@ -3,11 +3,7 @@
set -ue
-
-### Paths
-ROOT=$(pwd)
-
-# resolve $0
+# resolve $0 -> bin/update
self=$0
while [ -L $self ]; do
tgt=$(readlink $self)
@@ -22,747 +18,54 @@
# Our bin dir, with scripts
BIN=$(dirname $self)
-# Data files
+# code root
+CODE=$(dirname $BIN)
+
+
+LIB=$CODE/lib
+
+## Data paths
+# absolute path to data files; can be changed using -d
+ROOT=$(pwd)
+
DATA=settings
ZONES=zones
SERIALS=$DATA
+
+# hg repo to commit
REPO=$DATA
-# hide files under repo in diff output..
+## Settings used in lib
+# Hide files under repo in commit diff output..
REPO_HIDE='*.serial'
-# Script/data args
+# XXX: hosts data input charset?
PROCESS_ARGS='--input-charset latin-1'
-# external progs
+# External bins
NAMED_CHECKZONE=/usr/sbin/named-checkzone
HG=/usr/bin/hg
RNDC=/usr/sbin/rndc
+
+# Path to rndc key, must be readable to run..
RNDC_KEY=/etc/bind/rndc.key
-### Command-line argument handling
-
-IS_TTY=
-
-## Options
-LOG_ERROR=y
-LOG_WARN=y
-LOG=y
-LOG_FORCE=y
-LOG_UPDATE=y
-LOG_NOOP=y
-LOG_SKIP=
-LOG_DEBUG=
-LOG_CMD=
-
-UPDATE_FORCE=
-UPDATE_NOOP=
-UPDATE_DIFF=
-
-SERIAL_NOOP=
-SERIAL_FORCE=
-
-COMMIT_SKIP=
-COMMIT_FORCE=
-COMMIT_MSG=' '
-
-DEPLOY_SKIP=
-
-## Output command-line argument help.
-function help_args {
- local prog=$1
-
- cat <<END
-Usage: $prog [options]
-
-General:
- -h display this help text
- -d DIR datadir
-
-Logging:
- -q quiet
- -v verbose
- -D debug
- -V debug commands
-
-Updates:
- -p show changes
- -F force-updates without checking src mtime
- -S do not update serial
- -s update serials
- -n no-op/mock-update; don't actually change/deploy anything; implies -SpC
-
-Commit:
- -C do not commit changes
- -c commit changes
- -m MSG commit message
-END
-}
-
-## Parse any command-line arguments, setting the global options vars.
-function parse_args {
- OPTIND=1
-
- while getopts 'hd:qvDVpFSsnCcm:' opt "$@"; do
- case $opt in
- h)
- help_args $0
- exit 0
- ;;
-
- d) ROOT="$OPTARG" ;;
-
- q)
- LOG=
- LOG_WARN=
- LOG_UPDATE=
- LOG_FORCE=
- LOG_NOOP=
- ;;
-
- v) LOG_SKIP=y ;;
- D)
- LOG_DEBUG=y
- LOG_INFO=y
- ;;
- V) LOG_CMD=y ;;
-
- p) UPDATE_DIFF=y ;;
- F) UPDATE_FORCE=y ;;
- S) SERIAL_NOOP=y ;;
- s) SERIAL_FORCE=y ;;
-
- n)
- UPDATE_NOOP=y
- # implies -Sp
- UPDATE_DIFF=y
- SERIAL_NOUPDATE=y
- COMMIT_SKIP=y
- DEPLOY_SKIP=y
- ;;
-
- C) COMMIT_SKIP=y ;;
- c) COMMIT_FORCE=y ;;
- m) COMMIT_MSG="$OPTARG" ;;
-
- ?)
- die
- ;;
- esac
-
- done
-}
-
-### Logging
-# Output message to stderr.
-function log_msg {
- echo "$*" >&2
-}
-
-# Output message to stderr, optionally with given color, if TTY.
-function log_color {
- local code=$1; shift
-
- if [ $IS_TTY ]; then
- echo $'\e['${code}'m'"$*"$'\e[00m' >&2
- else
- echo "$*" >&2
- fi
-}
-
-## Log at various log-levels
-
-function log_error {
- [ $LOG_ERROR ] && log_color '31' "$*"
-}
-
-function log_warn {
- [ $LOG_WARN ] && log_color '33' "$*" || true
-}
-
-# plain
-function log {
- [ $LOG ] && log_msg "$*" || true
-}
-
-function log_force {
- [ $LOG_FORCE ] && log_color '2;33' " $*" || true
-}
-
-function log_update {
- [ $LOG_UPDATE ] && log_color '36' " $*" || true
-}
-
-function log_noop {
- [ $LOG_NOOP ] && log_color '2;34' " $*" || true
-}
-
-function log_skip {
- [ $LOG_SKIP ] && log_color '1;34' " $*" || true
-}
-
-function log_debug {
- [ $LOG_DEBUG ] && log_color 32 " $*" || true
-}
-
-function log_cmd {
- [ $LOG_CMD ] && log_color 35 " \$ $*" || true
-}
-
-# Output stacktrace, broken.
-function log_stack {
- local level=1
-
- while info=$(caller $level); do
- echo $info | read line sub file
-
- log_msg "$file:$lineno $sub()"
-
- level=$(($level + 1))
- done
-}
-
-# Output calling function's name.
-function func_caller {
- caller 1 | cut -d ' ' -f 2
-}
-
-### High-level logging output
-# Log with func_caller at log_debug
-function debug {
- printf -v prefix "%s" $(func_caller)
-
- log_debug "$prefix: $*"
-}
-
-# Log with func_caller at log_error and exit, intended for internal errors...
-function fail {
- log_error "$(func_caller): $*"
-
- exit 2
-}
-
-# Log at log_error and exit
-function die {
- log_error "$*"
- exit 1
-}
-
-### Command execution
-## Execute command, possibly logging its execution.
-#
-# cmd $cmd...
-#
-# Fails if the command returns an error exit code.
-function cmd {
- log_cmd "$@"
-
- "$@" || die "Failed"
-}
-
-function indent () {
- local indent=$1; shift
-
- log_cmd "$@"
-
- "$@" | sed "s/^/$indent/"
-
- return ${PIPESTATUS[0]}
-}
-
-
-### FS utils
-# Create dir in $ROOT if not exists.
-function ensure_dir {
- local dir=$1
-
- if [ ! -d $ROOT/$dir ]; then
- log_warn "Creating output dir: $dir"
- cmd mkdir $ROOT/$dir
- fi
-}
-
-## Output absolute path from $ROOT:
-#
-# abspath $path
-#
-function abspath () {
- local path=$1
-
- echo "$ROOT/$path"
-}
-
-### HG wrappers
-# Run `hg ...` within $REPO.
-function hg {
- local repo=$REPO
-
- cmd $HG -R $ROOT/$repo "$@"
-}
-
-# Does the repo have local modifications?
-function hg_modified {
- hg id | grep -q '+'
-}
-
-# Output possible -u flag for commit.
-function hg_user {
- if [ ${SUDO_USER:-} ]; then
- echo '-u' "$SUDO_USER"
-
- elif [ $HOME ] && [ -e $HOME/.hgrc ]; then
- debug "using .hgrc user"
- echo ''
-
- else
- echo '-u' "$USER"
- fi
-}
-
-# Show changes in repo
-function hg_diff {
- hg diff -X "$REPO/$REPO_HIDE"
-}
-
-## Commit changes in repo, with given message:
-#
-# hg_commit $msg
-#
-function hg_commit {
- local msg=$1
- local user_opt=$(hg_user)
-
- debug "$user_opt: $msg"
- hg commit $user_opt -m "$msg"
-}
-
-
-### Dependency-based updates
-
-## Compare the given output file with all given source files:
-#
-# check_update $out ${deps[@]} && do_update $out ... || ...
-#
-# Returns true if the output file needs to be updated.
-function check_update {
- # target
- local out=$1; shift
-
- debug "$out"
-
- # need update?
- local update=
-
- if [ ${#@} == 0 ]; then
- debug " update: unknown deps"
- update=y
-
- elif [ ! -e $out ]; then
- debug " update: dest missing"
- update=y
-
- elif [ $UPDATE_FORCE ]; then
- debug " update: forced"
- update=y
- fi
-
- # check deps
- for dep in "$@"; do
- # don't bother checking if already figured out
- [ $update ] && continue
-
- # check
- if [ ! -e $ROOT/$dep ]; then
- fail "$dst: Missing source: $dep"
-
- elif [ $ROOT/$out -ot $ROOT/$dep ]; then
- debug " update: $dep"
- update=y
- else
- debug " check: $dep"
- fi
- done
-
- [ ! $update ] && debug " up-to-date"
-
- # return
- [ $update ]
-}
-
-## Generate updated output file from given command's stdout:
-#
-# do_update $out $BIN/cmd --args
-#
-# Writes output to a temporary .new file, optionally shows a diff of changes, and commits
-# the new version to $out (unless noop'd).
-function do_update {
- local out=$1; shift
- local tmp=$out.new
+## Library includes
+# Command-line argument handling
+source $LIB/update.args
- debug "$out"
- cmd "$@" > $ROOT/$tmp
-
- # compare
- if [ -e $ROOT/$out ] && [ $UPDATE_DIFF ]; then
- debug " changes:"
-
- # terse
- indent " " diff --unified=1 $ROOT/$out $ROOT/$tmp || true
- fi
-
- # deploy
- if [ $UPDATE_NOOP ]; then
- # cleanup
- debug " no-op"
-
- cmd rm $ROOT/$tmp
- else
- # commit
- debug " deploy"
-
- cmd mv $ROOT/$tmp $ROOT/$out
- fi
-}
-
-## Look for a link target:
-#
-# find_link $lnk $tgt...
-#
-# Outputs the first given target to exist, skipping any that are the same as the given $lnk.
-# If no $tgt matches, outputs the last one, or '-'.
-function choose_link {
- local lnk=$1; shift
- local tgt=-
-
- for tgt in "$@"; do
- [ $tgt != $out ] && [ -e $ROOT/$tgt ] && break
- done
-
- echo $tgt
-}
-
-
-## Compare symlink to target:
-#
-# check_link $lnk $tgt && do_link $lnk $tgt || ...
-#
-# Tests if the symlink exists, and the target matches.
-# Fails if the target does not exist.
-function check_link {
- local lnk=$1
- local tgt=$2
-
- [ ! -e $ROOT/$tgt ] && fail "$tgt: target does not exist"
-
- [ ! -e $ROOT/$lnk ] || [ $(readlink $ROOT/$lnk) != $ROOT/$tgt ]
-}
-
-## Update symlink to point to target:
-#
-# do_link $lnk $tgt
-#
-function do_link {
- local lnk=$1
- local tgt=$2
-
- cmd ln -sf $ROOT/$tgt $ROOT/$lnk
-}
-
-## Update .serial number:
-#
-# do_update_serial $serial
-#
-# Shows old/new serial on debug.
-function do_update_serial {
- local serial=$1
-
- # read
- local old=$(test -e $ROOT/$serial && cat $ROOT/$serial || echo '')
-
-
- cmd $BIN/update-serial $ROOT/$serial
-
- # read
- local new=$(cat $ROOT/$serial)
-
- debug " $old -> $new"
-}
-
-## Perform `hg commit` for $DATA
-function do_commit {
- local msg=$1
-
- indent " " hg_diff
-
- hg_commit "$msg"
-}
-
-### Hosts
-## Update hosts from verbatim from input zone data:
-#
-# copy_hosts $ZONES/$zone $DATA/$base
-#
-# Writes updated zone to $zone, deps on $base.
-function copy_hosts {
- local zone=$1
- local base=$2
-
- if check_update $zone $base; then
- log_update "Copying hosts $zone <- $base..."
-
- do_update $zone \
- cat $ROOT/$base
- else
- log_skip "Copying hosts $zone <- $base: not changed"
- fi
-}
-
-## Generate hosts from input zone data using $BIN/process-zone:
-#
-# update_hosts $ZONES/$zone $DATA/$base
-#
-# Writes process-zone'd data to $zone, deps on $base.
-function update_hosts {
- local zone=$1; shift
- local base=$1; shift
-
- if check_update $zone $base; then
- log_update "Generating hosts $zone <- $base..."
-
- do_update $zone \
- $BIN/process-zone $PROCESS_ARGS $ROOT/$base "$@"
- else
- log_skip "Generating hosts $zone <- $base: not changed"
- fi
-}
-
-## Generate new serial for zone using $BIN/update-serial, if the zone data has changed:
-#
-# update_serial $zone $deps...
-#
-# Supports SERIAL_FORCE/NOOP.
-# Updates $SERIALS/$zone.serial.
-function update_serial {
- local zone=$1; shift
-
- local serial=$SERIALS/$zone.serial
-
- # test
- if [ $SERIAL_FORCE ]; then
- log_force "Updating $serial: forced"
-
- do_update_serial $serial
-
- elif ! check_update $serial "$@"; then
- log_skip "Updating $serial: not changed"
-
- elif [ $SERIAL_NOOP ]; then
- log_noop "Updating $serial: skipped"
-
- else
- log_update "Updating $serial..."
-
- do_update_serial $serial
- fi
-}
-
-## Link serial for zone from given base-zone:
-#
-# link_serial $zone $base
-function link_serial {
- local zone=$1
- local base=$2
-
- local lnk=$SERIALS/$zone.serial
- local tgt=$SERIALS/$base.serial
-
- if check_link $lnk $tgt; then
- log_update "Linking $lnk -> $tgt..."
-
- do_link $lnk $tgt
+# Logging
+source $LIB/update.logging
- else
- log_skip "Linking $lnk -> $tgt: not changed"
- fi
-}
-
-## Update zone file verbatim from source:
-#
-# copy_zone $view $zone [$base]
-#
-# Copies changed $DATA/$base zone data to $ZONES/$view/$zone.
-function copy_zone {
- local view=$1
- local zone=$2
- local base=${3:-$zone}
-
- local out=$ZONES/$view/$zone
- local src=$DATA/$base
-
- if check_update $out $src; then
- log_update "Copying $out <- $src..."
-
- do_update $out \
- cat $ROOT/$src
- else
- log_skip "Copying $out <- $src: not changed"
- fi
-}
-
-## Expand zone file from source using $BIN/expand-zone:
-#
-# update_zone $view $zone [$base]
-#
-# Processed $DATA/$base zone data through $BIN/expand-zone, writing output to $ZONES/$view/$zone.
-function update_zone {
- local view=$1
- local zone=$2
- local base=${3:-$zone}
-
- local out=$ZONES/$view/$zone
- local src=$DATA/$base.zone
- local lnk=$ZONES/$base
-
- local serial=$SERIALS/$base.serial
-
- if check_update $out $src $serial; then
- log_update "Generating $out <- $src..."
-
- do_update $out \
- $BIN/expand-zone $ROOT/$src \
- --serial $ROOT/$serial \
- --expand zones=$(abspath $ZONES) \
- --expand view=$view
- else
- log_skip "Generating $out <- $src: not changed"
- fi
-}
-
-## Link zone file to ues given shared zone.
-#
-# link_zone $view $zone [$base]
-#
-# Looks for shared zone at:
-# $ZONES/$view/$base
-# $ZONES/common/$base
-function link_zone {
- local view=$1
- local zone=$2
- local base=${3:-$zone}
-
- local out=$ZONES/$view/$zone
- local tgt=$(choose_link $out $ZONES/$view/$base $ZONES/common/$base)
-
- if check_link $out $tgt; then
- log_update "Linking $out -> $tgt..."
-
- do_link $out $tgt
-
- else
- log_skip "Linking $out -> $tgt: not changed"
- fi
-}
-
-## Test hosts zone for validity:
-#
-# check_hosts $DATA/$hosts --check-exempt ...
-#
-# Fails if the check fails.
-function check_hosts {
- local hosts=$1; shift 1
+# Utility functions
+source $LIB/update.utils
- local cmd=($BIN/process-zone $PROCESS_ARGS $ROOT/$hosts --check-hosts "$@")
-
- if "${cmd[@]}" -q; then
- log_skip "Check $hosts: OK"
- else
- log_error " Check $hosts: Failed"
-
- indent " " "${cmd[@]}"
-
- exit 1
- fi
-}
-
-## Test zone file for validity using named-checkzone:
-#
-# check_zone $view $zone $origin
-#
-# Uses the zonefile at $ZONES/$view/$zone, loading it with given initial $ORIGIN.
-# Fails if the check fails.
-function check_zone {
- local view=$1
- local zone=$2
- local origin=$3
-
- local src=$ZONES/$view/$zone
-
- local cmd=($NAMED_CHECKZONE $origin $ROOT/$src)
-
- # test
- # XXX: checkzone is very specific about the order of arguments, -q must be first
- if $NAMED_CHECKZONE -q $origin $ROOT/$src; then
- log_skip "Check $src ($origin): OK"
- else
- log_error " Check $src ($origin): Failed:"
-
- indent " " "${cmd[@]}"
-
- exit 1
- fi
-}
+# Dependency-based updates
+source $LIB/update.updates
-## Load update zonefiles into bind:
-#
-# deploy_zones
-#
-# Invokes `rndc reload`, showing its output.
-function deploy_zones {
- local msg="Reload zones"
-
- if [ $DEPLOY_SKIP ]; then
- log_skip "$msg: skipped"
-
- elif [ ! -r $RNDC_KEY ]; then
- log_error " $msg: rndc: permission denied: $RNDC_KEY"
-
- else
- log_update "$msg..."
+# Operations
+source $LIB/update.operations
- # run
- indent " rndc: " \
- $RNDC reload
- fi
-}
-## Commit changes in $DATA to version control:
-#
-# commit_data
-#
-# Invokes `hg commit` in the $REPO, first showing the diff.
-function commit_data {
- local repo=$REPO
- local commit_msg="$COMMIT_MSG"
-
- local msg="Commit changes in $repo"
-
- # operate?
- if [ $COMMIT_FORCE ]; then
- log_force "$msg..."
-
- do_commit "$commit_msg"
-
- elif ! hg_modified; then
- log_skip "$msg: no changes"
-
- elif [ $COMMIT_SKIP ]; then
- log_noop "$msg: skipped"
-
- else
- log_update "$msg..."
-
- do_commit "$commit_msg"
- fi
-}
## Site settings, used as arguments to scripts
# MX record to generate in hosts --forward-zone
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.args Tue Mar 20 14:12:11 2012 +0200
@@ -0,0 +1,118 @@
+# vim: set ft=sh :
+#
+# Command-line option handling
+
+# use color output?
+IS_TTY=
+
+
+## Options
+LOG_ERROR=y
+LOG_WARN=y
+LOG=y
+LOG_FORCE=y
+LOG_UPDATE=y
+LOG_NOOP=y
+LOG_SKIP=
+LOG_DEBUG=
+LOG_CMD=
+
+UPDATE_FORCE=
+UPDATE_NOOP=
+UPDATE_DIFF=
+
+SERIAL_NOOP=
+SERIAL_FORCE=
+
+COMMIT_SKIP=
+COMMIT_FORCE=
+COMMIT_MSG=' '
+
+DEPLOY_SKIP=
+
+## Output command-line argument help.
+function help_args {
+ local prog=$1
+
+ cat <<END
+Usage: $prog [options]
+
+General:
+ -h display this help text
+ -d DIR datadir
+
+Logging:
+ -q quiet
+ -v verbose
+ -D debug
+ -V debug commands
+
+Updates:
+ -p show changes
+ -F force-updates without checking src mtime
+ -S do not update serial
+ -s update serials
+ -n no-op/mock-update; don't actually change/deploy anything; implies -SpC
+
+Commit:
+ -C do not commit changes
+ -c commit changes
+ -m MSG commit message
+END
+}
+
+## Parse any command-line arguments, setting the global options vars.
+function parse_args {
+ OPTIND=1
+
+ while getopts 'hd:qvDVpFSsnCcm:' opt "$@"; do
+ case $opt in
+ h)
+ help_args $0
+ exit 0
+ ;;
+
+ d) ROOT="$OPTARG" ;;
+
+ q)
+ LOG=
+ LOG_WARN=
+ LOG_UPDATE=
+ LOG_FORCE=
+ LOG_NOOP=
+ ;;
+
+ v) LOG_SKIP=y ;;
+ D)
+ LOG_DEBUG=y
+ LOG_INFO=y
+ ;;
+ V) LOG_CMD=y ;;
+
+ p) UPDATE_DIFF=y ;;
+ F) UPDATE_FORCE=y ;;
+ S) SERIAL_NOOP=y ;;
+ s) SERIAL_FORCE=y ;;
+
+ n)
+ UPDATE_NOOP=y
+ # implies -Sp
+ UPDATE_DIFF=y
+ SERIAL_NOUPDATE=y
+ COMMIT_SKIP=y
+ DEPLOY_SKIP=y
+ ;;
+
+ C) COMMIT_SKIP=y ;;
+ c) COMMIT_FORCE=y ;;
+ m) COMMIT_MSG="$OPTARG" ;;
+
+ ?)
+ die
+ ;;
+ esac
+
+ done
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.logging Tue Mar 20 14:12:11 2012 +0200
@@ -0,0 +1,99 @@
+# vim: set ft=sh :
+#
+# Logging output
+
+# Output message to stderr.
+function log_msg {
+ echo "$*" >&2
+}
+
+# Output message to stderr, optionally with given color, if TTY.
+function log_color {
+ local code=$1; shift
+
+ if [ $IS_TTY ]; then
+ echo $'\e['${code}'m'"$*"$'\e[00m' >&2
+ else
+ echo "$*" >&2
+ fi
+}
+
+## Log at various log-levels
+
+function log_error {
+ [ $LOG_ERROR ] && log_color '31' "$*"
+}
+
+function log_warn {
+ [ $LOG_WARN ] && log_color '33' "$*" || true
+}
+
+# plain
+function log {
+ [ $LOG ] && log_msg "$*" || true
+}
+
+function log_force {
+ [ $LOG_FORCE ] && log_color '2;33' " $*" || true
+}
+
+function log_update {
+ [ $LOG_UPDATE ] && log_color '36' " $*" || true
+}
+
+function log_noop {
+ [ $LOG_NOOP ] && log_color '2;34' " $*" || true
+}
+
+function log_skip {
+ [ $LOG_SKIP ] && log_color '1;34' " $*" || true
+}
+
+function log_debug {
+ [ $LOG_DEBUG ] && log_color 32 " $*" || true
+}
+
+function log_cmd {
+ [ $LOG_CMD ] && log_color 35 " \$ $*" || true
+}
+
+# Output stacktrace, broken.
+function log_stack {
+ local level=1
+
+ while info=$(caller $level); do
+ echo $info | read line sub file
+
+ log_msg "$file:$lineno $sub()"
+
+ level=$(($level + 1))
+ done
+}
+
+# Output calling function's name.
+function func_caller {
+ caller 1 | cut -d ' ' -f 2
+}
+
+### High-level logging output
+# Log with func_caller at log_debug
+function debug {
+ printf -v prefix "%s" $(func_caller)
+
+ log_debug "$prefix: $*"
+}
+
+# Log with func_caller at log_error and exit, intended for internal errors...
+function fail {
+ log_error "$(func_caller): $*"
+
+ exit 2
+}
+
+# Log at log_error and exit
+function die {
+ log_error "$*"
+ exit 1
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.operations Tue Mar 20 14:12:11 2012 +0200
@@ -0,0 +1,271 @@
+## vim: set ft=sh :
+#
+# Operations on zonefiles/hosts/whatever
+
+## Hosts
+## Update hosts from verbatim from input zone data:
+#
+# copy_hosts $ZONES/$zone $DATA/$base
+#
+# Writes updated zone to $zone, deps on $base.
+function copy_hosts {
+ local zone=$1
+ local base=$2
+
+ if check_update $zone $base; then
+ log_update "Copying hosts $zone <- $base..."
+
+ do_update $zone \
+ cat $ROOT/$base
+ else
+ log_skip "Copying hosts $zone <- $base: not changed"
+ fi
+}
+
+## Generate hosts from input zone data using $BIN/process-zone:
+#
+# update_hosts $ZONES/$zone $DATA/$base
+#
+# Writes process-zone'd data to $zone, deps on $base.
+function update_hosts {
+ local zone=$1; shift
+ local base=$1; shift
+
+ if check_update $zone $base; then
+ log_update "Generating hosts $zone <- $base..."
+
+ do_update $zone \
+ $BIN/process-zone $PROCESS_ARGS $ROOT/$base "$@"
+ else
+ log_skip "Generating hosts $zone <- $base: not changed"
+ fi
+}
+
+## Generate new serial for zone using $BIN/update-serial, if the zone data has changed:
+#
+# update_serial $zone $deps...
+#
+# Supports SERIAL_FORCE/NOOP.
+# Updates $SERIALS/$zone.serial.
+function update_serial {
+ local zone=$1; shift
+
+ local serial=$SERIALS/$zone.serial
+
+ # test
+ if [ $SERIAL_FORCE ]; then
+ log_force "Updating $serial: forced"
+
+ do_update_serial $serial
+
+ elif ! check_update $serial "$@"; then
+ log_skip "Updating $serial: not changed"
+
+ elif [ $SERIAL_NOOP ]; then
+ log_noop "Updating $serial: skipped"
+
+ else
+ log_update "Updating $serial..."
+
+ do_update_serial $serial
+ fi
+}
+
+## Link serial for zone from given base-zone:
+#
+# link_serial $zone $base
+function link_serial {
+ local zone=$1
+ local base=$2
+
+ local lnk=$SERIALS/$zone.serial
+ local tgt=$SERIALS/$base.serial
+
+ if check_link $lnk $tgt; then
+ log_update "Linking $lnk -> $tgt..."
+
+ do_link $lnk $tgt
+
+ else
+ log_skip "Linking $lnk -> $tgt: not changed"
+ fi
+}
+
+## Update zone file verbatim from source:
+#
+# copy_zone $view $zone [$base]
+#
+# Copies changed $DATA/$base zone data to $ZONES/$view/$zone.
+function copy_zone {
+ local view=$1
+ local zone=$2
+ local base=${3:-$zone}
+
+ local out=$ZONES/$view/$zone
+ local src=$DATA/$base
+
+ if check_update $out $src; then
+ log_update "Copying $out <- $src..."
+
+ do_update $out \
+ cat $ROOT/$src
+ else
+ log_skip "Copying $out <- $src: not changed"
+ fi
+}
+
+## Expand zone file from source using $BIN/expand-zone:
+#
+# update_zone $view $zone [$base]
+#
+# Processed $DATA/$base zone data through $BIN/expand-zone, writing output to $ZONES/$view/$zone.
+function update_zone {
+ local view=$1
+ local zone=$2
+ local base=${3:-$zone}
+
+ local out=$ZONES/$view/$zone
+ local src=$DATA/$base.zone
+ local lnk=$ZONES/$base
+
+ local serial=$SERIALS/$base.serial
+
+ if check_update $out $src $serial; then
+ log_update "Generating $out <- $src..."
+
+ do_update $out \
+ $BIN/expand-zone $ROOT/$src \
+ --serial $ROOT/$serial \
+ --expand zones=$(abspath $ZONES) \
+ --expand view=$view
+ else
+ log_skip "Generating $out <- $src: not changed"
+ fi
+}
+
+## Link zone file to ues given shared zone.
+#
+# link_zone $view $zone [$base]
+#
+# Looks for shared zone at:
+# $ZONES/$view/$base
+# $ZONES/common/$base
+function link_zone {
+ local view=$1
+ local zone=$2
+ local base=${3:-$zone}
+
+ local out=$ZONES/$view/$zone
+ local tgt=$(choose_link $out $ZONES/$view/$base $ZONES/common/$base)
+
+ if check_link $out $tgt; then
+ log_update "Linking $out -> $tgt..."
+
+ do_link $out $tgt
+
+ else
+ log_skip "Linking $out -> $tgt: not changed"
+ fi
+}
+
+## Test hosts zone for validity:
+#
+# check_hosts $DATA/$hosts --check-exempt ...
+#
+# Fails if the check fails.
+function check_hosts {
+ local hosts=$1; shift 1
+
+ local cmd=($BIN/process-zone $PROCESS_ARGS $ROOT/$hosts --check-hosts "$@")
+
+ if "${cmd[@]}" -q; then
+ log_skip "Check $hosts: OK"
+ else
+ log_error " Check $hosts: Failed"
+
+ indent " " "${cmd[@]}"
+
+ exit 1
+ fi
+}
+
+## Test zone file for validity using named-checkzone:
+#
+# check_zone $view $zone $origin
+#
+# Uses the zonefile at $ZONES/$view/$zone, loading it with given initial $ORIGIN.
+# Fails if the check fails.
+function check_zone {
+ local view=$1
+ local zone=$2
+ local origin=$3
+
+ local src=$ZONES/$view/$zone
+
+ local cmd=($NAMED_CHECKZONE $origin $ROOT/$src)
+
+ # test
+ # XXX: checkzone is very specific about the order of arguments, -q must be first
+ if $NAMED_CHECKZONE -q $origin $ROOT/$src; then
+ log_skip "Check $src ($origin): OK"
+ else
+ log_error " Check $src ($origin): Failed:"
+
+ indent " " "${cmd[@]}"
+
+ exit 1
+ fi
+}
+
+## Load update zonefiles into bind:
+#
+# deploy_zones
+#
+# Invokes `rndc reload`, showing its output.
+function deploy_zones {
+ local msg="Reload zones"
+
+ if [ $DEPLOY_SKIP ]; then
+ log_skip "$msg: skipped"
+
+ elif [ ! -r $RNDC_KEY ]; then
+ log_error " $msg: rndc: permission denied: $RNDC_KEY"
+
+ else
+ log_update "$msg..."
+
+ # run
+ indent " rndc: " \
+ $RNDC reload
+ fi
+}
+## Commit changes in $DATA to version control:
+#
+# commit_data
+#
+# Invokes `hg commit` in the $REPO, first showing the diff.
+function commit_data {
+ local repo=$REPO
+ local commit_msg="$COMMIT_MSG"
+
+ local msg="Commit changes in $repo"
+
+ # operate?
+ if [ $COMMIT_FORCE ]; then
+ log_force "$msg..."
+
+ do_commit "$commit_msg"
+
+ elif ! hg_modified; then
+ log_skip "$msg: no changes"
+
+ elif [ $COMMIT_SKIP ]; then
+ log_noop "$msg: skipped"
+
+ else
+ log_update "$msg..."
+
+ do_commit "$commit_msg"
+ fi
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.updates Tue Mar 20 14:12:11 2012 +0200
@@ -0,0 +1,163 @@
+# vim: set ft=sh :
+#
+# Dependency-based updates + utils
+
+## Compare the given output file with all given source files:
+#
+# check_update $out ${deps[@]} && do_update $out ... || ...
+#
+# Returns true if the output file needs to be updated.
+function check_update {
+ # target
+ local out=$1; shift
+
+ debug "$out"
+
+ # need update?
+ local update=
+
+ if [ ${#@} == 0 ]; then
+ debug " update: unknown deps"
+ update=y
+
+ elif [ ! -e $out ]; then
+ debug " update: dest missing"
+ update=y
+
+ elif [ $UPDATE_FORCE ]; then
+ debug " update: forced"
+ update=y
+ fi
+
+ # check deps
+ for dep in "$@"; do
+ # don't bother checking if already figured out
+ [ $update ] && continue
+
+ # check
+ if [ ! -e $ROOT/$dep ]; then
+ fail "$dst: Missing source: $dep"
+
+ elif [ $ROOT/$out -ot $ROOT/$dep ]; then
+ debug " update: $dep"
+ update=y
+ else
+ debug " check: $dep"
+ fi
+ done
+
+ [ ! $update ] && debug " up-to-date"
+
+ # return
+ [ $update ]
+}
+
+## Generate updated output file from given command's stdout:
+#
+# do_update $out $BIN/cmd --args
+#
+# Writes output to a temporary .new file, optionally shows a diff of changes, and commits
+# the new version to $out (unless noop'd).
+function do_update {
+ local out=$1; shift
+ local tmp=$out.new
+
+ debug "$out"
+ cmd "$@" > $ROOT/$tmp
+
+ # compare
+ if [ -e $ROOT/$out ] && [ $UPDATE_DIFF ]; then
+ debug " changes:"
+
+ # terse
+ indent " " diff --unified=1 $ROOT/$out $ROOT/$tmp || true
+ fi
+
+ # deploy
+ if [ $UPDATE_NOOP ]; then
+ # cleanup
+ debug " no-op"
+
+ cmd rm $ROOT/$tmp
+ else
+ # commit
+ debug " deploy"
+
+ cmd mv $ROOT/$tmp $ROOT/$out
+ fi
+}
+
+## Look for a link target:
+#
+# find_link $lnk $tgt...
+#
+# Outputs the first given target to exist, skipping any that are the same as the given $lnk.
+# If no $tgt matches, outputs the last one, or '-'.
+function choose_link {
+ local lnk=$1; shift
+ local tgt=-
+
+ for tgt in "$@"; do
+ [ $tgt != $out ] && [ -e $ROOT/$tgt ] && break
+ done
+
+ echo $tgt
+}
+
+
+## Compare symlink to target:
+#
+# check_link $lnk $tgt && do_link $lnk $tgt || ...
+#
+# Tests if the symlink exists, and the target matches.
+# Fails if the target does not exist.
+function check_link {
+ local lnk=$1
+ local tgt=$2
+
+ [ ! -e $ROOT/$tgt ] && fail "$tgt: target does not exist"
+
+ [ ! -e $ROOT/$lnk ] || [ $(readlink $ROOT/$lnk) != $ROOT/$tgt ]
+}
+
+## Update symlink to point to target:
+#
+# do_link $lnk $tgt
+#
+function do_link {
+ local lnk=$1
+ local tgt=$2
+
+ cmd ln -sf $ROOT/$tgt $ROOT/$lnk
+}
+
+## Update .serial number:
+#
+# do_update_serial $serial
+#
+# Shows old/new serial on debug.
+function do_update_serial {
+ local serial=$1
+
+ # read
+ local old=$(test -e $ROOT/$serial && cat $ROOT/$serial || echo '')
+
+
+ cmd $BIN/update-serial $ROOT/$serial
+
+ # read
+ local new=$(cat $ROOT/$serial)
+
+ debug " $old -> $new"
+}
+
+## Perform `hg commit` for $DATA
+function do_commit {
+ local msg=$1
+
+ indent " " hg_diff
+
+ hg_commit "$msg"
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.utils Tue Mar 20 14:12:11 2012 +0200
@@ -0,0 +1,94 @@
+# vim: set ft=sh :
+#
+# Utility functions
+
+
+### Command execution
+## Execute command, possibly logging its execution.
+#
+# cmd $cmd...
+#
+# Fails if the command returns an error exit code.
+function cmd {
+ log_cmd "$@"
+
+ "$@" || die "Failed"
+}
+
+function indent () {
+ local indent=$1; shift
+
+ log_cmd "$@"
+
+ "$@" | sed "s/^/$indent/"
+
+ return ${PIPESTATUS[0]}
+}
+
+
+### FS utils
+# Create dir in $ROOT if not exists.
+function ensure_dir {
+ local dir=$1
+
+ if [ ! -d $ROOT/$dir ]; then
+ log_warn "Creating output dir: $dir"
+ cmd mkdir $ROOT/$dir
+ fi
+}
+
+## Output absolute path from $ROOT:
+#
+# abspath $path
+#
+function abspath () {
+ local path=$1
+
+ echo "$ROOT/$path"
+}
+
+### HG wrappers
+# Run `hg ...` within $REPO.
+function hg {
+ local repo=$REPO
+
+ cmd $HG -R $ROOT/$repo "$@"
+}
+
+# Does the repo have local modifications?
+function hg_modified {
+ hg id | grep -q '+'
+}
+
+# Output possible -u flag for commit.
+function hg_user {
+ if [ ${SUDO_USER:-} ]; then
+ echo '-u' "$SUDO_USER"
+
+ elif [ $HOME ] && [ -e $HOME/.hgrc ]; then
+ debug "using .hgrc user"
+ echo ''
+
+ else
+ echo '-u' "$USER"
+ fi
+}
+
+# Show changes in repo
+function hg_diff {
+ hg diff -X "$REPO/$REPO_HIDE"
+}
+
+## Commit changes in repo, with given message:
+#
+# hg_commit $msg
+#
+function hg_commit {
+ local msg=$1
+ local user_opt=$(hg_user)
+
+ debug "$user_opt: $msg"
+ hg commit $user_opt -m "$msg"
+}
+
+