Last active
February 6, 2024 05:08
-
-
Save coughingmouse/8be6321b1b050d1552f75de1edcb717e to your computer and use it in GitHub Desktop.
Cloud-utils cloud-localds for macOS (GPLv3): converts user data such as default password for Ubuntu cloud images
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Copyright (C) Canonical, Iso Lee | |
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License | |
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | |
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied | |
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
# You should have received a copy of the GNU General Public License along with this program. | |
# If not, see http://www.gnu.org/licenses/. | |
VERBOSITY=0 | |
TEMP_D="" | |
DEF_DISK_FORMAT="raw" | |
DEF_FILESYSTEM="iso9660" | |
CR=" | |
" | |
error() { echo "$@" 1>&2; } | |
fail() { [ $# -eq 0 ] || error "$@"; exit 1; } | |
Usage() { | |
cat <<EOF | |
Usage: ${0##*/} [ options ] output user-data [meta-data] | |
Create a disk for cloud-init to utilize nocloud | |
options: | |
-h | --help show usage | |
-d | --disk-format D disk format to output. default: raw | |
can be anything supported by qemu-img or | |
tar, tar-seed-local, tar-seed-net | |
-H | --hostname H set hostname in metadata to H | |
-f | --filesystem F filesystem format (vfat or iso), default: iso9660 | |
-i | --interfaces F write network interfaces file into metadata | |
-N | --network-config F write network config file to local datasource | |
-m | --dsmode M add 'dsmode' ('local' or 'net') to the metadata | |
default in cloud-init is 'net', meaning network is | |
required. | |
-V | --vendor-data F vendor-data file | |
-v | --verbose increase verbosity | |
Note, --dsmode, --hostname, and --interfaces are incompatible | |
with metadata. | |
Example: | |
* cat my-user-data | |
#cloud-config | |
password: passw0rd | |
chpasswd: { expire: False } | |
ssh_pwauth: True | |
* echo "instance-id: \$(uuidgen || echo i-abcdefg)" > 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 | |
## <<insert default variables here>> | |
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? | |
[ $# -ge 2 ] || bad_Usage "must provide output, userdata" | |
[ $# -le 9 ] || bad_Usage "confused by additional args" | |
output=$7 | |
output=$8 | |
output=$9 | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment