#!/bin/bash VERBOSITY=0 TEMP_D="" DEF_DISK_FORMAT="raw" DEF_FILESYSTEM="iso9660" CR=" " error() { echo "$@" 1>&2; } fail() { [ $# -eq 0 ] || error "$@"; exit 1; } Usage() { cat < my-meta-data * ${0##*/} my-seed.img my-user-data my-meta-data * kvm -net nic -net user,hostfwd=tcp::2222-:22 \\ -drive file=disk1.img,if=virtio -drive file=my-seed.img,if=virtio * ssh -p 2222 ubuntu@localhost EOF } bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; } cleanup() { [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" } debug() { local level=${1}; shift; [ "${level}" -gt "${VERBOSITY}" ] && return error "${@}" } has_cmd() { command -v "$1" >/dev/null 2>&1 } short_opts="hH:i:d:f:m:N:o:V:v" long_opts="disk-format:,dsmode:,filesystem:,help,hostname:,interfaces:," long_opts="${long_opts}network-config:,output:,vendor-data:,verbose" getopt_out=$(getopt -n "${0##*/}" \ -o "${short_opts}" -l "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || bad_Usage ## <> output="" userdata="" metadata="" vendordata="" filesystem="" diskformat=$DEF_DISK_FORMAT interfaces=_unset dsmode="" hostname="" ncname="network-config" while [ $# -ne 0 ]; do cur=${1}; next=${2}; case "$cur" in -h|--help) Usage ; exit 0;; -d|--disk-format) diskformat=$next; shift;; -f|--filesystem) filesystem=$next; shift;; -H|--hostname) hostname=$next; shift;; -i|--interfaces) interfaces=$next; shift;; -N|--network-config) netcfg=$next; shift;; -m|--dsmode) dsmode=$next; shift;; -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; -V|--vendor-data) vendordata="$next";; --) shift; break;; esac shift; done ## check arguments here ## how many args do you expect? echo $1 echo $2 echo $3 [ $# -ge 2 ] || bad_Usage "must provide output, userdata" [ $# -le 3 ] || bad_Usage "confused by additional args" output=$1 userdata=$2 metadata=$3 if [ -n "$metadata" ]; then [ "$interfaces" = "_unset" -a -z "$dsmode" -a -z "$hostname" ] || fail "metadata is incompatible with:" \ "--interfaces, --hostname, --dsmode" fi case "$diskformat" in tar|tar-seed-local|tar-seed-net) if [ "${filesystem:-tar}" != "tar" ]; then fail "diskformat=tar is incompatible with filesystem" fi filesystem="$diskformat" ;; tar*) fail "supported 'tar' formats are tar, tar-seed-local, tar-seed-net" esac if [ -z "$filesystem" ]; then filesystem="$DEF_FILESYSTEM" fi if [ "$filesystem" = "iso" ]; then filesystem="iso9660" fi case "$filesystem" in tar*) has_cmd tar || fail "missing 'tar'. Required for --filesystem=$filesystem";; vfat) has_cmd mkfs.vfat || fail "missing 'mkfs.vfat'. Required for --filesystem=vfat." has_cmd mcopy || fail "missing 'mcopy'. Required for --filesystem=vfat." ;; iso9660) has_cmd mkisofs || fail "missing 'mkisofs'. Required for --filesystem=iso9660." ;; *) fail "unknown filesystem $filesystem";; esac case "$diskformat" in tar*|raw) :;; *) has_cmd "qemu-img" || fail "missing 'qemu-img'. Required for --disk-format=$diskformat." esac [ "$interfaces" = "_unset" -o -r "$interfaces" ] || fail "$interfaces: not a readable file" TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || fail "failed to make tempdir" trap cleanup EXIT files=( "${TEMP_D}/user-data" "${TEMP_D}/meta-data" ) if [ -n "$metadata" ]; then cp "$metadata" "$TEMP_D/meta-data" || fail "$metadata: failed to copy" else instance_id="iid-local01" iface_data="" [ "$interfaces" != "_unset" ] && iface_data=$(sed ':a;N;$!ba;s/\n/\\n/g' "$interfaces") # write json formatted user-data (json is a subset of yaml) mdata="" for kv in "instance-id:$instance_id" "local-hostname:$hostname" \ "interfaces:${iface_data}" "dsmode:$dsmode"; do key=${kv%%:*} val=${kv#*:} [ -n "$val" ] || continue mdata="${mdata:+${mdata},${CR}}\"$key\": \"$val\"" done printf "{\n%s\n}\n" "$mdata" > "${TEMP_D}/meta-data" fi if [ -n "$netcfg" ]; then cp "$netcfg" "${TEMP_D}/$ncname" || fail "failed to copy network config" files[${#files[@]}]="$TEMP_D/$ncname" fi if [ -n "$vendordata" ]; then cp "$vendordata" "${TEMP_D}/vendor-data" || fail "failed to copy vendor data" files[${#files[@]}]="$TEMP_D/vendor-data" fi files_rel=( ) for f in "${files[@]}"; do files_rel[${#files_rel[@]}]="${f#${TEMP_D}/}" done if [ "$userdata" = "-" ]; then cat > "$TEMP_D/user-data" || fail "failed to read from stdin" else cp "$userdata" "$TEMP_D/user-data" || fail "$userdata: failed to copy" fi ## alternatively, create a vfat filesystem with same files img="$TEMP_D/seed-data" tar_opts=( --owner=root --group=root ) case "$filesystem" in tar) tar "${tar_opts[@]}" -C "${TEMP_D}" -cf "$img" "${files_rel[@]}" || fail "failed to create tarball of ${files_rel[*]}" ;; tar-seed-local|tar-seed-net) if [ "$filesystem" = "tar-seed-local" ]; then path="var/lib/cloud/seed/nocloud" else path="var/lib/cloud/seed/nocloud-net" fi mkdir -p "${TEMP_D}/${path}" || fail "failed making path for seed files" mv "${files[@]}" "${TEMP_D}/$path" || fail "failed moving files" tar "${tar_opts[@]}" -C "${TEMP_D}" -cf "$img" "${path}" || fail "failed to create tarball with $path" ;; iso9660) mkisofs -output "$img" -volid cidata \ -joliet -rock "${files[@]}" > "$TEMP_D/err" 2>&1 || { cat "$TEMP_D/err" 1>&2; fail "failed to mkisofs"; } ;; vfat) truncate -s 128K "$img" || fail "failed truncate image" out=$(mkfs.vfat -n cidata "$img" 2>&1) || { error "failed: mkfs.vfat -n cidata $img"; error "$out"; } mcopy -oi "$img" "${files[@]}" :: || fail "failed to copy user-data, meta-data to img" ;; esac [ "$output" = "-" ] && output="$TEMP_D/final" if [ "${diskformat#tar}" != "$diskformat" -o "$diskformat" = "raw" ]; then cp "$img" "$output" || fail "failed to copy image to $output" else qemu-img convert -f raw -O "$diskformat" "$img" "$output" || fail "failed to convert to disk format $diskformat" fi [ "$output" != "$TEMP_D/final" ] || { cat "$output" && output="-"; } || fail "failed to write to -" debug 1 "wrote ${output} with filesystem=$filesystem and diskformat=$diskformat" # vi: ts=4 noexpandtab