--- 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