diff -r c486df8ea68a -r b68b8615c512 bin/update --- 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 <&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