diff options
author | tv <tv@krebsco.de> | 2017-12-15 00:12:49 +0100 |
---|---|---|
committer | tv <tv@krebsco.de> | 2017-12-15 00:12:49 +0100 |
commit | 05534c806db2883e28f9905bffae9bd30c73b838 (patch) | |
tree | 2974c0f57644097a45ece944dff9ee8d85309e00 /bin | |
parent | ca12db51773bfcc3247842fb68a7a896bd0b04d1 (diff) |
replace jq-generated sh by direct shv2.0.0
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/populate | 302 |
1 files changed, 236 insertions, 66 deletions
diff --git a/bin/populate b/bin/populate index fdc4c5d..ab7dce5 100755 --- a/bin/populate +++ b/bin/populate @@ -1,75 +1,245 @@ #! /bin/sh set -efu -self=$(readlink -f "$0") -basename=${0##*/} -prefix=${self%/bin/*} -libdir=$prefix/lib +main() {( + self=$(readlink -f "$0") + basename=${0##*/} -debug=false -force=false -origin_host=${HOSTNAME-cat /proc/sys/kernel/hostname} -origin_user=$LOGNAME -ssh_cmd=ssh -target_spec= + debug=false + force=false + origin_host=${HOSTNAME-cat /proc/sys/kernel/hostname} + origin_user=$LOGNAME + target_spec= -fail=true + fail=true -error() { - echo "$basename: error: $1" >&2 - fail=false -} + error() { + echo "$basename: error: $1" >&2 + fail=false + } -for arg; do - case $arg in - --debug) - debug=true - ;; - --force) - force=true - ;; - --ssh=*) - ssh_cmd=${arg#--ssh=} - ;; - -*) - error "bad argument: $arg" - ;; - *) - if test -n "$target_spec"; then + for arg; do + case $arg in + --force) + force=true + ;; + -*) error "bad argument: $arg" - else - target_spec=$arg - fi - ;; - esac -done - -if test -z "$target_spec"; then - error 'no target specified' -fi - -if test "$fail" != true; then - exit 11 -fi - - -script=$(jq -e -r \ - --argjson use_force "$force" \ - --arg ssh_cmd "$ssh_cmd" \ - --arg target_spec "$target_spec" \ - --arg origin_host "$origin_host" \ - --arg origin_user "$origin_user" \ - -f "$libdir/populate.jq") - -if test -z "$script"; then - error 'no script produced' - exit 12 -fi - - -if test "$debug" = true; then - echo "$script" -else - eval "$script" -fi + ;; + *) + if test -n "$target_spec"; then + error "bad argument: $arg" + else + target_spec=$arg + fi + ;; + esac + done + + if test -z "$target_spec"; then + error 'no target specified' + fi + + if test "$fail" != true; then + exit 11 + fi + + target=$( + echo "$target_spec" | jq -R ' + def default(value; f): if . == null then value else f end; + def default(value): default(value; .); + + match("^(?:([^@]+)@)?([^:/]+)(?::([^/]*))?(/.*)?") + | { + user: .captures[0].string | default("root"), + host: .captures[1].string, + port: .captures[2].string | default(22; + if test("^[0-9]+$") then fromjson else + error(@json "bad target port: \(.)") + end), + path: .captures[3].string | default("/var/src"), + } + ' + ) + + target_host=$(echo $target | jq -r .host) + target_path=$(echo $target | jq -r .path) + target_port=$(echo $target | jq -r .port) + target_user=$(echo $target | jq -r .user) + + if test "$force" = true; then + force_target + else + check_target + fi + + jq -c 'to_entries | group_by(.value.type) | flatten[]' | + while read -r source; do + key=$(echo "$source" | jq -r .key) + type=$(echo "$source" | jq -r .value.type) + case $type in + file) + file_path=$(echo "$source" | jq -r .value.file.path) + populate_file "$key" "$file_path" + ;; + git) + git_url=$(echo "$source" | jq -r .value.git.url) + git_ref=$(echo "$source" | jq -r .value.git.ref) + populate_git "$key" "$git_url" "$git_ref" + ;; + symlink) + symlink_target=$(echo "$source" | jq -r .value.symlink.target) + populate_symlink "$key" "$symlink_target" + ;; + *) + echo "Warning: ignoring $source" >&2 + ;; + esac + done +)} + +# Safeguard to prevent clobbering of misspelled targets. +# This function has to be called first. +check_target() { + { + echo target_host=$(quote "$target_host") + echo target_path=$(quote "$target_path") + echo 'sentinel_file=$target_path/.populate' + echo 'if ! test -f "$sentinel_file"; then' + echo ' echo "error: missing sentinel file: $target_host:$sentinel_file" >&2' + echo ' exit 1' + echo 'fi' + } \ + | + target_shell +} + +force_target() { + { + echo target_path=$(quote "$target_path") + echo 'sentinel_file=$target_path/.populate' + echo 'mkdir -vp "$target_path"' + echo 'touch "$sentinel_file"' + } \ + | + target_shell +} + +is_local_target() { + test "$target_host" = "$origin_host" && + test "$target_user" = "$origin_user" +} + +populate_file() {( + print_info populate_file "$@" + + file_name=$1 + file_path=$2 + + if is_local_target; then + file_target=$target_path/$file_name + else + file_target=$target_user@$target_host:$target_path/$file_name + fi + + rsync \ + -vFrlptD \ + --delete-excluded \ + "$file_path"/ \ + -e "ssh -o ControlPersist=no -p $target_port" \ + "$file_target" +)} + +populate_git() {( + print_info populate_git "$@" + + git_name=$1 + git_url=$2 + git_ref=$3 + + git_work_tree=$target_path/$git_name + + { + echo set -efu + + echo git_name=$(quote "$git_name") + echo git_url=$(quote "$git_url") + echo git_ref=$(quote "$git_ref") + + echo git_work_tree=$(quote "$git_work_tree") + + echo 'if ! test -e "$git_work_tree"; then' + echo ' git clone "$git_url" "$git_work_tree"' + echo 'fi' + + echo 'cd $git_work_tree' + + echo 'if ! url=$(git config remote.origin.url); then' + echo ' git remote add origin "$git_url"' + echo 'elif test "$url" != "$git_url"; then' + echo ' git remote set-url origin "$git_url"' + echo 'fi' + + # TODO resolve git_ref to commit hash + echo 'hash=$git_ref' + + echo 'if ! test "$(git log --format=%H -1)" = "$hash"; then' + echo ' if ! git log -1 "$hash" >/dev/null 2>&1; then' + echo ' git fetch origin' + echo ' fi' + echo ' git checkout "$hash" -- "$git_work_tree"' + echo ' git -c advice.detachedHead=false checkout -f "$hash"' + echo 'fi' + + echo 'git clean -dfx' + + } \ + | + target_shell +)} + +populate_symlink() {( + print_info populate_symlink "$@" + + symlink_name=$1 + symlink_source=$2 + + symlink_target=$target_path/$symlink_name + + { + # TODO rm -fR instead of ln -f? + echo ln -fns $(quote "$symlink_source" "$symlink_target") + } \ + | + target_shell +)} + +print_info() { + printf '\e[1;33m* %s\e[m\n' "$*" >&2 +} + +quote() { + printf %s "$1" | sed 's/./\\&/g' + while test $# -gt 1; do + printf ' ' + shift + printf %s "$1" | sed 's/./\\&/g' + done + echo +} + +target_shell() { + if is_local_target; then + /bin/sh + else + ssh "$target_host" \ + -l "$target_user" \ + -o ControlPersist=no \ + -p "$target_port" \ + -T \ + /bin/sh + fi +} + +main "$@" |