cloud-localds 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. #!/bin/bash
  2. VERBOSITY=0
  3. TEMP_D=""
  4. DEF_DISK_FORMAT="raw"
  5. DEF_FILESYSTEM="iso9660"
  6. CR="
  7. "
  8. error() { echo "$@" 1>&2; }
  9. fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
  10. Usage() {
  11. cat <<EOF
  12. Usage: ${0##*/} [ options ] output user-data [meta-data]
  13. Create a disk for cloud-init to utilize nocloud
  14. options:
  15. -h | --help show usage
  16. -d | --disk-format D disk format to output. default: raw
  17. can be anything supported by qemu-img or
  18. tar, tar-seed-local, tar-seed-net
  19. -H | --hostname H set hostname in metadata to H
  20. -f | --filesystem F filesystem format (vfat or iso), default: iso9660
  21. -i | --interfaces F write network interfaces file into metadata
  22. -N | --network-config F write network config file to local datasource
  23. -m | --dsmode M add 'dsmode' ('local' or 'net') to the metadata
  24. default in cloud-init is 'net', meaning network is
  25. required.
  26. -V | --vendor-data F vendor-data file
  27. -v | --verbose increase verbosity
  28. Note, --dsmode, --hostname, and --interfaces are incompatible
  29. with metadata.
  30. Example:
  31. * cat my-user-data
  32. #cloud-config
  33. password: passw0rd
  34. chpasswd: { expire: False }
  35. ssh_pwauth: True
  36. * echo "instance-id: \$(uuidgen || echo i-abcdefg)" > my-meta-data
  37. * ${0##*/} my-seed.img my-user-data my-meta-data
  38. * kvm -net nic -net user,hostfwd=tcp::2222-:22 \\
  39. -drive file=disk1.img,if=virtio -drive file=my-seed.img,if=virtio
  40. * ssh -p 2222 ubuntu@localhost
  41. EOF
  42. }
  43. bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
  44. cleanup() {
  45. [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
  46. }
  47. debug() {
  48. local level=${1}; shift;
  49. [ "${level}" -gt "${VERBOSITY}" ] && return
  50. error "${@}"
  51. }
  52. has_cmd() {
  53. command -v "$1" >/dev/null 2>&1
  54. }
  55. short_opts="hH:i:d:f:m:N:o:V:v"
  56. long_opts="disk-format:,dsmode:,filesystem:,help,hostname:,interfaces:,"
  57. long_opts="${long_opts}network-config:,output:,vendor-data:,verbose"
  58. getopt_out=$(getopt -n "${0##*/}" \
  59. -o "${short_opts}" -l "${long_opts}" -- "$@") &&
  60. eval set -- "${getopt_out}" ||
  61. bad_Usage
  62. ## <<insert default variables here>>
  63. output=""
  64. userdata=""
  65. metadata=""
  66. vendordata=""
  67. filesystem=""
  68. diskformat=$DEF_DISK_FORMAT
  69. interfaces=_unset
  70. dsmode=""
  71. hostname=""
  72. ncname="network-config"
  73. while [ $# -ne 0 ]; do
  74. cur=${1}; next=${2};
  75. case "$cur" in
  76. -h|--help) Usage ; exit 0;;
  77. -d|--disk-format) diskformat=$next; shift;;
  78. -f|--filesystem) filesystem=$next; shift;;
  79. -H|--hostname) hostname=$next; shift;;
  80. -i|--interfaces) interfaces=$next; shift;;
  81. -N|--network-config) netcfg=$next; shift;;
  82. -m|--dsmode) dsmode=$next; shift;;
  83. -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
  84. -V|--vendor-data) vendordata="$next";;
  85. --) shift; break;;
  86. esac
  87. shift;
  88. done
  89. ## check arguments here
  90. ## how many args do you expect?
  91. echo $1
  92. echo $2
  93. echo $3
  94. [ $# -ge 2 ] || bad_Usage "must provide output, userdata"
  95. [ $# -le 3 ] || bad_Usage "confused by additional args"
  96. output=$1
  97. userdata=$2
  98. metadata=$3
  99. if [ -n "$metadata" ]; then
  100. [ "$interfaces" = "_unset" -a -z "$dsmode" -a -z "$hostname" ] ||
  101. fail "metadata is incompatible with:" \
  102. "--interfaces, --hostname, --dsmode"
  103. fi
  104. case "$diskformat" in
  105. tar|tar-seed-local|tar-seed-net)
  106. if [ "${filesystem:-tar}" != "tar" ]; then
  107. fail "diskformat=tar is incompatible with filesystem"
  108. fi
  109. filesystem="$diskformat"
  110. ;;
  111. tar*)
  112. fail "supported 'tar' formats are tar, tar-seed-local, tar-seed-net"
  113. esac
  114. if [ -z "$filesystem" ]; then
  115. filesystem="$DEF_FILESYSTEM"
  116. fi
  117. if [ "$filesystem" = "iso" ]; then
  118. filesystem="iso9660"
  119. fi
  120. case "$filesystem" in
  121. tar*)
  122. has_cmd tar ||
  123. fail "missing 'tar'. Required for --filesystem=$filesystem";;
  124. vfat)
  125. has_cmd mkfs.vfat ||
  126. fail "missing 'mkfs.vfat'. Required for --filesystem=vfat."
  127. has_cmd mcopy ||
  128. fail "missing 'mcopy'. Required for --filesystem=vfat."
  129. ;;
  130. iso9660)
  131. has_cmd mkisofs ||
  132. fail "missing 'mkisofs'. Required for --filesystem=iso9660."
  133. ;;
  134. *) fail "unknown filesystem $filesystem";;
  135. esac
  136. case "$diskformat" in
  137. tar*|raw) :;;
  138. *) has_cmd "qemu-img" ||
  139. fail "missing 'qemu-img'. Required for --disk-format=$diskformat."
  140. esac
  141. [ "$interfaces" = "_unset" -o -r "$interfaces" ] ||
  142. fail "$interfaces: not a readable file"
  143. TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
  144. fail "failed to make tempdir"
  145. trap cleanup EXIT
  146. files=( "${TEMP_D}/user-data" "${TEMP_D}/meta-data" )
  147. if [ -n "$metadata" ]; then
  148. cp "$metadata" "$TEMP_D/meta-data" || fail "$metadata: failed to copy"
  149. else
  150. instance_id="iid-local01"
  151. iface_data=""
  152. [ "$interfaces" != "_unset" ] &&
  153. iface_data=$(sed ':a;N;$!ba;s/\n/\\n/g' "$interfaces")
  154. # write json formatted user-data (json is a subset of yaml)
  155. mdata=""
  156. for kv in "instance-id:$instance_id" "local-hostname:$hostname" \
  157. "interfaces:${iface_data}" "dsmode:$dsmode"; do
  158. key=${kv%%:*}
  159. val=${kv#*:}
  160. [ -n "$val" ] || continue
  161. mdata="${mdata:+${mdata},${CR}}\"$key\": \"$val\""
  162. done
  163. printf "{\n%s\n}\n" "$mdata" > "${TEMP_D}/meta-data"
  164. fi
  165. if [ -n "$netcfg" ]; then
  166. cp "$netcfg" "${TEMP_D}/$ncname" ||
  167. fail "failed to copy network config"
  168. files[${#files[@]}]="$TEMP_D/$ncname"
  169. fi
  170. if [ -n "$vendordata" ]; then
  171. cp "$vendordata" "${TEMP_D}/vendor-data" ||
  172. fail "failed to copy vendor data"
  173. files[${#files[@]}]="$TEMP_D/vendor-data"
  174. fi
  175. files_rel=( )
  176. for f in "${files[@]}"; do
  177. files_rel[${#files_rel[@]}]="${f#${TEMP_D}/}"
  178. done
  179. if [ "$userdata" = "-" ]; then
  180. cat > "$TEMP_D/user-data" || fail "failed to read from stdin"
  181. else
  182. cp "$userdata" "$TEMP_D/user-data" || fail "$userdata: failed to copy"
  183. fi
  184. ## alternatively, create a vfat filesystem with same files
  185. img="$TEMP_D/seed-data"
  186. tar_opts=( --owner=root --group=root )
  187. case "$filesystem" in
  188. tar)
  189. tar "${tar_opts[@]}" -C "${TEMP_D}" -cf "$img" "${files_rel[@]}" ||
  190. fail "failed to create tarball of ${files_rel[*]}"
  191. ;;
  192. tar-seed-local|tar-seed-net)
  193. if [ "$filesystem" = "tar-seed-local" ]; then
  194. path="var/lib/cloud/seed/nocloud"
  195. else
  196. path="var/lib/cloud/seed/nocloud-net"
  197. fi
  198. mkdir -p "${TEMP_D}/${path}" ||
  199. fail "failed making path for seed files"
  200. mv "${files[@]}" "${TEMP_D}/$path" ||
  201. fail "failed moving files"
  202. tar "${tar_opts[@]}" -C "${TEMP_D}" -cf "$img" "${path}" ||
  203. fail "failed to create tarball with $path"
  204. ;;
  205. iso9660)
  206. mkisofs -output "$img" -volid cidata \
  207. -joliet -rock "${files[@]}" > "$TEMP_D/err" 2>&1 ||
  208. { cat "$TEMP_D/err" 1>&2; fail "failed to mkisofs"; }
  209. ;;
  210. vfat)
  211. truncate -s 128K "$img" || fail "failed truncate image"
  212. out=$(mkfs.vfat -n cidata "$img" 2>&1) ||
  213. { error "failed: mkfs.vfat -n cidata $img"; error "$out"; }
  214. mcopy -oi "$img" "${files[@]}" :: ||
  215. fail "failed to copy user-data, meta-data to img"
  216. ;;
  217. esac
  218. [ "$output" = "-" ] && output="$TEMP_D/final"
  219. if [ "${diskformat#tar}" != "$diskformat" -o "$diskformat" = "raw" ]; then
  220. cp "$img" "$output" ||
  221. fail "failed to copy image to $output"
  222. else
  223. qemu-img convert -f raw -O "$diskformat" "$img" "$output" ||
  224. fail "failed to convert to disk format $diskformat"
  225. fi
  226. [ "$output" != "$TEMP_D/final" ] || { cat "$output" && output="-"; } ||
  227. fail "failed to write to -"
  228. debug 1 "wrote ${output} with filesystem=$filesystem and diskformat=$diskformat"
  229. # vi: ts=4 noexpandtab