bin/update
author Tero Marttila <terom@paivola.fi>
Fri, 16 Mar 2012 15:03:31 +0200
changeset 13 7d02a07e0354
parent 10 26e789db3f72
child 14 b883ef452cd8
permissions -rwxr-xr-x
update: check_hosts
#!/bin/bash
# vim: set ft=sh :

set -ue

ROOT=$(pwd)

BIN=bin
PROCESS_ZONE=$BIN/process-zone
EXPAND_ZONE=$BIN/expand-zone
UPDATE_SERIAL=$BIN/update-serial

SETTINGS=settings
ZONES=zones
SERIALS=serials

PROCESS_ARGS='--input-charset latin-1'

FORWARD_MX=mail
REVERSE_ZONE=194.197.235
REVERSE_DOMAIN=paivola.fi

NAMED_CHECKZONE=/usr/sbin/named-checkzone

## options
IS_TTY=

LOG=y
LOG_INFO=
LOG_DEBUG=
LOG_CMD=

UPDATE_FORCE=
UPDATE_NOOP=
UPDATE_DIFF=
SERIAL_NOUPDATE=

function help_args {
    local prog=$1

    cat <<END
Usage: $prog [options]

    -h      display this help text

    -q      quiet
    -v      verbose
    -D      debug
    -C      debug commands
    
    -p      show changes

    -F      force-updates without checking src mtime
    -S      do not update serial
    -n      no-op/mock-update; do not actually change anything; implies -Sp
END
}

function parse_args {
    OPTIND=1

    while getopts 'hqvDCpFSn' opt "$@"; do
        case $opt in
            h)  
                help_args $1
                exit 0
            ;;

            q)  LOG= ;;
            v)  LOG_INFO=y ;;
            D)  LOG_DEBUG=y  ;;
            C)  LOG_CMD=y ;;
            p)  UPDATE_DIFF=y ;;
            F)  UPDATE_FORCE=y ;;
            S)  SERIAL_NOUPDATE=y ;;
 
            n)  
                UPDATE_NOOP=y 
                # implies -Sp
                UPDATE_DIFF=y
                SERIAL_NOUPDATE=y
                ;;

           
            ?)  
                die 
            ;;
        esac

    done
}

## lib
function log_msg {
    echo "$*" >&2
}

function log_color {
    local code=$1; shift

    if [ $IS_TTY ]; then
        echo $'\e[0;'${code}'m'"$*"$'\e[00m' >&2
    else
        echo "$*" >&2
    fi
}

function log_error {
    log_color 31 "$*"
}

function log {
    [ $LOG ] && log_msg "$*" || true
}

function log_info {
    [ $LOG_INFO ] && log_color 36 "  $*" || true
}

function log_debug {
    [ $LOG_DEBUG ] && log_color 32 "    $*" || true
}

function log_cmd {
    [ $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 {
    log_error "$*"
    exit 1
}

function cmd {
    log_cmd "$@"

    "$@" || die "Failed"
}

function run_cmd {
    local msg=$1; shift

    log_info "$msg... "

    cmd "$@"
}

function indent () {
    local indent=$1; shift

    "$@" | (
        while read line; do
            echo "$indent$line"
        done
    ) || exit $?
}

## test
[ -d $SETTINGS ] || die "Missing settings: $SETTINGS"
[ -d $SERIALS ] || die "Missing serials: $SERIALS"
[ -d $ZONES ] || die "Missing zones: $ZONES"

## functions
function check_update {
    # target
    local dst=$1; shift

    log_debug "check_update: $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

    log_debug "update: $dst"
    cmd "$@" > $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 "  update"

        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

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

}

function process_zone {
    local output=$1; shift
    local src=$1; shift

    check_update $output $src && update $output $PROCESS_ZONE $PROCESS_ARGS "$@" $src
}

## actions
function copy_zone_part {
    local zone=$1
    local part=$2

    local name=$zone.zone.$part
    local src=$SETTINGS/$name
    local dst=$ZONES/$name


    if check_update $dst $src; then
        log_info "Copying zone $zone.$part..."

        do_update $dst cat $src
    else
        log_info "Copying zone $zone.$part: not changed"
    fi
}

function update_zone {
    local zone=$1

    local name=$zone.zone

    local out=$ZONES/$name
    local in=$SETTINGS/$zone.zone
    local serial=$SERIALS/$zone.serial

    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 {
    local zone=$1
    local view=$2

    local name=$view/$zone.zone

    local out=$ZONES/$name
    local in=$SETTINGS/$zone.zone
    local serial=$SERIALS/$zone.serial

    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 check_hosts {
    local hosts=$1; shift 1

    local cmd=($PROCESS_ZONE $PROCESS_ARGS $hosts --check-hosts "$@")

    if "${cmd[@]}" -q; then
        log_info "Check $hosts: OK"
    else
        log_error "  Check $hosts: Failed"

        indent "    " "${cmd[@]}"

        exit 1
    fi
}

function check_zone {
    local name=$1
    local file=$2

    local cmd=($NAMED_CHECKZONE $name $file)

    # test
    # XXX: checkzone is very specific about the order of arguments, -q must be first
    if $NAMED_CHECKZONE -q $name $file; then
        log_info "Check $file($name): OK"
    else
        log_error "  Check $file($name): Failed:"

        indent "    " "${cmd[@]}"
        
        exit 1
    fi
}

function main {
    # test tty
    [ -t 1 ] && IS_TTY=y

    parse_args "$@"

    log "Testing hosts..."
        check_hosts     $SETTINGS/paivola.txt --check-exempt ufc

    log "Generating host zones..."
        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
        copy_zone_part      paivola             services
        copy_zone_part      paivola             internal
        copy_zone_part      paivola             external

    log "Updating serials..."

    if [ $SERIAL_NOUPDATE ]; then
        log_info "Skipped"
    else
        update_serial   $SERIALS/paivola.serial
        update_serial   $SERIALS/paivola-reverse.serial
    fi


    log "Updating zones headers..."
        update_zone         paivola-reverse
        update_zone_view    paivola             internal
        update_zone_view    paivola             external

    log "Testing zones..."
        check_zone          paivola.fi                  $ZONES/external/paivola.zone
        check_zone          paivola.fi                  $ZONES/external/paivola.zone
        check_zone          235.197.194.in-addr.arpa    $ZONES/paivola-reverse.zone

}

main "$@"