3
# This script uses bash arrays; do not switch to /bin/sh
5
# cloud-publish-image - wrapper for cloud image publishing
7
# Copyright (C) 2010 Canonical Ltd.
9
# Authors: Scott Moser <smoser@canonical.com>
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the GNU General Public License as published by
13
# the Free Software Foundation, version 3 of the License.
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
# GNU General Public License for more details.
20
# You should have received a copy of the GNU General Public License
21
# along with this program. If not, see <http://www.gnu.org/licenses/>.
24
EC2PRE=${EC2PRE:-euca-}
28
IMAGE_TYPES=( auto image kernel ramdisk vmlinuz initrd )
30
error() { echo "$@" 1>&2; }
31
errorp() { printf "$@" 1>&2; }
32
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
33
failp() { [ $# -eq 0 ] || errorp "$@"; exit 1; }
37
Usage: ${0##*/} [ options ] arch image bucket
39
arch : one of i386 or x86_64
40
image : the image to upload and register
41
bucket : bucket to publish image to
44
-l|--add-launch ID : ID can be "all", or "none", or numerical ID
45
--dry-run : only report what would be done
46
--allow-existing : if a image is already registered
47
simply report as if work was done
48
-o|--output <file> : write registered id and manifest to file
49
|--rename <publish_path> : publish to bucket/<publish_path>
50
default: bucket/<basename(image)>
51
-t|--type <type> : type is one of kernel/ramdisk/image
52
-v|--verbose : increase verbosity
53
--name <name> : register with '--name'.
56
--save-downloaded : if the image is a url, save it to '.'
58
if type is 'image', then:
59
-k | --kernel k : use previously registered kernel with id 'k'
60
specify 'none' for no kernel
61
-K | --kernel-file f : bundle, upload, use file 'f' as kernel
62
-r | --ramdisk r : use previously registered ramdisk with id 'r'
63
specify 'none' for no ramdisk
64
-R | --ramdisk-file f : bundle, upload, use file 'f' as ramdisk
65
-B | --block-device-mapping m : specify block device mapping in bundle
69
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
72
for x in "${RENAME_D}" "${TMPD}"; do
73
[ -z "${x}" -o ! -d "${x}" ] || rm -Rf "${x}"
81
[ "${level}" -ge "${VERBOSITY}" ] && return
82
error "$(date):" "${@}"
85
local dir="${1}" pre=${2} msg=${3};
87
[ -e "${dir}/stamp.${pre}" ] &&
88
{ debug 1 "skipping ${pre}"; return 0; }
90
echo "$@" > "${dir}/${pre}.cmd"
91
"$@" > "${dir}/${pre}.stdout" 2> "${dir}/${pre}.stderr" &&
92
: > "${dir}/stamp.${pre}" && return 0
95
cat "${dir}/${pre}.stdout"
96
cat "${dir}/${pre}.stderr" 1>&2
101
local x="" i=0 needle="$1"
104
[ "${needle}" = "${x}" ] && { _RET=$i; return 0; }
113
[ "$x" = "0" ] || i=$(($i+1))
119
local tmpf="" out="" ret=1 m1="${1}" m2="${2}"
120
out=$(${EC2PRE}describe-images -o self |
121
awk '$3 ~ m1 || $3 ~ m2 { printf("%s\t%s\n",$2,$3); }' \
122
"m1=$m1" "m2=${m2:-^$}"
123
checkstatus ${PIPESTATUS[@]}) || return 1
128
local image=${1} file_out="" img_type=""
129
file_out=$(file --uncompress "${image}") || return 1;
130
case "${file_out}" in
131
*[lL]inux\ kernel*) img_type="kernel";;
132
*LSB\ executable*gzip*) img_type="kernel";;
133
*cpio\ archive*) img_type="ramdisk";;
134
*ext[234]\ file*|*boot\ sector*) img_type="image";;
135
*) error "unable to determine image type. pass --type"; return 1;;
143
out=$(cloud-publish-image "${@}") || return
149
# dl url, target, quiet
150
local url=${1} target=${2} quiet=${3:-1}
151
if [ -f "${url}" ]; then
152
[ "${target}" = "-" ] && { cat "$url"; return; }
157
[ "$quiet" = "0" ] && qflag=""
159
wget $qflag "$url" -O "$target" ||
164
# this downloads an image if necessary and sets _RET to location of image
165
local input="$1" save_dir="${2:-.}" ret="" quiet=1
166
[ $VERBOSITY -ge 2 ] && quiet=0
169
ret="$save_dir/${input##*/}"
170
dl "${input#file://}" "$ret" $quiet || return $?;;
171
http://*|ftp://*|https://*)
172
ret="$save_dir/${input##*/}"
173
dl "$input" "$ret" $quiet || return $?
181
[ "${CLOUD_UTILS_WARN_UEC:-0}" = "0" ] && _n="${0##*/}" &&
182
[ "${_n#uec}" != "${_n}" ] && export CLOUD_UTILS_WARN_UEC=1 &&
183
error "WARNING: '${0##*/}' is now to 'cloud${_n#uec}'. Please update your tools or docs"
185
short_opts="B:h:k:K:l:no:r:R:t:vw:"
186
long_opts="add-launch:,allow-existing,block-device-mapping:,dry-run,help,kernel:,kernel-file:,name:,output:,image-to-raw,ramdisk:,ramdisk-file:,rename:,save-downloaded,type:,verbose,working-dir:"
187
getopt_out=$(getopt --name "${0##*/}" \
188
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
189
eval set -- "${getopt_out}" ||
212
while [ $# -ne 0 ]; do
215
-d|--working-dir) wdir_in=${next}; shift;;
216
-h|--help) Usage; exit 0;;
217
-B|--block-device-mapping) dev_mapping=${next}; shift;;
218
-k|--kernel) kernel=${next}; shift;;
219
-K|--kernel-file) kernel_file=${next}; shift;;
221
if [ "${next}" = "none" ]; then
224
user=${next//-/}; # just be nice and remove '-'
225
add_acl="${add_acl:+${add_acl} }${user}";
228
--name) name=${next}; shift;;
229
-o|--output) output="${next}"; shift;;
230
--image-to-raw) image2raw=1;;
231
-r|--ramdisk) ramdisk=${next}; shift;;
232
-R|--ramdisk-file) ramdisk_file=${next}; shift;;
233
-n|--dry-run) dry_run=1;;
234
--rename) rename=${next}; shift;;
235
--save-downloaded) save_dl=1;;
238
search_args "${img_type}" "${IMAGE_TYPES[@]}" ||
239
bad_Usage "image type (${next}) not in ${IMAGE_TYPES[*]}"
241
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
242
--allow-existing) allow_existing=1;;
244
-*) bad_Usage "confused by ${cur}";;
249
[ $# -lt 3 ] && bad_Usage "must provide arch, image, bucket"
250
[ $# -gt 3 ] && bad_Usage "unexpected arguments: ${4}"
255
# remove any trailing slashes on bucket
256
while [ "${bucket%/}" != "${bucket}" ]; do bucket=${bucket%/}; done
258
[ "${arch}" = "amd64" ] && arch=x86_64
260
[ "${img_type}" = "vmlinuz" ] && img_type="kernel"
261
[ "${img_type}" = "initrd" ] && img_type="ramdisk"
263
[ -n "${kernel_file}" -a -n "${kernel}" ] &&
264
bad_Usage "--kernel-file is incompatible with --kernel"
265
[ -n "${ramdisk_file}" -a -n "${ramdisk}" ] &&
266
bad_Usage "--ramdisk-file is incompatible with --ramdisk"
268
if [ -n "${wdir_in}" ]; then
269
[ -d "${wdir_in}" ] || fail "input working directory not a directory";
270
wdir=$(readlink -f "${wdir_in}") ||
271
fail "failed to realize ${wdir_in}"
273
TMPD=$(mktemp -d ${TMPDIR:-/tmp}/${0##*/}.XXXXXX) ||
274
fail "failed to make tmpdir"
279
if [ -n "$kernel" -a "$kernel" != "none" ]; then
280
aki_arch=""; ari_arch="";
281
# if kernel is given, check that its arch matches the register arch
282
aki_arch=""; ari_arch="";
284
[ "$ramdisk" = "none" ] && _ramdisk="" || _ramdisk="$ramdisk"
286
${EC2PRE}describe-images "$kernel" $_ramdisk > "${TMPD}/kernel.info" ||
287
fail "failed to describe kernel ${kernel}"
288
aki_arch=$(awk '-F\t' '$1 == "IMAGE" && $2 == id { print $8 }' \
289
"id=$kernel" "$TMPD/kernel.info") && [ -n "$aki_arch" ] ||
290
fail "failed to get arch of $kernel"
291
if [ -n "$ramdisk" -a "$ramdisk" != "none" ]; then
292
ari_arch=$(awk '-F\t' '$1 == "IMAGE" && $2 == id { print $8 }' \
293
"id=$ramdisk" "$TMPD/kernel.info") && [ -n "$ari_arch" ] ||
294
fail "failed to get arch of $ramdisk"
297
# if kernel and ramdisk are given, and arch=i386 kernel/ramdisk=x86_64,
298
# then assume loader kernel.
299
case "$arch:$aki_arch:$ari_arch" in
300
$arch:$arch:$arch|$arch:$arch:) : ;;
302
error "WARNING: assuming loader kernel ($kernel/$ramdisk arch=$aki_arch, provided arch=$arch)"
304
*) fail "arch $arch != kernel/ramdisk arch [$aki_arch/$ari_arch]";;
309
[ $save_dl -eq 1 ] && save_dir=.
311
dl_input_image "$image" "$save_dir" && image="$_RET" ||
312
fail "failed to download image $image to $save_dir"
314
[ -z "$kernel_file" ] ||
315
{ dl_input_image "$kernel_file" "$save_dir" && kernel_file="$_RET"; } ||
316
fail "failed to download kernel $kernel_file to $save_dir"
318
[ -z "$ramdisk_file" ] ||
319
{ dl_input_image "$ramdisk_file" "$save_dir" && ramdisk_file="$_RET"; } ||
320
fail "failed to download ramdisk $ramdisk_file to $save_dir"
322
[ -f "${image}" ] || bad_Usage "${image}: image is not a file"
324
[ -z "${kernel_file}" -o -f "${kernel_file}" ] ||
325
fail "${kernel_file} is not a file"
326
[ -z "${ramdisk_file}" -o -f "${ramdisk_file}" ] ||
327
fail "${ramdisk_file} is not a file"
329
if [ "${img_type}" = "auto" ]; then
330
get_image_type "${image}" ||
331
fail "failed to determine file type of ${image}"
335
[ -n "${dev_mapping}" -a "${img_type}" != "image" ] &&
336
fail "-B/--block-device-mapping can only be specified for --type=image"
338
[ -n "${rename}" ] || rename=${image##*/}
340
if [ "${name}" = "__unset__" ]; then
342
# if user did not pass --name, try to figure out if register supports it
343
# we unfortunately can't assume that '--help' exits 0
344
${EC2PRE}register --help > "${TMPD}/register-help.out" 2>&1
345
if grep -q -- "--name" "${TMPD}/register-help.out"; then
346
name="${bucket}/${rename}"
347
debug 1 "using ${name} for --name"
349
debug 1 "${EC2PRE}register seems not to support --name, not passing"
353
elif [ -z "${name}" -o "${name}" == "none" ]; then
354
# if user passed in '--name=""' or '--name=none", do not pass --name
358
image_full=$(readlink -f "${image}") ||
359
fail "failed to get full path to ${image}"
361
if [ -e "${wdir}/${rename}" ]; then
362
[ "${wdir}/${rename}" -ef "${image}" ] ||
363
fail "${wdir} already contains file named ${rename}"
366
# bundle-kernel doesn't like for file to exist in destination-dir
367
# so, create it one dir under there
368
RENAME_D=$(mktemp -d "${wdir}/.rename.XXXXXX") &&
369
ln -s "${image_full}" "${RENAME_D}/${rename}" &&
370
rename_full="${RENAME_D}/${rename}" ||
371
fail "link failed: working-dir/rename/${rename} -> ${image_full}"
375
manifest="${rename}.manifest.xml"
377
# set up "pass through" args to go through to kernel/ramdisk publishing
379
[ $VERBOSITY -eq 0 ] || pthr[${#pthr[@]}]="--verbose"
380
[ ${allow_existing} -eq 0 ] || pthr[${#pthr[@]}]="--allow-existing"
381
[ -z "${add_acl}" ] ||
382
{ pthr[${#pthr[@]}]="--add-launch"; pthr[${#pthr[@]}]="${add_acl}"; }
383
[ ${dry_run} -eq 0 ] || pthr[${#pthr[@]}]="--dry-run"
385
if [ -n "${kernel_file}" ]; then
386
debug 1 "publishing kernel ${kernel_file}"
387
upload_register --type kernel "${pthr[@]}" \
388
"${arch}" "${kernel_file}" "${bucket}" ||
389
fail "failed to register ${kernel_file}"
391
debug 1 "kernel registered as ${kernel}"
394
if [ -n "${ramdisk_file}" ]; then
395
debug 1 "publishing ramdisk ${ramdisk_file}"
396
upload_register --type ramdisk "${pthr[@]}" \
397
"${arch}" "${ramdisk_file}" "${bucket}" ||
398
fail "failed to register ${ramdisk_file}"
400
debug 1 "ramdisk registered as ${ramdisk}"
403
if [ ${VERBOSITY} -ge 1 -o ${dry_run} -ne 0 ]; then
404
[ -n "${kernel}" ] && krd_fmt=" %s/%s" &&
405
krd_args=( "${kernel}" "${ramdisk:-none}" )
406
errorp "[%-6s] %s => %s/%s ${krd_fmt}\n" "${img_type}" \
407
"${image##*/}" "${bucket}" "${rename}" "${krd_args[@]}"
408
if [ ${dry_run} -ne 0 ]; then
409
case "${img_type}" in
414
printf "%s\t%s\n" "${pre}-xxxxxxxx" "${bucket}/${rename##*/}"
420
[ -n "${kernel}" -a "${kernel}" != "none" ] &&
421
krd_args=( "${krd_args[@]}" "--kernel" "${kernel}" )
422
[ -n "${ramdisk}" -a "${ramdisk}" != "none" ] &&
423
krd_args=( "${krd_args[@]}" "--ramdisk" "${ramdisk}" )
425
if [ "${EC2PRE%ec2-}" != "${EC2PRE}" ]; then
426
req="EC2_CERT EC2_PRIVATE_KEY EC2_USER_ID EC2_ACCESS_KEY EC2_SECRET_KEY"
427
for env_name in ${req}; do
428
[ -n "${!env_name}" ] ||
429
fail "when using ec2- tools, you must set env: ${req}"
431
ex_bundle_args=( --cert "${EC2_CERT}"
432
--privatekey "${EC2_PRIVATE_KEY}"
433
--user "${EC2_USER_ID}" )
434
ex_upload_args=( --access-key "${EC2_ACCESS_KEY}"
435
--secret-key "${EC2_SECRET_KEY}" )
439
debug 1 "checking for existing registered image at ${bucket}/${manifest}"
440
get_manifest_id "^${bucket}/${manifest}" "/$name$" ||
441
fail "failed to check for existing manifest"
442
if [ -n "${_RET}" ]; then
444
img_id=${1}; path=${2}
445
[ ${allow_existing} -eq 1 ] ||
446
fail "${path} already registered as ${img_id}"
447
debug 1 "using existing ${img_id} for ${bucket}/${manifest}"
449
if [ $image2raw -eq 1 -a "$img_type" = "image" ]; then
450
# this is really here because of LP: #836759
451
# but could be useful elsewhere
452
qemu-img info "$image" > "${TMPD}/disk-info.out" ||
453
fail "failed to qemu-img info $image"
454
imgfmt=$(awk '-F:' '$1 == "file format" { sub(/ /,"",$2); print $2 }' \
455
"${TMPD}/disk-info.out")
456
if [ "$imgfmt" != "raw" ]; then
457
debug 1 "converting image to raw"
458
raw_image="${TMPD}/image.raw"
459
qemu-img convert -O raw "$image" "$raw_image" ||
460
fail "failed to convert image to raw"
462
ln -sf "$raw_image" "$rename_full" ||
463
fail "symlink to raw image $raw_image failed"
465
debug 1 "disk is already raw format, not converting"
468
bundle_args=( "--image" "${rename_full}" )
469
[ -n "${dev_mapping}" ] &&
470
bundle_args[${#bundle_args[@]}]="--block-device-mapping=${dev_mapping}"
472
case "${img_type}" in
474
bundle_args[${#bundle_args[@]}]="--${img_type}"
475
bundle_args[${#bundle_args[@]}]="true"
477
run "${wdir}" "bundle" "bundling ${img_type} ${image}" \
478
${EC2PRE}bundle-image --destination "${wdir}" --arch "${arch}" \
479
"${ex_bundle_args[@]}" \
480
"${bundle_args[@]}" "${krd_args[@]}" ||
481
fail "failed to bundle ${img_type} ${image}"
483
run "${wdir}" "upload" "upload ${bucket}/${manifest}" \
484
${EC2PRE}upload-bundle --bucket "${bucket}" \
485
"${ex_upload_args[@]}" \
486
--manifest "${wdir}/${manifest}" ||
487
fail "failed to upload bundle to ${bucket}/${manifest}"
490
run "${wdir}" "register" "register ${bucket}/${manifest}" \
491
${EC2PRE}register ${name:+--name "${name}"} \
492
"${ex_register_args[@]}" "${bucket}/${manifest}" &&
493
read junk img_id < "${wdir}/register.stdout" &&
494
[ "${img_id#???-}" != "${img_id}" ] || {
495
if bad=$(get_manifest_id "${bucket}/${manifest}" "/${name}") &&
496
[ -n "${bad}" ]; then
499
error "un-registering invalid $bad" >/dev/null
500
${EC2PRE}deregister "${bad_id}"
502
fail "failed to register ${manifest}"
505
debug 1 "registered at ${bucket}/${manifest} as ${img_id}"
508
debug 1 "${img_id} ${bucket}/${manifest}"
510
if [ -z "${output}" -o "${output}" = "-" ]; then
511
printf "%s\t%s\n" "${img_id}" "${bucket}/${manifest}"
513
printf "%s\t%s\n" "${img_id}" "${bucket}/${manifest}" >> "${output}"
516
for user in ${add_acl}; do
517
run "${wdir}" "add_user.${user}" \
518
"add ${user} to ${manifest}" \
519
${EC2PRE}modify-image-attribute \
520
--launch-permission --add "${user}" "${img_id}" ||
521
fail "failed to add launch permission for ${user} to ${img_id}"