update: mad bash wizardry
authorTero Marttila <terom@paivola.fi>
Fri, 16 Mar 2012 13:59:26 +0200
changeset 8 dab145ee3f81
parent 7 f4457348faa0
child 9 beb4bef2498a
update: mad bash wizardry
bin/update
--- a/bin/update	Thu Mar 15 18:34:57 2012 +0200
+++ b/bin/update	Fri Mar 16 13:59:26 2012 +0200
@@ -22,7 +22,15 @@
 
 ## options
 IS_TTY=
+
+LOG=y
+LOG_INFO=
+LOG_DEBUG=
+LOG_CMD=
+
 UPDATE_FORCE=
+UPDATE_NOOP=
+UPDATE_DIFF=
 SERIAL_NOUPDATE=
 
 function help_args {
@@ -32,7 +40,15 @@
 Usage: $prog [options]
 
     -h      display this help text
+
+    -q      quiet
+    -v      verbose
+    -D      debug
+    
+    -d      show changes
+
     -F      force-updates without checking src mtime
+    -n      no-op/mock-update; do not actually change anything; implies -S
     -S      do not update serial
 END
 }
@@ -40,16 +56,32 @@
 function parse_args {
     OPTIND=1
 
-    while getopts 'hFS' opt "$@"; do
+    while getopts 'hqvDdFnS' opt "$@"; do
         case $opt in
             h)  
                 help_args $1
                 exit 0
             ;;
 
+            q)  LOG= ;;
+            v)  LOG_INFO=y ;;
+            D)  
+                LOG_DEBUG=y 
+                LOG_CMD=y
+                
+                ;;
+
+            d)  UPDATE_DIFF=y ;;
             F)  UPDATE_FORCE=y ;;
             S)  SERIAL_NOUPDATE=y ;;
-            
+ 
+            n)  
+                # implies -S
+                UPDATE_NOOP=y 
+                SERIAL_NOUPDATE=y
+                ;;
+
+           
             ?)  
                 die 
             ;;
@@ -78,19 +110,40 @@
 }
 
 function log {
-    log_msg "$*"
+    [ $LOG ] && log_msg "$*" || true
 }
 
 function log_info {
-    log_color 36 "  $*"
+    [ $LOG_INFO ] && log_color 36 "  $*" || true
 }
 
 function log_debug {
-    log_color 32 "    $*"
+    [ $LOG_DEBUG ] && log_color 32 "    $*" || true
 }
 
 function log_cmd {
-    log_color 35 "       \$ $*"
+    [ $LOG_CMD ] && log_color 35 "        \$ $*" || true
+}
+
+# XXX: 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
+}
+
+function fail {
+    func=$(caller 1 | cut -d ' ' -f 2)
+    
+    log_error "$func: $*"
+
+    exit 2
 }
 
 function die {
@@ -113,9 +166,11 @@
 }
 
 function indent () {
+    local indent=$1; shift
+
     "$@" | (
         while read line; do
-            echo "    $line"
+            echo "$indent$line"
         done
     ) || exit $?
 }
@@ -126,49 +181,116 @@
 [ -d $ZONES ] || die "Missing zones: $ZONES"
 
 ## functions
