# HG changeset patch # User Tero Marttila # Date 1332243492 -7200 # Node ID 8f49e2f51c0dd5196daf6dc0d9a7469eb7662a3d # Parent b2ac0fd8582833df848e51b69a87e65eab2ba34d update: cleanup, docs diff -r b2ac0fd85828 -r 8f49e2f51c0d bin/update --- a/bin/update Tue Mar 20 12:55:31 2012 +0200 +++ b/bin/update Tue Mar 20 13:38:12 2012 +0200 @@ -3,6 +3,8 @@ set -ue + +### Paths ROOT=$(pwd) # resolve $0 @@ -17,30 +19,29 @@ fi done -# bin dir +# Our bin dir, with scripts BIN=$(dirname $self) -# data files +# Data files DATA=settings ZONES=zones SERIALS=$DATA REPO= -# data args +# Script/data args PROCESS_ARGS='--input-charset latin-1' -FORWARD_MX=mail -REVERSE_ZONE=194.197.235 -REVERSE_DOMAIN=paivola.fi - # external progs NAMED_CHECKZONE=/usr/sbin/named-checkzone HG=/usr/bin/hg RNDC=/usr/sbin/rndc -## options +### Command-line argument handling + IS_TTY= +## Options +LOG_ERROR=y LOG_WARN=y LOG=y LOG_FORCE=y @@ -63,6 +64,7 @@ DEPLOY_SKIP= +## Output command-line argument help. function help_args { local prog=$1 @@ -93,6 +95,7 @@ END } +## Parse any command-line arguments, setting the global options vars. function parse_args { OPTIND=1 @@ -146,11 +149,13 @@ done } -## lib +### 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 @@ -161,14 +166,17 @@ fi } +## Log at various log-levels + function log_error { - log_color '31' "$*" + [ $LOG_ERROR ] && log_color '31' "$*" } function log_warn { [ $LOG_WARN ] && log_color '33' "$*" || true } +# plain function log { [ $LOG ] && log_msg "$*" || true } @@ -197,7 +205,7 @@ [ $LOG_CMD ] && log_color 35 " \$ $*" || true } -# XXX: broken +# Output stacktrace, broken. function log_stack { local level=1 @@ -210,27 +218,38 @@ 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 "$@" @@ -247,6 +266,9 @@ return ${PIPESTATUS[0]} } + +### FS utils +# Create dir in $ROOT if not exists. function ensure_dir { local dir=$1 @@ -256,21 +278,30 @@ fi } +## Output absolute path from $ROOT: +# +# abspath $path +# function abspath () { - echo "$ROOT/$1" + local path=$1 + + echo "$ROOT/$path" } -## hg +### HG wrappers +# Run `hg ...` within $REPO. function hg { local repo=$REPO; shift 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" @@ -284,12 +315,17 @@ fi } +# Show changes in repo function hg_diff { hg diff } +## Commit changes in repo, with given message: +# +# hg_commit $msg +# function hg_commit { - local msg=$2 + local msg=$1 local user_opt=$(hg_user) debug "$user_opt: $msg" @@ -297,17 +333,18 @@ } -## functions +### Dependency-based updates -## Dependency-based updates -# Compare the given output file with all given source files. +## 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 dst=$1; shift + local out=$1; shift - debug "$dst" + debug "$out" # need update? local update= @@ -316,7 +353,7 @@ debug " update: unknown deps" update=y - elif [ ! -e $dst ]; then + elif [ ! -e $out ]; then debug " update: dest missing" update=y @@ -334,7 +371,7 @@ if [ ! -e $ROOT/$dep ]; then fail "$dst: Missing source: $dep" - elif [ $ROOT/$dst -ot $ROOT/$dep ]; then + elif [ $ROOT/$out -ot $ROOT/$dep ]; then debug " update: $dep" update=y else @@ -348,21 +385,28 @@ [ $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 dst=$1; shift - local tmp=$dst.new + local out=$1; shift + local tmp=$out.new - debug "$dst" + debug "$out" cmd "$@" > $ROOT/$tmp # compare - if [ -e $ROOT/$dst ] && [ $UPDATE_DIFF ]; then + if [ -e $ROOT/$out ] && [ $UPDATE_DIFF ]; then debug " changes:" # terse - indent " " diff --unified=1 $ROOT/$dst $ROOT/$tmp || true + indent " " diff --unified=1 $ROOT/$out $ROOT/$tmp || true fi + # deploy if [ $UPDATE_NOOP ]; then # cleanup debug " no-op" @@ -370,20 +414,49 @@ cmd rm $ROOT/$tmp else # commit - debug " commit" + debug " deploy" - cmd mv $ROOT/$tmp $ROOT/$dst + cmd mv $ROOT/$tmp $ROOT/$out fi } -# links +## 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 @@ -391,39 +464,11 @@ cmd ln -sf $ROOT/$tgt $ROOT/$lnk } -## hosts -# copy hosts input zone verbatim -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 zone from input zone -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 -} - -## actions -# serial +## Update .serial number: +# +# do_update_serial $serial +# +# Shows old/new serial on debug. function do_update_serial { local serial=$1 @@ -439,6 +484,60 @@ 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 @@ -463,6 +562,9 @@ fi } +## Link serial for zone from given base-zone: +# +# link_serial $zone $base function link_serial { local zone=$1 local base=$2 @@ -480,7 +582,11 @@ fi } -# zone +## 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 @@ -499,6 +605,11 @@ 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 @@ -523,28 +634,36 @@ 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 - - # find tgt - for tgt in $ZONES/$view/$base $ZONES/common/$base; do - [ $tgt != $out ] && [ -e $tgt ] && break - done + 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 } -## Tests +## Test hosts zone for validity: +# +# check_hosts $DATA/$hosts --check-exempt ... +# +# Fails if the check fails. function check_hosts { local hosts=$1; shift 1 @@ -561,6 +680,12 @@ 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 @@ -583,52 +708,83 @@ fi } -## Deploy -# deploy new zone data to bind +## Load update zonefiles into bind: +# +# deploy_zones +# +# Invokes `rndc reload`, showing its output. function deploy_zones { - indent " rndc: " $RNDC reload + if [ $DEPLOY_SKIP ]; then + log_skip "Reload zones: skipped" + + else + log_update "Reload zones..." + + # run + indent " rndc: " \ + $RNDC reload + fi } - -# commit data changes +## 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 - - if hg_modified; then - log_update "Commit changes in $repo:" + local commit_msg="$COMMIT_MSG" - indent " " hg_diff + local msg="Commit changes in $repo..." - hg_commit "$COMMIT_MSG" + # 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_skip "Commit changes in $repo: no changes" + log_update "msg..." + + do_commit "$commit_msg" fi } -function main { - # test tty - [ -t 1 ] && IS_TTY=y - - parse_args "$@" +## Site settings, used as arguments to scripts +# MX record to generate in hosts --forward-zone +FORWARD_MX=mail - ## test env - [ -d $ROOT/$DATA ] || die "Missing data: $ROOT/$DATA" - ensure_dir $ZONES - - # output dirs - local views=(internal external) +# IP network to generate reverse records for in --reverse-zone +REVERSE_ZONE=194.197.235 - for view in "${views[@]}" "common" "hosts" "includes"; do - ensure_dir $ZONES/$view - done +# Origin domain to generate reverse records for in --reverse-zone +REVERSE_DOMAIN=paivola.fi - ## hosts +# Views used +VIEWS=(internal external) + +# Base domain zone for domains +DOMAIN_BASE=paivola + +# List of actual domains used; will be linked to $DOMAIN_BASE +DOMAINS=(paivola.fi paivola.net paivola.org paivola.info paivola.mobi xn--pivl-load8j.fi) + +## Operate! +function run { + ## Hosts # test log "Testing hosts..." - check_hosts $DATA/paivola.txt --check-exempt ufc + # data args... + check_hosts $DATA/paivola.txt --check-exempt ufc # update log "Generating host zones..." - # zone base *args + # hosts data args... update_hosts $ZONES/hosts/paivola:internal $DATA/paivola.txt --forward-zone --forward-txt --forward-mx $FORWARD_MX update_hosts $ZONES/hosts/paivola:external $DATA/paivola.txt --forward-zone update_hosts $ZONES/hosts/194.197.235 $DATA/paivola.txt --reverse-zone $REVERSE_ZONE --reverse-domain $REVERSE_DOMAIN @@ -641,8 +797,7 @@ # update_hosts $ZONES/hosts/pvl $DATA/pvl.txt --forward-zone copy_hosts $ZONES/hosts/pvl $DATA/pvl.txt - ## zones - # parts + ## Includes log "Copying zone includes..." # view zone base copy_zone includes paivola:internal paivola.zone.internal @@ -650,25 +805,25 @@ copy_zone includes paivola.auto paivola.zone.auto copy_zone includes paivola.services paivola.zone.services - # serials + ## Serials log "Updating serials..." - # zone *deps - update_serial pvl $ZONES/hosts/pvl $DATA/pvl.zone - update_serial 10 $ZONES/hosts/10 $DATA/10.zone - update_serial 192.168 $ZONES/hosts/192.168 $DATA/192.168.zone + # zone deps... + update_serial pvl $ZONES/hosts/pvl $DATA/pvl.zone + update_serial 10 $ZONES/hosts/10 $DATA/10.zone + update_serial 192.168 $ZONES/hosts/192.168 $DATA/192.168.zone - update_serial paivola \ - $ZONES/hosts/paivola:* \ - $DATA/paivola.zone \ - $ZONES/includes/paivola:* \ - $ZONES/includes/paivola.* + update_serial paivola \ + $ZONES/hosts/paivola:* \ + $DATA/paivola.zone \ + $ZONES/includes/paivola:* \ + $ZONES/includes/paivola.* - update_serial 194.197.235 \ - $ZONES/hosts/194.197.235 \ - $DATA/194.197.235.zone + update_serial 194.197.235 \ + $ZONES/hosts/194.197.235 \ + $DATA/194.197.235.zone - # zones + ## Zones log "Updating zones..." # view zone base update_zone internal pvl @@ -682,7 +837,7 @@ link_zone internal 194.197.235 link_zone external 194.197.235 - # test + ## Test log "Testing zones..." # view zone origin check_zone internal paivola paivola.fi @@ -692,36 +847,45 @@ check_zone internal 192.168 192.168.in-addr.arpa check_zone common 194.197.235 235.197.194.in-addr.arpa - # extra zones... - local base=paivola - local link_zones=(paivola.fi paivola.net paivola.org paivola.info paivola.mobi xn--pivl-load8j.fi) + ## Domains... + log "Linking domains..." + for view in "${VIEWS[@]}"; do + for zone in "${DOMAINS[@]}"; do + # link + link_zone $view $zone $DOMAIN_BASE - log "Linking zones..." - for view in "${views[@]}"; do - for zone in "${link_zones[@]}"; do - link_zone $view $zone $base - check_zone $view $zone $zone + # test + check_zone $view $zone $zone + done done + + ## Deploy + log "Deploy zones..." + deploy_zones + + ## Commit + log "Commit data..." + commit_data +} + +## Main entry point +function main { + # test tty + [ -t 1 ] && IS_TTY=y + + parse_args "$@" + + ## test env + [ -d $ROOT/$DATA ] || die "Missing data: $ROOT/$DATA" + ensure_dir $ZONES + + ## Output dirs + for dir in "common" "hosts" "includes" "${VIEWS[@]}"; do + ensure_dir $ZONES/$dir done - ## deploy - if [ $DEPLOY_SKIP ]; then - log "Deploy zones: skipped" - - else - log "Deploy zones..." - - deploy_zones - fi - - ## commit - if [ $COMMIT_SKIP ] && [ ! $COMMIT_FORCE ]; then - log "Commit data: skipped" - - else - log "Commit data..." - commit_data - fi + ## Go + run } main "$@"