# Copyright (c) 2013 The CoreOS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Format options. Each variable uses the form IMG__. # Default values use the format IMG_DEFAULT_. VALID_IMG_TYPES=( ami pxe iso openstack qemu qemu_no_kexec rackspace rackspace_vhd vagrant vagrant_vmware_fusion virtualbox vmware vmware_insecure xen gce brightbox ) # Set at runtime to one of the above types VM_IMG_TYPE=DEFAULT # Set at runtime to the source and destination image paths VM_SRC_IMG= VM_TMP_IMG= VM_TMP_DIR= VM_TMP_ROOT= VM_DST_IMG= VM_README= VM_NAME= # Contains a list of all generated files VM_GENERATED_FILES=() ## DEFAULT # If set to 0 then a partition skeleton won't be laid out on VM_TMP_IMG IMG_DEFAULT_PARTITIONED_IMG=1 # If set to 0 boot_kernel is skipped IMG_DEFAULT_BOOT_KERNEL=1 # If set install the given package name to the OEM partition IMG_DEFAULT_OEM_PACKAGE= # USE flags for the OEM package IMG_DEFAULT_OEM_USE= # Hook to do any final tweaks or grab data while fs is mounted. IMG_DEFAULT_FS_HOOK= # Name of the target image format. # May be raw, qcow2 (qemu), or vmdk (vmware, virtualbox) IMG_DEFAULT_DISK_FORMAT=raw # Name of the partition layout from disk_layout.json IMG_DEFAULT_DISK_LAYOUT=base # Name of the target config format, default is no config IMG_DEFAULT_CONF_FORMAT= # Bundle configs and disk image into some archive IMG_DEFAULT_BUNDLE_FORMAT= # Memory size to use in any config files IMG_DEFAULT_MEM=1024 ## qemu IMG_qemu_DISK_FORMAT=qcow2 IMG_qemu_DISK_LAYOUT=vm IMG_qemu_CONF_FORMAT=qemu IMG_qemu_no_kexec_BOOT_KERNEL=0 IMG_qemu_no_kexec_DISK_FORMAT=qcow2 IMG_qemu_no_kexec_DISK_LAYOUT=vm IMG_qemu_no_kexec_CONF_FORMAT=qemu ## xen IMG_xen_BOOT_KERNEL=0 IMG_xen_DISK_FORMAT=vhd IMG_xen_CONF_FORMAT=xl ## virtualbox IMG_virtualbox_DISK_FORMAT=vmdk_ide IMG_virtualbox_DISK_LAYOUT=vm IMG_virtualbox_CONF_FORMAT=ovf ## vagrant IMG_vagrant_FS_HOOK=box IMG_vagrant_BUNDLE_FORMAT=box IMG_vagrant_DISK_FORMAT=vmdk_ide IMG_vagrant_DISK_LAYOUT=vagrant IMG_vagrant_CONF_FORMAT=vagrant IMG_vagrant_OEM_PACKAGE=oem-vagrant ## vagrant_vmware IMG_vagrant_vmware_fusion_FS_HOOK=box IMG_vagrant_vmware_fusion_BUNDLE_FORMAT=box IMG_vagrant_vmware_fusion_DISK_FORMAT=vmdk_scsi IMG_vagrant_vmware_fusion_DISK_LAYOUT=vagrant IMG_vagrant_vmware_fusion_CONF_FORMAT=vagrant_vmware_fusion IMG_vagrant_vmware_fusion_OEM_PACKAGE=oem-vagrant ## vmware IMG_vmware_DISK_FORMAT=vmdk_scsi IMG_vmware_DISK_LAYOUT=vm IMG_vmware_CONF_FORMAT=vmx ## vmware_insecure IMG_vmware_insecure_DISK_FORMAT=vmdk_scsi IMG_vmware_insecure_DISK_LAYOUT=vm IMG_vmware_insecure_CONF_FORMAT=vmware_zip IMG_vmware_insecure_OEM_PACKAGE=oem-vagrant-key ## ami IMG_ami_BOOT_KERNEL=0 IMG_ami_OEM_PACKAGE=oem-ec2-compat IMG_ami_OEM_USE=ec2 ## openstack, supports ec2's metadata format so use oem-ec2-compat IMG_openstack_DISK_FORMAT=qcow2 IMG_openstack_DISK_LAYOUT=vm IMG_openstack_OEM_PACKAGE=oem-ec2-compat IMG_openstack_OEM_USE=openstack ## brightbox, supports ec2's metadata format so use oem-ec2-compat IMG_brightbox_DISK_FORMAT=qcow2 IMG_brightbox_DISK_LAYOUT=vm IMG_brightbox_OEM_PACKAGE=oem-ec2-compat IMG_brightbox_OEM_USE=brightbox ## pxe, which is an cpio image IMG_pxe_DISK_FORMAT=cpio IMG_pxe_PARTITIONED_IMG=0 IMG_pxe_CONF_FORMAT=pxe ## iso, which is an cpio image IMG_iso_DISK_FORMAT=iso IMG_iso_PARTITIONED_IMG=0 IMG_iso_CONF_FORMAT=iso ## gce, image tarball IMG_gce_DISK_LAYOUT=vm IMG_gce_CONF_FORMAT=gce IMG_gce_OEM_PACKAGE=oem-gce ## rackspace IMG_rackspace_BOOT_KERNEL=0 IMG_rackspace_OEM_PACKAGE=oem-rackspace IMG_rackspace_vhd_BOOT_KERNEL=0 IMG_rackspace_vhd_DISK_FORMAT=vhd IMG_rackspace_vhd_OEM_PACKAGE=oem-rackspace ########################################################### # Validate and set the vm type to use for the rest of the functions set_vm_type() { local vm_type="$1" local valid_type for valid_type in "${VALID_IMG_TYPES[@]}"; do if [[ "${vm_type}" == "${valid_type}" ]]; then VM_IMG_TYPE="${vm_type}" return 0 fi done return 1 } # Validate and set source vm image path set_vm_paths() { local src_dir="$1" local dst_dir="$2" local src_name="$3" VM_SRC_IMG="${src_dir}/${src_name}" if [[ ! -f "${VM_SRC_IMG}" ]]; then die "Source image does not exist: $VM_SRC_IMG" fi local dst_name="$(_src_to_dst_name "${src_name}" "_image.$(_disk_ext)")" VM_DST_IMG="${dst_dir}/${dst_name}" VM_TMP_DIR="${dst_dir}/${dst_name}.vmtmpdir" VM_TMP_IMG="${VM_TMP_DIR}/disk_image.bin" VM_TMP_ROOT="${VM_TMP_DIR}/rootfs" VM_NAME="$(_src_to_dst_name "${src_name}" "")-${COREOS_VERSION_STRING}" VM_README="${dst_dir}/$(_src_to_dst_name "${src_name}" ".README")" } _get_vm_opt() { local opt="$1" local type_opt="IMG_${VM_IMG_TYPE}_${opt}" local default_opt="IMG_DEFAULT_${opt}" echo "${!type_opt:-${!default_opt}}" } # Translate source image names to output names. # This keeps naming consistent across all vm types. _src_to_dst_name() { local src_img="$1" local suffix="$2" echo "${1%_image.bin}_${VM_IMG_TYPE}${suffix}" } # Generate a destination name based on file extension _dst_name() { local src_name=$(basename "$VM_SRC_IMG") local suffix="$1" echo "${src_name%_image.bin}_${VM_IMG_TYPE}${suffix}" } # Return the destination directory _dst_dir() { echo $(dirname "$VM_DST_IMG") } # Combine dst name and dir _dst_path() { echo "$(_dst_dir)/$(_dst_name "$@")" } # Get the proper disk format extension. _disk_ext() { local disk_format=$(_get_vm_opt DISK_FORMAT) case ${disk_format} in raw) echo bin;; qcow2) echo img;; cpio) echo cpio.gz;; vmdk_ide) echo vmdk;; vmdk_scsi) echo vmdk;; *) echo "${disk_format}";; esac } setup_disk_image() { local disk_layout="${1:-$(_get_vm_opt DISK_LAYOUT)}" rm -rf "${VM_TMP_DIR}" mkdir -p "${VM_TMP_DIR}" "${VM_TMP_ROOT}" info "Initializing new disk image..." cp --sparse=always "${VM_SRC_IMG}" "${VM_TMP_IMG}" if [[ $(_get_vm_opt PARTITIONED_IMG) -eq 1 ]]; then "${BUILD_LIBRARY_DIR}/disk_util" --disk_layout="${disk_layout}" \ resize "${VM_TMP_IMG}" fi info "Mounting image to $(relpath "${VM_TMP_ROOT}")" "${BUILD_LIBRARY_DIR}/disk_util" --disk_layout="${disk_layout}" \ mount "${VM_TMP_IMG}" "${VM_TMP_ROOT}" local SYSLINUX_DIR="${VM_TMP_ROOT}/boot/efi/syslinux" if [[ $(_get_vm_opt BOOT_KERNEL) -eq 0 ]]; then sudo mv "${SYSLINUX_DIR}/default.cfg.A" "${SYSLINUX_DIR}/default.cfg" fi # The only filesystem after this point that may be modified is OEM # Note: it would be more logical for disk_util to mount things read-only # to begin with but I'm having trouble making that work reliably. # When mounting w/ ro the automatically allocated loop device will # also be configured as read-only. blockdev --setrw will change that # but io will start throwing errors so that clearly isn't sufficient. local mnt for mnt in $(findmnt -nrR -o target -T "${VM_TMP_ROOT}"); do if [[ "${mnt}" != */usr/share/oem ]]; then sudo mount -o remount,ro "${mnt}" fi done } # If the current type defines a oem package install it to the given fs image. install_oem_package() { local oem_pkg=$(_get_vm_opt OEM_PACKAGE) local oem_use=$(_get_vm_opt OEM_USE) local oem_tmp="${VM_TMP_DIR}/oem" if [[ -z "${oem_pkg}" ]]; then return 0 fi info "Installing ${oem_pkg} to OEM partition" USE="${oem_use}" emerge-${BOARD} --root="${oem_tmp}" \ --root-deps=rdeps --usepkg --quiet "${oem_pkg}" sudo rsync -a "${oem_tmp}/usr/share/oem/" "${VM_TMP_ROOT}/usr/share/oem/" sudo rm -rf "${oem_tmp}" } # Any other tweaks required? run_fs_hook() { local fs_hook=$(_get_vm_opt FS_HOOK) if [[ -n "${fs_hook}" ]]; then info "Running ${fs_hook} fs hook" _run_${fs_hook}_fs_hook "$@" fi } _run_box_fs_hook() { # Copy basic Vagrant configs from OEM mkdir -p "${VM_TMP_DIR}/box" cp -R "${VM_TMP_ROOT}/usr/share/oem/box/." "${VM_TMP_DIR}/box" } # Write the vm disk image to the target directory in the proper format write_vm_disk() { if [[ $(_get_vm_opt PARTITIONED_IMG) -eq 1 ]]; then # unmount before creating block device images cleanup_mounts "${VM_TMP_ROOT}" fi local disk_format=$(_get_vm_opt DISK_FORMAT) info "Writing $disk_format image $(basename "${VM_DST_IMG}")" _write_${disk_format}_disk "${VM_TMP_IMG}" "${VM_DST_IMG}" # Add disk image to final file list if it isn't going to be bundled if [[ -z "$(_get_vm_opt BUNDLE_FORMAT)" ]]; then VM_GENERATED_FILES+=( "${VM_DST_IMG}" ) fi } _write_raw_disk() { mv "$1" "$2" } _write_qcow2_disk() { qemu-img convert -f raw "$1" -O qcow2 "$2" } _write_vhd_disk() { qemu-img convert -f raw "$1" -O vpc "$2" } _write_vmdk_ide_disk() { qemu-img convert -f raw "$1" -O vmdk -o adapter_type=ide "$2" } _write_vmdk_scsi_disk() { qemu-img convert -f raw "$1" -O vmdk -o adapter_type=lsilogic "$2" } _write_cpio_common() { local cpio_target="${VM_TMP_DIR}/rootcpio" local dst_dir=$(_dst_dir) local vmlinuz_name="$(_dst_name ".vmlinuz")" local base_dir="${VM_TMP_ROOT}/usr" local squashfs="usr.squashfs" sudo mkdir -p "${cpio_target}/etc" # If not a /usr image pack up root instead if ! mountpoint -q "${base_dir}"; then base_dir="${VM_TMP_ROOT}" squashfs="newroot.squashfs" # The STATE partition and all of its bind mounts shouldn't be # packed into the squashfs image. Just ROOT. sudo umount --all-targets "${VM_TMP_ROOT}/media/state" # Inject /usr/.noupdate into squashfs to disable update_engine echo "/usr/.noupdate f 444 root root echo -n" >"${VM_TMP_DIR}/extra" else # Use OEM cloud-config to setup the core user's password if [[ -s /etc/shared_user_passwd.txt ]]; then sudo mkdir -p "${cpio_target}/usr/share/oem" sudo_clobber "${cpio_target}/usr/share/oem/cloud-config.yml" <"${VM_TMP_DIR}/extra" fi # Build the squashfs, embed squashfs into a gzipped cpio pushd "${cpio_target}" >/dev/null sudo mksquashfs "${base_dir}" "./${squashfs}" -pf "${VM_TMP_DIR}/extra" find . | cpio -o -H newc | gzip > "$2" popd >/dev/null } # The cpio "disk" is a bit special, # consists of a kernel+initrd not a block device _write_cpio_disk() { local base_dir="${VM_TMP_ROOT}/usr" local dst_dir=$(_dst_dir) local vmlinuz_name="$(_dst_name ".vmlinuz")" _write_cpio_common $@ # Pull the kernel out of the filesystem cp "${base_dir}"/boot/vmlinuz "${dst_dir}/${vmlinuz_name}" VM_GENERATED_FILES+=( "${dst_dir}/${vmlinuz_name}" ) } _write_iso_disk() { local base_dir="${VM_TMP_ROOT}/usr" local iso_target="${VM_TMP_DIR}/rootiso" local dst_dir=$(_dst_dir) local vmlinuz_name="$(_dst_name ".vmlinuz")" mkdir "${iso_target}" pushd "${iso_target}" >/dev/null mkdir isolinux syslinux coreos _write_cpio_common "$1" "${iso_target}/coreos/cpio.gz" cp "${base_dir}"/boot/vmlinuz "${iso_target}/coreos/vmlinuz" cp -R /usr/share/syslinux/* isolinux/ cat< isolinux/isolinux.cfg INCLUDE /syslinux/syslinux.cfg EOF cat< syslinux/syslinux.cfg default coreos prompt 1 timeout 15 label coreos menu default kernel /coreos/vmlinuz append initrd=/coreos/cpio.gz coreos.autologin EOF mkisofs -v -l -r -J -o $2 -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table . isohybrid $2 popd >/dev/null } # If a config format is defined write it! write_vm_conf() { local conf_format=$(_get_vm_opt CONF_FORMAT) if [[ -n "${conf_format}" ]]; then info "Writing ${conf_format} configuration" _write_${conf_format}_conf "$@" fi } _write_qemu_common() { local script="$1" local vm_mem="$(_get_vm_opt MEM)" sed -e "s%^VM_NAME=.*%VM_NAME='${VM_NAME}'%" \ -e "s%^VM_MEMORY=.*%VM_MEMORY='${vm_mem}'%" \ "${BUILD_LIBRARY_DIR}/qemu_template.sh" > "${script}" checkbashisms --posix "${script}" || die chmod +x "${script}" cat >"${VM_README}" <>"${VM_README}" <"${vmx_path}" < "${VM_TMP_DIR}/insecure_ssh_key" < "${VM_README}" < "${pygrub}" echo 'bootloader = "pygrub"' >> "${pygrub}" echo '# Xen PV config using pvgrub' > "${pvgrub}" echo 'kernel = "/usr/lib/xen/boot/pv-grub-x86_64.gz"' >> "${pvgrub}" echo 'extra = "(hd0,0)/boot/grub/menu.lst"' >> "${pvgrub}" # The rest is the same tee -a "${pygrub}" >> "${pvgrub}" < "${VM_README}" < "${VM_README}" < "${VM_TMP_DIR}"/box/metadata.json < "${VM_TMP_DIR}"/box/metadata.json <"${json}" <