--- a/bin/update Thu Feb 26 19:49:10 2015 +0200
+++ b/bin/update Thu Feb 26 21:38:09 2015 +0200
@@ -1,135 +1,21 @@
#!/bin/bash
-# vim: set ft=sh :
-
-# Bootstrap
-if [ $0 == './update' ]; then
- SRV=$(pwd)
- OPT=/opt/pvl-verkko
- LIB=/opt/pvl-dns/lib
-else
- SRV=${SRV:-/srv/verkko}
- OPT=${SRV:-/opt/pvl-verkko}
- LIB=/opt/pvl-dns/lib
- cd $SRV
-fi
-
-source $LIB/update
-
-function commit {
- ## Commit
- # pre-commit check
- log "Testing hosts..."
- for hosts in $(list_files etc/hosts); do
- log_warn "TODO: check_hosts $hosts"
- done
-
- # commit, unless noop'd
- log "Commit..."
- update_commit etc
-}
-
-function update {
- if hg_modified etc; then
- serial=$(unix_time)
- log_warn "Using local unix time for uncommited changes: $serial"
- else
- serial=$(hg_time etc)
- log_update "Using HG commit timestamp: $serial"
- fi
-
- ## Hosts
- log "Updating forward host zones..."
- for zone in $(list_dirs etc/hosts/forward); do
- update_hosts_forward "var/zones/hosts/forward/$zone" "$zone" \
- etc/hosts/forward/$zone/*
- done
-
- log "Updating DHCP hosts..."
- for hosts in $(list etc/hosts/dhcp); do
- update_hosts_dhcp "var/dhcp/hosts/$hosts.conf" $hosts \
- $(expand_files "etc/hosts/dhcp/$hosts")
- done
-
- log "Updating reverse host zones..."
- for zone in $(list_dirs etc/hosts/reverse); do
- update_hosts_reverse "var/zones/hosts/reverse/$zone" "$zone" \
- etc/hosts/reverse/$zone/*
- done
-
- ## Zones
- log "Copying zone includes..."
- for zone in $(list_files etc/zones/includes); do
- copy "var/zones/includes/$zone" "etc/zones/includes/$zone"
- done
- log "Updating zone serials..."
- for zone in $(list_files etc/zones); do
- update_serial "var/serials/$zone" $serial \
- "etc/zones/$zone" $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
- done
-
- log "Updating zones..."
- for zone in $(list_files etc/zones); do
- update_zone "var/zones/$zone" "etc/zones/$zone" "var/serials/$zone" \
- $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
- done
-
- log "Updating DHCP confs..."
- for conf in $(list_files etc/dhcp); do
- update_dhcp_conf "var/dhcp/$conf" "etc/dhcp/$conf"
- done
-}
-
-function deploy {
- ## Check
- log "Testing zones..."
- for zone in $(list_files etc/zones); do
- check_zone "var/zones/$zone" $zone
- done
-
- log "Testing DHCP confs..."
- for conf in var/dhcp/*.conf; do
- check_dhcp $conf
- done
-
- log "Reload zones..."
- reload_zones
+SRV=${SRV:-.}
+SRC=${OPT:-.}
+OPT=${OPT:-./opt}
+ETC=${ETC:-$SRV/etc}
+LIB=${LIB:-$SRC/lib}
+VAR=${VAR:-$SRV/var}
- log "Reload dhcp..."
- reload_dhcp
-
-}
-
-## Main entry point
-function main {
- log_init
-
- parse_args "$@"
- ## Input dirs
- for dir in etc etc/zones etc/hosts; do
- [ -d $dir ] || die "Missing directory: $dir"
- done
-
- ## Output dirs
- ensure_dir var
- for dir in var/dhcp var/zones var/include-cache var/serials; do
- ensure_dir $dir
- done
- for dir in var/dhcp/hosts; do
- ensure_dir $dir
- done
- for dir in var/zones/includes var/zones/hosts; do
- ensure_dir $dir
- done
- for dir in var/zones/hosts/forward var/zones/hosts/reverse; do
- ensure_dir $dir
- done
+. $LIB/pvl/main.sh
- ## Go
- commit
- update
- deploy
-}
+MODULES=(log commit apply update)
+MODULE=update
+
+. $LIB/pvl/commit.sh
+. $LIB/pvl/apply.sh
+
+. $LIB/pvl/hosts/update.sh
main "$@"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/apply.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,78 @@
+# Idempotent refreshable operations
+
+apply_GETOPTS='pFn'
+
+APPLY=
+APPLY_DIFF=
+
+function apply_help {
+ cat <<END
+Apply:
+ -p show changes
+ -F force-updates without checking src mtime
+ -n no-op/mock-update; don't actually change/deploy anything; implies -SpC
+END
+}
+
+function apply_opt {
+ local opt=$1
+ local optarg="$2"
+
+ case $opt in
+ p) APPLY_DIFF=1 ;;
+ F) APPLY=1 ;;
+
+ n)
+ APPLY=0
+ APPLY_DIFF=1
+ ;;
+ *) return 1
+ esac
+}
+
+. $LIB/pvl/apply/dir.sh
+. $LIB/pvl/apply/link.sh
+. $LIB/pvl/apply/cmd.sh
+. $LIB/pvl/apply/cat.sh
+
+## 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 apply_check {
+ local update=
+ local out="$1"
+
+ debug "$out"
+
+ if [ ${#@} -eq 1 ]; then
+ debug " update: unknown deps"
+ return 0
+
+ elif [ ! -e "$out" ]; then
+ debug " update: dest missing"
+ return 0
+
+ elif [ "$APPLY" = 1]; then
+ debug " update: forced"
+ return 2
+ fi
+
+ # check deps
+ for dep in "${@:2}"; do
+ # check
+ if [ ! -e "$dep" ]; then
+ warn "$out: Missing source: $dep"
+
+ elif [ "$out" -ot "$dep" ]; then
+ debug " update: $dep"
+ return 1
+ else
+ debug " check: $dep"
+ fi
+ done
+
+ debug " up-to-date"
+ return 0
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/apply/cat.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,15 @@
+function apply_cat {
+ local out="$1"
+ local src=("${@:2}")
+
+ if apply_check "$out" "${src[@]}"; then
+ log_skip "$out: skip cat: ${src[@]}"
+ elif [ "$APPLY" = 0 ]; then
+ log_noop "$out: noop cat: $tgt"
+ else
+ log_update "$out: cat ${src@}"
+
+ cmd_apply "$out" \
+ cat "${src[@]}"
+ fi
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/apply/cmd.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,35 @@
+## 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 cmd_apply {
+ local out="$1"
+ local tmp="$out.new"
+
+ debug "$out"
+ cmd "${@:1}" > "$tmp"
+
+ # compare
+ if [ -e "$out" -a -z "$APPLY_DIFF" ]; then
+ debug " changes:"
+
+ # terse
+ indent " " diff --unified=1 "$out" "$tmp" || true
+ fi
+
+ # deploy
+ if [ "$APPLY" = 0 ]; then
+ # cleanup
+ debug " no-op"
+
+ cmd rm "$tmp"
+ else
+ # commit
+ debug " deploy"
+
+ cmd mv "$tmp" "$out"
+ fi
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/apply/dir.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,13 @@
+# Create dir if not exists.
+function apply_dir {
+ local dir="$1"
+
+ if [ -d "$dir" ]; then
+ log_skip "$dir: apply dir"
+ elif [ "$APPLY" = 0 ]; then
+ log_noop "$dir: apply dir"
+ else
+ log_apply "$dir: apply dir"
+ cmd mkdir "$dir"
+ fi
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/apply/link.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,47 @@
+## Compare symlink to target:
+#
+# link_check $lnk $tgt && do_link $lnk $tgt || ...
+#
+# Tests if the symlink exists, and the target matches.
+# Fails if the symlink needs updating.
+function link_check {
+ local out="$1"
+ local lnk="$2"
+
+ [ -e "$out" ] || return 1
+
+ [ -L "$out" ] || fail "$out: is not a link"
+
+ [ "$(readlink "$out")" == "$lnk" ] || return 1
+
+ return 0
+}
+
+## Update symlink to point to target:
+#
+# do_link $lnk $tgt
+#
+function link_apply {
+ local out="$1"
+ local lnk="$2"
+
+ cmd ln -srf "$lnk" "$out"
+
+ [ -e "$out" ] || fail "$out: given target does not exist: $lnk"
+}
+
+# Create symlink if not exists.
+function apply_link {
+ local out="$1"
+ local lnk="$2"
+
+ if link_check "$out" "$lnk"; then
+ log_skip "$out: not changed: $lnk"
+ elif [ "$APPLY" = 0 ]; then
+ log_noop "$out: skip link: $tgt"
+ else
+ log_apply "$out: $tgt..."
+
+ link_apply "$out" "$lnk"
+ fi
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/cmd.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,34 @@
+### 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: $@"
+}
+
+## Execute command as a test, logging its execution at log_cmd
+#
+# cmd_test $cmd... && ... || ...
+#
+# Fails if the command returns an error exit code.
+function cmd_test {
+ log_cmd "$@"
+
+ "$@"
+}
+## Execute command, prefixing its output on stdout with given indent prefix.
+#
+# indent " " $cmd...
+#
+# Output is kept on stdout, exit status is that of the given command.
+function cmd_indent () {
+ local indent="$1"; shift
+
+ "$@" | sed "s/^/$indent/"
+
+ return ${PIPESTATUS[0]}
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/commit.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,90 @@
+# VCS abstraction layer
+
+. $LIB/pvl/commit/git.sh
+. $LIB/pvl/commit/hg.sh
+
+commit_GETOPTS='cCm:'
+
+COMMIT=
+COMMIT_DIFF=
+COMMIT_MSG=' '
+
+function commit_help {
+ cat <<END
+Commit:
+ -C do not commit changes
+ -c commit changes
+ -m MSG commit message
+END
+}
+
+function commit_opt {
+ local opt=$1
+ local optarg="$2"
+
+ case $opt in
+ c) COMMIT=1 ;;
+ C) COMMIT=0 ;;
+
+ m) COMMIT_MSG="$optarg" ;;
+
+ n) COMMIT= ;;
+ p) COMMIT_DIFF=1 ;;
+ *) return 1
+ esac
+}
+
+function commit_probe {
+ local repo="$1"
+
+ for commit in git hg; do
+ if ${commit}_probe "$repo"; then
+ echo $commit
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+## Commit changes to version control:
+#
+# update_commit .../etc "commit message"
+#
+# Invokes `hg commit`, first showing the diff.
+function commit {
+ local repo="$1"
+ local commit_msg="$COMMIT_MSG"
+
+ # detect
+ local commit="$(commit_probe "$repo")"
+
+ if [ -z "$commit" ]; then
+ log_warn "$repo: Unable to detect VCS repo"
+ return 1
+ fi
+
+ # operate?
+ if [ "$COMMIT" = 1 ]; then
+ log_force "$repo: force commit"
+
+ [ $COMMIT_DIFF ] && indent " " ${commit}_diff "$repo"
+
+ ${commit}_commit "$repo" "$commit_msg"
+
+ elif ! ${commit}_modified "$repo"; then
+ log_warn "$repo: no changes to commit"
+
+ elif [ "$COMMIT" = 0 ]; then
+ log_noop "$repo: skip commit"
+
+ # still show diff, though
+ [ $COMMIT_DIFF ] && indent " " ${commit}_diff "$repo"
+ else
+ log_apply "$repo: commit: $commit_msg"
+
+ [ $COMMIT_DIFF ] && indent " " ${commit}_diff "$repo"
+
+ ${commit}_commit "$repo" "$commit_msg"
+ fi
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/commit/hg.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,70 @@
+# HG wrappers
+
+HG=/usr/bin/hg
+HG_ARGS=(--config trusted.users=root)
+
+function hg_probe {
+ local repo=$1
+
+ [ -d "$repo/.hg" ]
+}
+
+## Run `hg ...` within $REPO.
+function hg {
+ local repo=$1
+ cmd $HG -R "$repo" "${HG_ARGS[@]:-}" "${@:2}"
+}
+
+## Does the repo have local modifications?
+function hg_modified {
+ hg $1 id -i | grep -q '+'
+}
+
+## Get the date for the current commit as an unix timestamp
+function hg_time {
+ local repo=$1
+ local hg_unix=
+ local hg_tz=
+
+ local hg_date=$(hg $repo log -r . --template '{date|hgdate}')
+ local hg_unix=${hg_date% *}
+ local hg_tz=${hg_date#* }
+
+ [ -n "$hg_unix" ] || fail "failed to read hg time"
+
+ echo "$hg_unix"
+}
+
+## Show changes in repo
+# hg_diff [path ...]
+function hg_diff {
+ local repo=$1
+ hg $repo diff "${@:2}"
+}
+
+## Commit changes in repo, with given message:
+#
+# hg_commit .../etc $msg
+#
+# Automatically determines possible -u to use when running with sudo.
+function hg_commit {
+ local repo="$1"
+ local msg="$2"
+ local opts=()
+
+ if [ ${SUDO_USER:-} ]; then
+ opts+=('-u' "$SUDO_USER")
+
+ elif [ $HOME ] && [ -e $HOME/.hgrc ]; then
+ debug "using .hgrc user"
+
+ else
+ opts+=('-u' "$USER")
+ fi
+
+ if [ "$msg" ]; then
+ opts+=('-m' "$msg")
+ fi
+
+ hg $repo commit "${opts[@]:-}"
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/file.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,49 @@
+### FS utils
+
+## Output absolute path
+#
+# abspath $path
+#
+# XXX: improve...?
+function abspath () {
+ local path="$1"
+
+ echo "$SRV/$path"
+}
+
+function _list {
+ local glob="$1"
+ local test="$2"
+ local prefix="$3"
+
+ for file in $glob; do
+ [ $test "$file" ] || continue
+ [ -n "$prefix" ] && file="${file#$prefix}"
+
+ echo -n "$file "
+ done
+}
+
+## List names of all files in dir
+function list {
+ _list "$1/*" '-e' ${2:-$1/}
+}
+
+## List names of files in dir:
+#
+# list_files $dir
+#
+function list_files {
+ _list "$1/*" '-f' ${2:-$1/}
+}
+
+## List names of dirs in dir:
+function list_dirs {
+ _list "$1/*" '-d' ${2:-$1/}
+}
+
+## List names of any files underneath dir or file:
+function expand_files {
+ _list "$1 $1/**" '-f' ''
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/hosts/update.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,152 @@
+update_GETOPTS='sSrR'
+
+UPDATE_SERIAL=
+UPDATE_RELOAD=
+
+function update_help {
+ cat <<END
+Update:
+ -s force update serials
+ -S do not update serial
+
+ -r force reload zones/dhcp
+ -R do not reload zones/dhcp
+END
+}
+
+function update_opt {
+ local opt=$1
+ local optarg="$2"
+
+ case $opt in
+ s) UPDATE_SERIAL=1 ;;
+ S) UPDATE_SERIAL=0 ;;
+
+ r) UPDATE_RELOAD=1 ;;
+ R) UPDATE_RELOAD=0 ;;
+
+ n)
+ UPDATE_SERIAL=0
+ UPDATE_RELOAD=0
+ ;;
+ *) return 1
+ esac
+}
+
+function update_setup {
+ for dir in etc etc/zones etc/hosts; do
+ [ -d $dir ] || die "$dir: missing source directory"
+ done
+
+ apply_dir $VAR
+
+ for dir in $VAR/dhcp $VAR/zones $VAR/include-cache $VAR/serials; do
+ apply_dir $dir
+ done
+ for dir in $VAR/dhcp/hosts; do
+ apply_dir $dir
+ done
+ for dir in $VAR/zones/includes $VAR/zones/forward $VAR/zones/reverse; do
+ apply_dir $dir
+ done
+}
+
+function update_commit {
+ ## Commit
+ # pre-commit check
+ log "Testing hosts..."
+ for hosts in $(list_files etc/hosts); do
+ log_warn "TODO: check_hosts $hosts"
+ done
+
+ # commit, unless noop'd
+ log "Commit..."
+ commit $SRV
+}
+
+function update_update {
+ if hg_modified etc; then
+ serial=$(unix_time)
+ log_warn "Using local unix time for uncommited changes: $serial"
+ else
+ serial=$(hg_time etc)
+ log_update "Using HG commit timestamp: $serial"
+ fi
+
+ ## Hosts
+ log "Updating forward host zones..."
+ for zone in $(list_dirs etc/hosts/forward); do
+ update_hosts_forward "var/zones/hosts/forward/$zone" "$zone" \
+ etc/hosts/forward/$zone/*
+ done
+
+ log "Updating DHCP hosts..."
+ for hosts in $(list etc/hosts/dhcp); do
+ update_hosts_dhcp "var/dhcp/hosts/$hosts.conf" $hosts \
+ $(expand_files "etc/hosts/dhcp/$hosts")
+ done
+
+ log "Updating reverse host zones..."
+ for zone in $(list_dirs etc/hosts/reverse); do
+ update_hosts_reverse "var/zones/hosts/reverse/$zone" "$zone" \
+ etc/hosts/reverse/$zone/*
+ done
+
+ ## Zones
+ log "Copying zone includes..."
+ for zone in $(list_files etc/zones/includes); do
+ copy "var/zones/includes/$zone" "etc/zones/includes/$zone"
+ done
+
+ log "Updating zone serials..."
+ for zone in $(list_files etc/zones); do
+ update_serial "var/serials/$zone" $serial \
+ "etc/zones/$zone" $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+ done
+
+ log "Updating zones..."
+ for zone in $(list_files etc/zones); do
+ update_zone "var/zones/$zone" "etc/zones/$zone" "var/serials/$zone" \
+ $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+ done
+
+ log "Updating DHCP confs..."
+ for conf in $(list_files etc/dhcp); do
+ update_dhcp_conf "var/dhcp/$conf" "etc/dhcp/$conf"
+ done
+}
+
+function update_reload {
+ ## Check
+ log "Testing zones..."
+ for zone in $(list_files etc/zones); do
+ check_zone "var/zones/$zone" $zone
+ done
+
+ log "Testing DHCP confs..."
+ for conf in var/dhcp/*.conf; do
+ check_dhcp $conf
+ done
+
+ log "Reload zones..."
+ reload_zones
+
+ log "Reload dhcp..."
+ reload_dhcp
+
+}
+
+## Main entry point
+function update_main {
+ ## Setup source/output dirs
+ update_setup
+
+ ## Commit source dirs
+ update_commit
+
+ ## Update output from sources
+ update_update
+
+ ## Reload output
+ update_reload
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/log.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,152 @@
+# Logging output
+
+log_GETOPTS="qvDV"
+
+LOG=y
+LOG_ERROR=y
+LOG_WARN=y
+LOG_FORCE=y
+LOG_APPLY=y
+LOG_NOOP=y
+LOG_SKIP=
+LOG_DEBUG=
+LOG_CMD=
+LOG_DIFF=y
+
+# use color output?
+IS_TTY=
+
+function log_help {
+ cat <<END
+Logging:
+ -q quiet
+ -v verbose
+ -D debug
+ -V debug commands
+END
+}
+
+function log_opt {
+ local opt=$1
+ local optarg="$2"
+
+ case $opt in
+ q)
+ LOG=
+ LOG_WARN=
+ LOG_APPLY=
+ LOG_FORCE=
+ LOG_NOOP=
+ LOG_DIFF=
+ ;;
+
+ v) LOG_SKIP=y ;;
+ D)
+ LOG_DEBUG=y
+ LOG_INFO=y
+ ;;
+ V) LOG_CMD=y ;;
+
+ *) return 1
+ esac
+}
+
+function log_init {
+ [ -t 1 ] && IS_TTY=y
+}
+
+# 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
+# plain
+function log {
+ [ $LOG ] && log_msg "$*" || true
+}
+
+function log_error {
+ [ $LOG_ERROR ] && log_color '31' "$*" || true
+}
+
+function log_warn {
+ [ $LOG_WARN ] && log_color '33' "$*" || true
+}
+
+function log_force {
+ [ $LOG_FORCE ] && log_color '2;33' " $*" || true
+}
+
+function log_apply {
+ [ $LOG_APPLY ] && log_color '36' " $*" || true
+}
+
+function log_check {
+ [ $LOG_APPLY ] && log_color '37' " $*" || 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
+}
+
+### High-level logging output
+# Log with func_caller at log_debug
+function debug {
+ printf -v prefix "%s" $(func_caller)
+
+ log_debug "$prefix: $*"
+}
+
+function warn {
+ log_warn "$(func_caller): $*"
+}
+
+# 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
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/main.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,70 @@
+set -ue
+
+shopt -s globstar nullglob
+
+. $LIB/pvl/util.sh
+. $LIB/pvl/cmd.sh
+. $LIB/pvl/log.sh
+
+function main_help {
+ cat <<END
+Usage: $0 [options]
+
+General:
+ -h display this help text
+END
+
+ for module in ${MODULES[@]}; do
+ ${module}_help
+ done
+}
+
+function main_opts {
+ local module=
+
+ # build opts string
+ local opts=$(
+ echo -n 'h'
+
+ for module in ${MODULES[@]}; do
+ module_getopts=${module}_GETOPTS
+ echo -n ${!module_getopts}
+ done
+ )
+
+ local OPTIND
+ while getopts "$opts" opt; do
+ local opt_module=
+
+ if [ "$opt" = 'h' ]; then
+ main_help
+ exit 0;
+ fi
+
+ for module in ${MODULES[@]}; do
+ if ${module}_opt $opt "${OPTARG:-}"; then
+ opt_module=$module
+ fi
+ done
+
+ if [ "$opt_module" ]; then
+ continue
+ else
+ die "opt: $opt"
+ fi
+ done
+ shift $(($OPTIND - 1))
+}
+
+function main {
+ local module=
+
+ main_opts "$@"
+
+ for module in ${MODULES[@]}; do
+ func_test ${module}_init && ${module}_init
+ done
+
+ ${MODULE}_main
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/test.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,22 @@
+
+## Run test command on given file, outputting results if it fails.
+#
+# check $src $cmd $args...
+#
+function test_cmd {
+ local src="$1"
+ local cmd="$2"
+ local args="${@:3}"
+
+ if cmd_test "$cmd" -q "${args[@]}"; then
+ log_skip "$src: test: OK"
+
+ else
+ log_error "$src: test: Failed"
+
+ indent " " "$cmd" "${args[@]}"
+
+ return 1
+ fi
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/pvl/util.sh Thu Feb 26 21:38:09 2015 +0200
@@ -0,0 +1,15 @@
+## Output calling function's name.
+function func_caller {
+ caller 1 | cut -d ' ' -f 2
+}
+
+## Test if given symbol is a function
+# XXX: tests if it is anything atm?
+function func_test {
+ type -t "$1" > /dev/null
+}
+
+## Get current unix (utc) timestamp
+function unix_time {
+ date +'%s'
+}
--- a/lib/update Thu Feb 26 19:49:10 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-#!/bin/bash
-#
-
-## Strict errors
-set -ue
-
-shopt -s globstar nullglob
-
-## Library includes
-for lib in $LIB/update.*; do
- source $lib
-done
-
--- a/lib/update.args Thu Feb 26 19:49:10 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-#!/bin/bash
-# vim: set ft=sh :
-#
-# Command-line options
-
-## Options
-
-SERIAL_NOOP=
-SERIAL_FORCE=
-
-COMMIT_SKIP=
-COMMIT_FORCE=
-COMMIT_MSG=' '
-
-RELOAD_NOOP=
-RELOAD_FORCE=
-
-## 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
-
-Deploy:
- -R do not reload zones/dhcp
- -r force reload zones/dhcp
-END
-}
-
-## Parse any command-line arguments, setting the global options vars.
-function parse_args {
- OPTIND=1
-
- while getopts 'hd:qvDVpFSsnCcm:Rr' opt "$@"; do
- case $opt in
- h)
- help_args $0
- exit 0
- ;;
-
- d) SRV="$OPTARG" ;;
-
- q)
- LOG=
- LOG_WARN=
- LOG_UPDATE=
- LOG_FORCE=
- LOG_NOOP=
- LOG_DIFF=
- ;;
-
- 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_NOOP=y
- COMMIT_SKIP=y
- RELOAD_NOOP=y
- ;;
-
- C) COMMIT_SKIP=y ;;
- c) COMMIT_FORCE=y ;;
- m) COMMIT_MSG="$OPTARG" ;;
-
- R) RELOAD_NOOP=y ;;
- r) RELOAD_FORCE=y ;;
-
- ?)
- die
- ;;
- esac
-
- done
-}
-
-
--- a/lib/update.config Thu Feb 26 19:49:10 2015 +0200
+++ b/lib/update.config Thu Feb 26 21:38:09 2015 +0200
@@ -8,9 +8,6 @@
DHCPD_CONF=/etc/dhcp/dhcpd.conf
DHCPD_INIT=/etc/init.d/isc-dhcp-server
-HG=/usr/bin/hg
-HG_ARGS=(--config trusted.users=root)
-
RNDC=/usr/sbin/rndc
RNDC_KEY=/etc/bind/rndc.key
--- a/lib/update.hg Thu Feb 26 19:49:10 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-#!/bin/bash
-#
-# HG wrappers
-
-## Run `hg ...` within $REPO.
-function hg {
- local repo=$1; shift
- cmd $HG -R "$repo" "${HG_ARGS[@]}" "$@"
-}
-
-## Does the repo have local modifications?
-function hg_modified {
- hg $1 id -i | grep -q '+'
-}
-
-## Get the date for the current commit as an unix timestamp
-function hg_time {
- local repo=$1
- local hg_unix=
- local hg_tz=
-
- local hg_date=$(hg $repo log -r . --template '{date|hgdate}')
- local hg_unix=${hg_date% *}
- local hg_tz=${hg_date#* }
-
- [ -n "$hg_unix" ] || fail "failed to read hg time"
-
- echo "$hg_unix"
-}
-
-## Show changes in repo
-# hg_diff [path ...]
-function hg_diff {
- local repo=$1; shift
- hg $repo diff "$@"
-}
-
-## Commit changes in repo, with given message:
-#
-# hg_commit .../etc $msg
-#
-# Automatically determines possible -u to use when running with sudo.
-function hg_commit {
- local repo="$1"
- local msg="$2"
- local user_opt=
- local msg_opt=
-
- if [ ${SUDO_USER:-} ]; then
- user_opt=('-u' "$SUDO_USER")
-
- elif [ $HOME ] && [ -e $HOME/.hgrc ]; then
- debug "using .hgrc user"
- user_opt=( )
-
- else
- user_opt=('-u' "$USER")
- fi
-
- if [ "$msg" ]; then
- msg_opt=('-m' "$msg")
- fi
-
- # XXX: there's something about bash arrays that I don't like... empty arrays behave badly
- # mercurial does not like it if you pass it '' as an argument
- if [ -n "${user_opt:-}" -a -n "${msg_opt:-}" ]; then
- hg $repo commit "${user_opt[@]}" "${msg_opt[@]}"
- elif [ -n "${user_opt:-}" ]; then
- hg $repo commit "${user_opt[@]}"
- elif [ -n "${msg_opt:-}" ]; then
- hg $repo commit "${msg_opt[@]}"
- else
- hg $repo commit
- fi
-}
--- a/lib/update.log Thu Feb 26 19:49:10 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-#!/bin/bash
-# vim: set ft=sh :
-#
-# Logging output
-
-# use color output?
-IS_TTY=
-
-LOG_ERROR=y
-LOG_WARN=y
-LOG=y
-LOG_FORCE=y
-LOG_UPDATE=y
-LOG_NOOP=y
-LOG_SKIP=
-LOG_DEBUG=
-LOG_CMD=
-LOG_DIFF=y
-
-function log_init {
- [ -t 1 ] && IS_TTY=y
-}
-
-# 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
-# plain
-function log {
- [ $LOG ] && log_msg "$*" || true
-}
-
-function log_error {
- [ $LOG_ERROR ] && log_color '31' "$*" || true
-}
-
-function log_warn {
- [ $LOG_WARN ] && log_color '33' "$*" || true
-}
-
-function log_force {
- [ $LOG_FORCE ] && log_color '2;33' " $*" || true
-}
-
-function log_update {
- [ $LOG_UPDATE ] && log_color '36' " $*" || true
-}
-
-function log_check {
- [ $LOG_UPDATE ] && log_color '37' " $*" || 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: $*"
-}
-
-function warn {
- log_warn "$(func_caller): $*"
-}
-
-# 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
-}
-
-
--- a/lib/update.operations Thu Feb 26 19:49:10 2015 +0200
+++ b/lib/update.operations Thu Feb 26 21:38:09 2015 +0200
@@ -3,51 +3,37 @@
#
# Operations on zonefiles/hosts/whatever
-function link {
- local out="$1"
- local tgt="$2"
-
- if check_link "$out" "$tgt"; then
- log_skip "Linking $out -> $tgt: not changed"
- else
- log_update "Linking $out -> $tgt..."
-
- do_link "$out" "$tgt"
- fi
+## Read include paths from file
+function read_zone_includes {
+ cmd sed -n -E 's/^\$INCLUDE\s+"(.+)"/\1/p' "$@"
}
-function copy {
- local out="$1"
+## (cached) include paths for zone file
+function zone_includes {
+ local cache="$1"
local src="$2"
+ local prefix="${3:-}"
- if check_update "$out" "$src"; then
- log_update "Copying $out <- $src..."
+ if [ ! -e "$cache" -o "$cache" -ot "$src" ]; then
+ read_zone_includes "$src" > "$cache"
+ fi
- do_update "$out" \
- cat "$src"
- else
- log_skip "Copying $out <- $src: not changed"
- fi
+ while read include; do
+ echo -n "$prefix$include "
+ done < "$cache"
}
-## Run check-command on given file, outputting results:
-#
-# check $src $cmd $args...
-#
-function check {
- local src="$1"; shift
- local cmd="$1"; shift
-
- if cmd_test "$cmd" -q "$@"; then
- log_skip "Check $src: OK"
-
- else
- log_error " Check $src: Failed"
-
- indent " " "$cmd" "$@"
-
- exit 1
- fi
+## Search for prefix-matching includes in zone file
+function zone_includes_grep {
+ local cache="$1"
+ local src="$2"
+ local prefix="$3"
+
+ for include in $(zone_includes $cache $src); do
+ if [ "${include#$prefix}" != "$include" ]; then
+ echo -n " ${include#$prefix}"
+ fi
+ done
}
## Generate forward zone from hosts hosts using pvl.hosts-dns:
@@ -335,39 +321,4 @@
fi
}
-### Commit
-## Commit changes to version control:
-#
-# update_commit .../etc "commit message"
-#
-# Invokes `hg commit`, first showing the diff.
-function update_commit {
- local repo="$1"
- local commit_msg="$COMMIT_MSG"
-
- local msg="Commit changes"
-
- # operate?
- if [ $COMMIT_FORCE ]; then
- log_force "$msg: $commit_msg"
-
- [ $LOG_DIFF ] && indent " " hg_diff $repo
- hg_commit "$repo" "$commit_msg"
-
- elif ! hg_modified "$repo"; then
- log_warn "$msg: no changes"
-
- elif [ $COMMIT_SKIP ]; then
- log_noop "$msg: skipped"
-
- # still show diff, though
- [ $LOG_DIFF ] && indent " " hg_diff "$repo"
- else
- log_update "$msg: $commit_msg"
-
- [ $LOG_DIFF ] && indent " " hg_diff $repo
-
- hg_commit "$repo" "$commit_msg"
- fi
-}
--- a/lib/update.updates Thu Feb 26 19:49:10 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-#!/bin/bash
-# vim: set ft=sh :
-#
-# Dependency-based updates + utils
-
-UPDATE_FORCE=
-UPDATE_NOOP=
-UPDATE_DIFF=
-
-## 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 "$dep" ]; then
- warn "$out: Missing source: $dep"
-
- elif [ "$out" -ot "$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"
-
- debug "$out"
- cmd "$@" > "$tmp"
-
- # compare
- if [ -e "$out" ] && [ $UPDATE_DIFF ]; then
- debug " changes:"
-
- # terse
- indent " " diff --unified=1 "$out" "$tmp" || true
- fi
-
- # deploy
- if [ $UPDATE_NOOP ]; then
- # cleanup
- debug " no-op"
-
- cmd rm "$tmp"
- else
- # commit
- debug " deploy"
-
- cmd mv "$tmp" "$out"
- fi
-}
-
-## Compare symlink to target:
-#
-# check_link $lnk $tgt && do_link $lnk $tgt || ...
-#
-# Tests if the symlink exists, and the target matches.
-# Fails if the symlink needs updating.
-function check_link {
- local out="$1"
- local tgt="$2"
-
- [ -e "$out" ] || return 1
-
- [ -L "$out" ] || fail "$out: is not a link"
-
- [ "$(readlink "$out")" == "$tgt" ] || return 1
-
- return 0
-}
-
-## Update symlink to point to target:
-#
-# do_link $lnk $tgt
-#
-function do_link {
- local out="$1"
- local tgt="$2"
-
- cmd ln -sf "$tgt" "$out"
-
- [ -e "$out" ] || fail "$out: given target does not exist: $tgt"
-}
-
-## Read include paths from file
-function read_zone_includes {
- cmd sed -n -E 's/^\$INCLUDE\s+"(.+)"/\1/p' "$@"
-}
-
-## (cached) include paths for zone file
-function zone_includes {
- local cache="$1"
- local src="$2"
- local prefix="${3:-}"
-
- if [ ! -e "$cache" -o "$cache" -ot "$src" ]; then
- read_zone_includes "$src" > "$cache"
- fi
-
- while read include; do
- echo -n "$prefix$include "
- done < "$cache"
-}
-
-## Search for prefix-matching includes in zone file
-function zone_includes_grep {
- local cache="$1"
- local src="$2"
- local prefix="$3"
-
- for include in $(zone_includes $cache $src); do
- if [ "${include#$prefix}" != "$include" ]; then
- echo -n " ${include#$prefix}"
- fi
- done
-}
--- a/lib/update.utils Thu Feb 26 19:49:10 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-#!/bin/bash
-#
-# Utility functions
-
-### 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: $@"
-}
-
-## Execute command as a test, logging its execution at log_cmd
-#
-# cmd_test $cmd... && ... || ...
-#
-# Fails if the command returns an error exit code.
-function cmd_test {
- log_cmd "$@"
-
- "$@"
-}
-## Execute command, prefixing its output on stdout with given indent prefix.
-#
-# indent " " $cmd...
-#
-# Output is kept on stdout, exit status is that of the given command.
-function indent () {
- local indent="$1"; shift
-
- "$@" | sed "s/^/$indent/"
-
- return ${PIPESTATUS[0]}
-}
-
-
-### FS utils
-# Create dir if not exists.
-function ensure_dir {
- local dir="$1"
-
- if [ ! -d "$dir" ]; then
- log_warn "Creating output dir: $dir"
- cmd mkdir "$dir"
- fi
-}
-
-## Output absolute path
-#
-# abspath $path
-#
-# XXX: improve...?
-function abspath () {
- local path="$1"
-
- echo "$SRV/$path"
-}
-
-function _list {
- local glob="$1"
- local test="$2"
- local prefix="$3"
-
- for file in $glob; do
- [ $test "$file" ] || continue
- [ -n "$prefix" ] && file="${file#$prefix}"
-
- echo -n "$file "
- done
-}
-
-## List names of all files in dir
-function list {
- _list "$1/*" '-e' ${2:-$1/}
-}
-
-## List names of files in dir:
-#
-# list_files $dir
-#
-function list_files {
- _list "$1/*" '-f' ${2:-$1/}
-}
-
-## List names of dirs in dir:
-function list_dirs {
- _list "$1/*" '-d' ${2:-$1/}
-}
-
-## List names of any files underneath dir or file:
-function expand_files {
- _list "$1 $1/**" '-f' ''
-}
-
-## Get current unix (utc) timestamp
-function unix_time {
- date +'%s'
-}