2
# This script uses bash arrays; do not switch to /bin/sh
4
# cloud-publish-image - wrapper for cloud image publishing
6
# Copyright (C) 2010 Canonical Ltd.
8
# Authors: Scott Moser <smoser@canonical.com>
10
# This program is free software: you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License as published by
12
# the Free Software Foundation, version 3 of the License.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
23
EC2PRE=${EC2PRE:-euca-}
27
IMAGE_TYPES=( auto image kernel ramdisk vmlinuz initrd )
29
error() { echo "$@" 1>&2; }
30
errorp() { printf "$@" 1>&2; }
31
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
32
failp() { [ $# -eq 0 ] || errorp "$@"; exit 1; }
36
Usage: ${0##*/} [ options ] arch image bucket
38
arch : one of i386 or x86_64
39
image : the image to upload and register
40
bucket : bucket to publish image to
43
-l|--add-launch ID : ID can be "all", or "none", or numerical ID
44
--dry-run : only report what would be done
45
--allow-existing : if a image is already registered
46
simply report as if work was done
47
-o|--output <file> : write registered id and manifest to file
48
|--rename <publish_path> : publish to bucket/<publish_path>
49
default: bucket/<basename(image)>
50
-t|--type <type> : type is one of kernel/ramdisk/image
51
-v|--verbose : increase verbosity
52
--name <name> : register with '--name'.
55
--save-downloaded : if the image is a url, save it to '.'
57
if type is 'image', then:
58
-k | --kernel k : use previously registered kernel with id 'k'
59
specify 'none' for no kernel
60
-K | --kernel-file f : bundle, upload, use file 'f' as kernel
61
-r | --ramdisk r : use previously registered ramdisk with id 'r'
62
specify 'none' for no ramdisk
63
-R | --ramdisk-file f : bundle, upload, use file 'f' as ramdisk
64
-B | --block-device-mapping m : specify block device mapping in bundle
68
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
71
for x in "${RENAME_D}" "${TMPD}"; do
72
[ -z "${x}" -o ! -d "${x}" ] || rm -Rf "${x}"
80
[ "${level}" -ge "${VERBOSITY}" ] && return
81
error "$(date):" "${@}"
84
local dir="${1}" pre=${2} msg=${3};
86
[ -e "${dir}/stamp.${pre}" ] &&
87
{ debug 1 "skipping ${pre}"; return 0; }
89
echo "$@" > "${dir}/${pre}.cmd"
90
"$@" > "${dir}/${pre}.stdout" 2> "${dir}/${pre}.stderr" &&
91
: > "${dir}/stamp.${pre}" && return 0
94
cat "${dir}/${pre}.stdout"
95
cat "${dir}/${pre}.stderr" 1>&2
100
local x="" i=0 needle="$1"
103
[ "${needle}" = "${x}" ] && { _RET=$i; return 0; }
112
[ "$x" = "0" ] || i=$(($i+1))
118
local tmpf="" out="" ret=1 m1="${1}" m2="${2}"
119
out=$(${EC2PRE}describe-images -o self |
120
awk '$3 ~ m1 || $3 ~ m2 { printf("%s\t%s\n",$2,$3); }' \
121
"m1=$m1" "m2=${m2:-^$}"
122
checkstatus ${PIPESTATUS[@]}) || return 1
127
local image=${1} file_out="" img_type=""
128
file_out=$(file --uncompress "${image}") || return 1;
129
case "${file_out}" in
130
*[lL]inux\ kernel*) img_type="kernel";;
131
*LSB\ executable*gzip*) img_type="kernel";;
132
*cpio\ archive*) img_type="ramdisk";;
133
*ext[234]\ file*|*boot\ sector*) img_type="image";;
134
*) error "unable to determine image type. pass --type"; return 1;;
142
out=$(cloud-publish-image "${@}") || return
148
# dl url, target, quiet
149
local url=${1} target=${2} quiet=${3:-1}
150
if [ -f "${url}" ]; then
151
[ "${target}" = "-" ] && { cat "$url"; return; }
156
[ "$quiet" = "0" ] && qflag=""
158
wget $qflag --progress=dot:mega "$url" -O "$target" ||
163
# this downloads an image if necessary and sets _RET to location of image
164
local input="$1" save_dir="${2:-.}" ret="" quiet=1
165
[ $VERBOSITY -ge 2 ] && quiet=0
168
ret="$save_dir/${input##*/}"
169
dl "${input#file://}" "$ret" $quiet || return $?;;
170
http://*|ftp://*|https://*)
171
ret="$save_dir/${input##*/}"
172
dl "$input" "$ret" $quiet || return $?
180
[ "${CLOUD_UTILS_WARN_UEC:-0}" = "0" ] && _n="${0##*/}" &&
181
[ "${_n#uec}" != "${_n}" ] && export CLOUD_UTILS_WARN_UEC=1 &&
182
error "WARNING: '${0##*/}' is now to 'cloud${_n#uec}'. Please update your tools or docs"
184
short_opts="B:h:k:K:l:no:r:R:t:vw:"
185
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:"
186
getopt_out=$(getopt --name "${0##*/}" \
187
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
188
eval set -- "${getopt_out}" ||
211
while [ $# -ne 0 ]; do
214
-d|--working-dir) wdir_in=${next}; shift;;
215
-h|--help) Usage; exit 0;;
216
-B|--block-device-mapping) dev_mapping=${next}; shift;;
217
-k|--kernel) kernel=${next}; shift;;
218
-K|--kernel-file) kernel_file=${next}; shift;;
220
if [ "${next}" = "none" ]; then
223
user=${next//-/}; # just be nice and remove '-'
224
add_acl="${add_acl:+${add_acl} }${user}";
227
--name) name=${next}; shift;;
228
-o|--output) output="${next}"; shift;;
229
--image-to-raw) image2raw=1;;
230
-r|--ramdisk) ramdisk=${next}; shift;;
231
-R|--ramdisk-file) ramdisk_file=${next}; shift;;
232
-n|--dry-run) dry_run=1;;
233
--rename) rename=${next}; shift;;
234
--save-downloaded) save_dl=1;;
237
search_args "${img_type}" "${IMAGE_TYPES[@]}" ||
238
bad_Usage "image type (${next}) not in ${IMAGE_TYPES[*]}"
240
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
241
--allow-existing) allow_existing=1;;
243
-*) bad_Usage "confused by ${cur}";;
248
[ $# -lt 3 ] && bad_Usage "must provide arch, image, bucket"
249
[ $# -gt 3 ] && bad_Usage "unexpected arguments: ${4}"
254
# remove any trailing slashes on bucket
255
while [ "${bucket%/}" != "${bucket}" ]; do bucket=${bucket%/}; done
257
[ "${arch}" = "amd64" ] && arch=x86_64
259
[ "${img_type}" = "vmlinuz" ] && img_type="kernel"
260
[ "${img_type}" = "initrd" ] && img_type="ramdisk"
262
[ -n "${kernel_file}" -a -n "${kernel}" ] &&
263
bad_Usage "--kernel-file is incompatible with --kernel"
264
[ -n "${ramdisk_file}" -a -n "${ramdisk}" ] &&
265
bad_Usage "--ramdisk-file is incompatible with --ramdisk"
267
if [ -n "${wdir_in}" ]; then
268
[ -d "${wdir_in}" ] || fail "input working directory not a directory";
269
wdir=$(readlink -f "${wdir_in}") ||
270
fail "failed to realize ${wdir_in}"
272
TMPD=$(mktemp -d ${TMPDIR:-/tmp}/${0##*/}.XXXXXX) ||
273
fail "failed to make tmpdir"
278
if [ -n "$kernel" -a "$kernel" != "none" ]; then
279
aki_arch=""; ari_arch="";
280
# if kernel is given, check that its arch matches the register arch
281
aki_arch=""; ari_arch="";
283
[ "$ramdisk" = "none" ] && _ramdisk="" || _ramdisk="$ramdisk"
285
${EC2PRE}describe-images "$kernel" $_ramdisk > "${TMPD}/kernel.info" ||
286
fail "failed to describe kernel ${kernel}"
287
aki_arch=$(awk '-F\t' '$1 == "IMAGE" && $2 == id { print $8 }' \
288
"id=$kernel" "$TMPD/kernel.info") && [ -n "$aki_arch" ] ||
289
fail "failed to get arch of $kernel"
290
if [ -n "$ramdisk" -a "$ramdisk" != "none" ]; then
291
ari_arch=$(awk '-F\t' '$1 == "IMAGE" && $2 == id { print $8 }' \
292
"id=$ramdisk" "$TMPD/kernel.info") && [ -n "$ari_arch" ] ||
293
fail "failed to get arch of $ramdisk"
296
# if kernel and ramdisk are given, and arch=i386 kernel/ramdisk=x86_64,
297
# then assume loader kernel.
298
case "$arch:$aki_arch:$ari_arch" in
299
$arch:$arch:$arch|$arch:$arch:) : ;;
301
error "WARNING: assuming loader kernel ($kernel/$ramdisk arch=$aki_arch, provided arch=$arch)"
303
*) fail "arch $arch != kernel/ramdisk arch [$aki_arch/$ari_arch]";;
308
[ $save_dl -eq 1 ] && save_dir=.
310
dl_input_image "$image" "$save_dir" && image="$_RET" ||
311
fail "failed to download image $image to $save_dir"
313
[ -z "$kernel_file" ] ||
314
{ dl_input_image "$kernel_file" "$save_dir" && kernel_file="$_RET"; } ||
315
fail "failed to download kernel $kernel_file to $save_dir"
317
[ -z "$ramdisk_file" ] ||
318
{ dl_input_image "$ramdisk_file" "$save_dir" && ramdisk_file="$_RET"; } ||
319
fail "failed to download ramdisk $ramdisk_file to $save_dir"
321
[ -f "${image}" ] || bad_Usage "${image}: image is not a file"
323
[ -z "${kernel_file}" -o -f "${kernel_file}" ] ||
324
fail "${kernel_file} is not a file"
325
[ -z "${ramdisk_file}" -o -f "${ramdisk_file}" ] ||
326
fail "${ramdisk_file} is not a file"
328
if [ "${img_type}" = "auto" ]; then
329
get_image_type "${image}" ||
330
fail "failed to determine file type of ${image}"
334
[ -n "${dev_mapping}" -a "${img_type}" != "image" ] &&
335
fail "-B/--block-device-mapping can only be specified for --type=image"
337
[ -n "${rename}" ] || rename=${image##*/}
339
if [ "${name}" = "__unset__" ]; then
341
# if user did not pass --name, try to figure out if register supports it
342
# we unfortunately can't assume that '--help' exits 0
343
${EC2PRE}register --help > "${TMPD}/register-help.out" 2>&1
344
if grep -q -- "--name" "${TMPD}/register-help.out"; then
345
name="${bucket}/${rename}"
346
debug 1 "using ${name} for --name"
348
debug 1 "${EC2PRE}register seems not to support --name, not passing"
352
elif [ -z "${name}" -o "${name}" == "none" ]; then
353
# if user passed in '--name=""' or '--name=none", do not pass --name
357
image_full=$(readlink -f "${image}") ||
358
fail "failed to get full path to ${image}"
360
if [ -e "${wdir}/${rename}" ]; then
361
[ "${wdir}/${rename}" -ef "${image}" ] ||
362
fail "${wdir} already contains file named ${rename}"
365
# bundle-kernel doesn't like for file to exist in destination-dir
366
# so, create it one dir under there
367
RENAME_D=$(mktemp -d "${wdir}/.rename.XXXXXX") &&
368
ln -s "${image_full}" "${RENAME_D}/${rename}" &&
369
rename_full="${RENAME_D}/${rename}" ||
370
fail "link failed: working-dir/rename/${rename} -> ${image_full}"
374
manifest="${rename}.manifest.xml"
376
# set up "pass through" args to go through to kernel/ramdisk publishing
378
[ $VERBOSITY -eq 0 ] || pthr[${#pthr[@]}]="--verbose"
379
[ ${allow_existing} -eq 0 ] || pthr[${#pthr[@]}]="--allow-existing"
380
[ -z "${add_acl}" ] ||
381
{ pthr[${#pthr[@]}]="--add-launch"; pthr[${#pthr[@]}]="${add_acl}"; }
382
[ ${dry_run} -eq 0 ] || pthr[${#pthr[@]}]="--dry-run"
384
if [ -n "${kernel_file}" ]; then
385
debug 1 "publishing kernel ${kernel_file}"
386
upload_register --type kernel "${pthr[@]}" \
387
"${arch}" "${kernel_file}" "${bucket}" ||
388
fail "failed to register ${kernel_file}"
390
debug 1 "kernel registered as ${kernel}"
393
if [ -n "${ramdisk_file}" ]; then
394
debug 1 "publishing ramdisk ${ramdisk_file}"
395
upload_register --type ramdisk "${pthr[@]}" \
396
"${arch}" "${ramdisk_file}" "${bucket}" ||
397
fail "failed to register ${ramdisk_file}"
399
debug 1 "ramdisk registered as ${ramdisk}"
402
if [ ${VERBOSITY} -ge 1 -o ${dry_run} -ne 0 ]; then
403
[ -n "${kernel}" ] && krd_fmt=" %s/%s" &&
404
krd_args=( "${kernel}" "${ramdisk:-none}" )
405
errorp "[%-6s] %s => %s/%s ${krd_fmt}\n" "${img_type}" \
406
"${image##*/}" "${bucket}" "${rename}" "${krd_args[@]}"
407
if [ ${dry_run} -ne 0 ]; then
408
case "${img_type}" in
413
printf "%s\t%s\n" "${pre}-xxxxxxxx" "${bucket}/${rename##*/}"
419
[ -n "${kernel}" -a "${kernel}" != "none" ] &&
420
krd_args=( "${krd_args[@]}" "--kernel" "${kernel}" )
421
[ -n "${ramdisk}" -a "${ramdisk}" != "none" ] &&
422
krd_args=( "${krd_args[@]}" "--ramdisk" "${ramdisk}" )
424
if [ "${EC2PRE%ec2-}" != "${EC2PRE}" ]; then
425
req="EC2_CERT EC2_PRIVATE_KEY EC2_USER_ID EC2_ACCESS_KEY EC2_SECRET_KEY"
426
for env_name in ${req}; do
427
[ -n "${!env_name}" ] ||
428
fail "when using ec2- tools, you must set env: ${req}"
430
ex_bundle_args=( --cert "${EC2_CERT}"
431
--privatekey "${EC2_PRIVATE_KEY}"
432
--user "${EC2_USER_ID}" )
433
ex_upload_args=( --access-key "${EC2_ACCESS_KEY}"
434
--secret-key "${EC2_SECRET_KEY}" )
438
debug 1 "checking for existing registered image at ${bucket}/${manifest}"
439
get_manifest_id "^${bucket}/${manifest}" "/$name$" ||
440
fail "failed to check for existing manifest"
441
if [ -n "${_RET}" ]; then
443
img_id=${1}; path=${2}
444
[ ${allow_existing} -eq 1 ] ||
445
fail "${path} already registered as ${img_id}"
446
debug 1 "using existing ${img_id} for ${bucket}/${manifest}"
448
if [ $image2raw -eq 1 -a "$img_type" = "image" ]; then
449
# this is really here because of LP: #836759
450
# but could be useful elsewhere
451
qemu-img info "$image" > "${TMPD}/disk-info.out" ||
452
fail "failed to qemu-img info $image"
453
imgfmt=$(awk '-F:' '$1 == "file format" { sub(/ /,"",$2); print $2 }' \
454
"${TMPD}/disk-info.out")
455
if [ "$imgfmt" != "raw" ]; then
456
debug 1 "converting image to raw"
457
raw_image="${TMPD}/image.raw"
458
qemu-img convert -O raw "$image" "$raw_image" ||
459
fail "failed to convert image to raw"
461
ln -sf "$raw_image" "$rename_full" ||
462
fail "symlink to raw image $raw_image failed"
464
debug 1 "disk is already raw format, not converting"
467
bundle_args=( "--image" "${rename_full}" )
468
[ -n "${dev_mapping}" ] &&
469
bundle_args[${#bundle_args[@]}]="--block-device-mapping=${dev_mapping}"
471
case "${img_type}" in
473
bundle_args[${#bundle_args[@]}]="--${img_type}"
474
bundle_args[${#bundle_args[@]}]="true"
476
run "${wdir}" "bundle" "bundling ${img_type} ${image}" \
477
${EC2PRE}bundle-image --destination "${wdir}" --arch "${arch}" \
478
"${ex_bundle_args[@]}" \
479
"${bundle_args[@]}" "${krd_args[@]}" ||
480
fail "failed to bundle ${img_type} ${image}"
482
run "${wdir}" "upload" "upload ${bucket}/${manifest}" \
483
${EC2PRE}upload-bundle --bucket "${bucket}" \
484
"${ex_upload_args[@]}" \
485
--manifest "${wdir}/${manifest}" ||
486
fail "failed to upload bundle to ${bucket}/${manifest}"
489
run "${wdir}" "register" "register ${bucket}/${manifest}" \
490
${EC2PRE}register ${name:+--name "${name}"} \
491
"${ex_register_args[@]}" "${bucket}/${manifest}" &&
492
read junk img_id < "${wdir}/register.stdout" &&
493
[ "${img_id#???-}" != "${img_id}" ] || {
494
if bad=$(get_manifest_id "${bucket}/${manifest}" "/${name}") &&
495
[ -n "${bad}" ]; then
498
error "un-registering invalid $bad" >/dev/null
499
${EC2PRE}deregister "${bad_id}"
501
fail "failed to register ${manifest}"
504
debug 1 "registered at ${bucket}/${manifest} as ${img_id}"
507
debug 1 "${img_id} ${bucket}/${manifest}"
509
if [ -z "${output}" -o "${output}" = "-" ]; then
510
printf "%s\t%s\n" "${img_id}" "${bucket}/${manifest}"
512
printf "%s\t%s\n" "${img_id}" "${bucket}/${manifest}" >> "${output}"
515
for user in ${add_acl}; do
516
run "${wdir}" "add_user.${user}" \
517
"add ${user} to ${manifest}" \
518
${EC2PRE}modify-image-attribute \
519
--launch-permission --add "${user}" "${img_id}" ||
520
fail "failed to add launch permission for ${user} to ${img_id}"
525
# vi: ts=4 noexpandtab