30. Remote exec and copy commands

Cdist interacts with the target host in two ways:

  • it executes code (__remote_exec)

  • and it copies files (__remote_copy)

By default this is accomplished with ssh and scp respectively. The default implementations used by cdist are:

__remote_exec: ssh -o User=root
__remote_copy: scp -o User=root -q

The user can override these defaults by providing custom implementations and passing them to cdist with the --remote-exec and/or --remote-copy arguments.

For __remote_exec, the custom implementation must behave as if it where ssh. For __remote_copy, it must behave like scp. Please notice, custom implementations should work like ssh/scp so __remote_copy must support IPv6 addresses enclosed in square brackets. For __remote_exec you must take into account that for some options (like -L) IPv6 addresses can be specified by enclosed in square brackets (see ssh(1) and scp(1)).

With this simple interface the user can take total control of how cdist interacts with the target when required, while the default implementation remains as simple as possible.

30.1. Examples

Here are examples of using alternative __remote_copy and __remote_exec scripts.

All scripts from below are present in cdist sources in other/examples/remote directory.

30.1.1. ssh

Same as cdist default.

copy

Usage: cdist config --remote-copy "/path/to/this/script" target_host

#echo "$@" | logger -t "cdist-ssh-copy"
scp -o User=root -q $@

exec

Usage: cdist config --remote-exec "/path/to/this/script" target_host

#echo "$@" | logger -t "cdist-ssh-exec"
ssh -o User=root $@

30.1.2. local

This effectively turns remote calling into local calling. Probably most useful for the unit testing.

copy

code="$(echo "$@" | sed "s|\([[:space:]]\)$__target_host:|\1|g")"
cp -L $code

exec

target_host=$1; shift
echo "$@" | /bin/sh

30.1.3. chroot

copy

Usage: cdist config --remote-copy "/path/to/this/script /path/to/your/chroot" target-id

log() {
   #echo "$@" | logger -t "cdist-chroot-copy"
   :
}

chroot="$1"; shift
target_host="$__target_host"

# replace target_host with chroot location
code="$(echo "$@" | sed "s|$target_host:|$chroot|g")"

log "target_host: $target_host"
log "chroot: $chroot"
log "$@"
log "$code"

# copy files into chroot
cp $code

log "-----"

exec

Usage: cdist config --remote-exec "/path/to/this/script /path/to/your/chroot" target-id

log() {
   #echo "$@" | logger -t "cdist-chroot-exec"
   :
}

chroot="$1"; shift
target_host="$1"; shift

script=$(mktemp "${chroot}/tmp/chroot-${0##*/}.XXXXXXXXXX")
trap cleanup INT TERM EXIT
cleanup() {
   [ $__cdist_debug ] || rm "$script"
}

log "target_host: $target_host"
log "script: $script"
log "@: $@"
echo "#!/bin/sh -l" > "$script"
echo "$@" >> "$script"
chmod +x "$script"

relative_script="${script#$chroot}"
log "relative_script: $relative_script"

# run in chroot
chroot "$chroot" "$relative_script"

log "-----"

30.1.4. rsync

copy

Usage: cdist config --remote-copy /path/to/this/script target_host

# For rsync to do the right thing, the source has to end with "/" if it is
# a directory. The below preprocessor loop takes care of that.