-function update {
-    local dst=$1
-    local src=$2
+function check_update {
+    # target
+    local dst=$1; shift
+
+    log_debug "$dst:"
+
+    # need update?
+    local update=
+
+    if [ ! -e $dst ] || [ $UPDATE_FORCE ]; then
+        log_debug "  update forced"
+        update=y
+    fi
+
+    # check deps
+    for dep in "$@"; do
+        # don't bother checking if already figured out
+        [ $update ] && continue
+
+        # check
+
+        if [ $dst -ot $dep ]; then
+            log_debug "  changed: $dep"
+            update=y
+        fi
+    done
+
+    [ ! $update ] && log_debug "  up-to-date"
+
+    # return
+    [ $update ]
+}
+
+function do_update {
+    local dst=$1; shift
     local tmp=$dst.new
 
-    shift 2
-
-    if [ -z $src ] || [ $UPDATE_FORCE ] || [ $dst -ot $src ]; then
-        log_debug "update: $dst <- $src"
-        cmd "$@" $src > $tmp
+    log_debug "  update: $dst"
+    cmd "$@" > $tmp
 
-        # compare
-        if [ -e $dst ]; then
-            # terse
-            indent diff --unified=1 $dst $tmp
+    # compare
+    if [ -e $dst ] && [ $UPDATE_DIFF ]; then
+        log_debug "  changes:"
+
+        # terse
+        indent "        " diff --unified=1 $dst $tmp
+    fi
+    
+    if [ $UPDATE_NOOP ]; then
+        # cleanup
+        log_debug "  no-op:"
+
+        cmd rm $tmp
+    else
+        # commit
+        log_debug "  done:"
+
+        cmd mv $tmp $dst
+    fi
+}
+
+function update {
+    local dst=$1; shift;
+
+    local sep=
+    local dep=()
+    local cmd=()
+
+    for arg in "$@"; do
+        if [ $arg == '--' ]; then
+            sep=y
         fi
-        
-        # overwrite
-        mv $tmp $dst
 
-    else
-        log_debug "$dst <- $src: up-to-date"
-    fi
+        if [ $sep ]; then
+            cmd=("${cmd[@]:-}" "$arg")
+        else
+            dep=("${dep[@]:-}" "$arg")
+        fi
+    done
+
+    [ ! $sep ] && fail "Invalid args given: $@"
+
+    check_update $dst "${dep[@]}" && do_update $dst "${cmd[@]}" || true
 }
 
 ## bin wrappers
 function update_serial {
-    cmd $UPDATE_SERIAL $*
+    local serial=$1; shift
+    local old=$(cat $serial)
+
+    log_info "Updating serial: $serial"
+
+    cmd $UPDATE_SERIAL $* $serial
+    
+    local new=$(cat $serial)
+        
+    log_debug "  $old -> $new"
 }
 
 function expand_zone {
     local output=$1; shift
     local src=$1; shift
 
-
-    update $output $src $EXPAND_ZONE "$@"
 }
 
 function process_zone {
     local output=$1; shift
     local src=$1; shift
 
-    update $output $src $PROCESS_ZONE $PROCESS_ARGS "$@"
+    check_update $output $src && update $output $PROCESS_ZONE $PROCESS_ARGS "$@" $src
 }
 
 ## actions
@@ -177,21 +299,38 @@
     local part=$2
 
     local name=$zone.zone.$part
+    local src=$SETTINGS/$name
+    local dst=$ZONES/$name
 
-    log_info "Copying zone $zone/$part: zones/$name"
 
-    update $ZONES/$name $SETTINGS/$name cat
+    if check_update $dst $src; then
+        log_info "Copying zone $zone.$part..."
+
+        do_update $output cat $src
+    else
+        log_info "Copying zone $zone.$part: not changed"
+    fi
 }
 
 function update_zone {
     local zone=$1
+
     local name=$zone.zone
 
-    log_info "Generating $zone zone headers: zones/$name" 
+    local out=$ZONES/$name
+    local in=$SETTINGS/$zone.zone
+    local serial=$SERIALS/$zone.serial
 
-    expand_zone     $ZONES/$name    $SETTINGS/$zone.zone   \
-        --serial $SERIALS/$zone.serial  \
-        --expand zones=$ROOT/$ZONES
+    if check_update $out $in $serial; then
+        log_info "Generating $zone zone headers..." 
+
+        do_update $out \
+            $EXPAND_ZONE $SETTINGS/$zone.zone   \
+                --serial $SERIALS/$zone.serial  \
+                --expand zones=$ROOT/$ZONES
+    else
+        log_info "Generating $zone zone headers: not changed" 
+    fi
 }
 
 function update_zone_view {
@@ -200,12 +339,35 @@
 
     local name=$view/$zone.zone
 
-    log_info "Generating $zone:$view zone headers: zones/$name"
+    local out=$ZONES/$name
+    local in=$SETTINGS/$zone.zone
+    local serial=$SERIALS/$zone.serial
 
-    expand_zone     $ZONES/$name    $SETTINGS/$zone.zone   \
-        --serial $SERIALS/$zone.serial  \
-        --expand zones=$ROOT/$ZONES     \
-        --expand view=$view
+    if check_update $out $in $serial; then
+        log_info "Generating $zone:$view zone headers..."
+
+        do_update $out \
+            $EXPAND_ZONE $SETTINGS/$zone.zone   \
+                --serial $SERIALS/$zone.serial  \
+                --expand zones=$ROOT/$ZONES     \
+                --expand view=$view
+    else
+        log_info "Generating $zone:$view zone headers: not changed"
+    fi
+}
+
+function update_hosts {
+    local dst=$1; shift
+    local src=$1; shift
+
+
+    if check_update $dst $src; then
+        log_info "Generating $dst..."
+
+        do_update $dst $PROCESS_ZONE $PROCESS_ARGS $src "$@"
+    else
+        log_info "Generating $dst: not changed"
+    fi
 }
 
 function main {
@@ -214,18 +376,19 @@
 
     parse_args "$@"
 
+    log "Updating serials..."
+
     if [ $SERIAL_NOUPDATE ]; then
-        log_debug "skipping serial-update"
+        log_debug "skipping"
     else
-        log "Updating serials..."
-            update_serial   $SERIALS/paivola.serial
-            update_serial   $SERIALS/paivola-reverse.serial
+        update_serial   $SERIALS/paivola.serial
+        update_serial   $SERIALS/paivola-reverse.serial
     fi
 
     log "Generating host zones..."
-        process_zone    $ZONES/external/paivola.zone.hosts  $SETTINGS/paivola.txt --forward-zone
-        process_zone    $ZONES/internal/paivola.zone.hosts  $SETTINGS/paivola.txt --forward-zone --forward-txt --forward-mx $FORWARD_MX
-        process_zone    $ZONES/paivola-reverse.zone.hosts   $SETTINGS/paivola.txt --reverse-zone $REVERSE_ZONE --reverse-domain $REVERSE_DOMAIN
+        update_hosts    $ZONES/external/paivola.zone.hosts  $SETTINGS/paivola.txt --forward-zone
+        update_hosts    $ZONES/internal/paivola.zone.hosts  $SETTINGS/paivola.txt --forward-zone --forward-txt --forward-mx $FORWARD_MX
+        update_hosts    $ZONES/paivola-reverse.zone.hosts   $SETTINGS/paivola.txt --reverse-zone $REVERSE_ZONE --reverse-domain $REVERSE_DOMAIN
 
     log "Copying zone parts..."
         copy_zone_part      paivola             auto
@@ -233,7 +396,7 @@
         copy_zone_part      paivola             internal
         copy_zone_part      paivola             external
 
-    log "Updating zones..."
+    log "Updating zones headers..."
         update_zone         paivola-reverse
         update_zone_view    paivola             internal
         update_zone_view    paivola             external