# second last argument is the source
source_index=$(($#-1))
index=0
for arg in $@; do
   if [ $index -eq 0 ]; then
      # reset $@
      set --
   fi
   index=$((index+=1))
   if [ $index -eq $source_index -a -d "$arg" ]; then
      arg="${arg%/}/"
   fi
   set -- "$@" "$arg"
done

rsync --backup --suffix=~cdist -e 'ssh -o User=root' $@

30.1.5. schroot

__remote_copy and __remote_exec scripts to run cdist against a chroot on the target host over ssh.

copy

Usage: cdist config --remote-copy "/path/to/this/script schroot-chroot-name" target_host

log() {
   #echo "$@" | logger -t "cdist-schroot-copy"
   :
}

chroot_name="$1"; shift
target_host="$__target_host"

# get directory for given chroot_name
chroot="$(ssh -o User=root -q $target_host schroot -c $chroot_name --config | awk -F = '/directory=/ {print $2}')"

# prefix destination with chroot
code="$(echo "$@" | sed "s|$target_host:|$target_host:$chroot|g")"

log "target_host: $target_host"
log "chroot_name: $chroot_name"
log "chroot: $chroot"
log "@: $@"
log "code: $code"

# copy files into remote chroot
scp -o User=root -q $code

log "-----"

exec

Usage: cdist config --remote-exec "/path/to/this/script schroot-chroot-name" target_host

log() {
   #echo "$@" | logger -t "cdist-schroot-exec"
   :
}

chroot_name="$1"; shift
target_host="$1"; shift

code="ssh -o User=root -q $target_host schroot -c $chroot_name -- $@"

log "target_host: $target_host"
log "chroot_name: $chroot_name"
log "@: $@"
log "code: $code"

# run in remote chroot
$code

log "-----"

30.1.6. schroot-uri

__remote_exec/__remote_copy script to run cdist against a schroot target URI.

Usage:

cdist config \
    --remote-exec "/path/to/this/script exec" \
    --remote-copy "/path/to/this/script copy" \
    target_uri

# target_uri examples:
schroot:///chroot-name
schroot://foo.ethz.ch/chroot-name
schroot://user-name@foo.ethz.ch/chroot-name

# and how to match them in .../manifest/init
case "$target_host" in
schroot://*)
    # any schroot
;;
schroot://foo.ethz.ch/*)
    # any schroot on specific host
;;
schroot://foo.ethz.ch/chroot-name)
    # specific schroot on specific host
;;
schroot:///chroot-name)
    # specific schroot on localhost
;;
esac

copy/exec

my_name="${0##*/}"
mode="$1"; shift

log() {
   # uncomment me for debugging
   #echo "$@" | logger -t "cdist-$my_name-$mode"
   :
}

die() {
   echo "$@" >&2
   exit 1
}


uri="$__target_host"

scheme="${uri%%:*}"; rest="${uri#$scheme:}"; rest="${rest#//}"
authority="${rest%%/*}"; rest="${rest#$authority}"
path="${rest%\?*}"; rest="${rest#$path}"
schroot_name="${path#/}"

[ "$scheme" = "schroot" ] || die "Failed to parse scheme from __target_host ($__target_host). Expected 'schroot', got '$scheme'"
[ -n "$schroot_name" ] || die "Failed to parse schroot name from __target_host: $__target_host"

case "$authority" in
   '')
      # authority is empty, neither user nor host given
      user=""
      host=""
   ;;
   *@*)
      # authority contains @, take user from authority
      user="${authority%@*}"
      host="${authority#*@}"
   ;;
   *)
      # no user in authority, default to root
      user="root"
      host="$authority"
   ;;
esac

log "mode: $mode"
log "@: $@"
log "uri: $uri"
log "scheme: $scheme"
log "authority: $authority"
log "user: $user"
log "host: $host"
log "path: $path"
log "schroot_name: $schroot_name"

exec_prefix=""
copy_prefix=""
if [ -n "$host" ]; then
   # we are working on a remote host
   exec_prefix="ssh -o User=$user -q $host"
   copy_prefix="scp -o User=$user -q"
   copy_destination_prefix="$host:"
else
   # working on local machine
   copy_prefix="cp"
   copy_destination_prefix=""
fi
log "exec_prefix: $exec_prefix"
log "copy_prefix: $copy_prefix"
log "copy_destination_prefix: $copy_destination_prefix"

case "$mode" in
   exec)
      # In exec mode the first argument is the __target_host which we already got from env. Get rid of it.
      shift
      code="$exec_prefix schroot -c $schroot_name -- sh -c '$@'"
   ;;
   copy)
      # get directory for given chroot_name
      schroot_directory="$($exec_prefix schroot -c $schroot_name --config | awk -F = '/directory=/ {print $2}')"
      [ -n "$schroot_directory" ] || die "Failed to retreive schroot directory for schroot: $schroot_name"
      log "schroot_directory: $schroot_directory"
      # prefix destination with chroot
      code="$copy_prefix $(echo "$@" | sed "s|$uri:|${copy_destination_prefix}${schroot_directory}|g")"
   ;;
   *) die "Unknown mode: $mode";;
esac

log "code: $code"

# Run the code
$code

log "-----"

30.1.7. sudo

copy

Use rsync over ssh to copy files. Uses the "--rsync-path" option to run the remote rsync instance with sudo.

This command assumes your ssh configuration is already set up in ~/.ssh/config.

Usage: cdist config --remote-copy /path/to/this/script target_host

# For rsync to do the right thing, the source has to end with "/" if it is
# a directory. The below preprocessor loop takes care of that.

# second last argument is the source
source_index=$(($#-1))
index=0
for arg in $@; do
   if [ $index -eq 0 ]; then
      # reset $@
      set --
   fi
   index=$((index+=1))
   if [ $index -eq $source_index -a -d "$arg" ]; then
      arg="${arg%/}/"
   fi
   set -- "$@" "$arg"
done

rsync --copy-links --rsync-path="sudo rsync" -e 'ssh' "$@"

exec

Prefixes all remote commands with sudo.

This command assumes your ssh configuration is already set up in ~/.ssh/config.

Usage: cdist config --remote-exec "/path/to/this/script" target_host

host="$1"; shift
ssh -q "$host" sudo sh -c \""$@"\"