[molecule.specs.plugins] introduce iso_to_image plugin.

This plugin allows to build filesystem images (user can define the
filesystem type) off an ISO image.
This commit is contained in:
Fabio Erculiani
2010-08-08 23:41:57 +02:00
parent 91242f3744
commit 3e918c703c
4 changed files with 758 additions and 0 deletions
@@ -0,0 +1,13 @@
# Use abs path, otherwise daily builds automagic won't work
# For further documentation, see the file above:
%import /sabayon/molecules/spinbase-amazon-ami-image-template.common
# pre chroot command, example, for 32bit chroots on 64bit system, you always
# have to append "linux32" this is useful for inner_chroot_script
# prechroot:
# Path to source ISO file (MANDATORY)
source_iso: /sabayon/iso/Sabayon_Linux_SpinBase_DAILY_amd64.iso
release_version: 5.3
image_name: Sabayon_Linux_SpinBase_5.3_amd64_ami.img
@@ -0,0 +1,13 @@
# Use abs path, otherwise daily builds automagic won't work
# For further documentation, see the file above:
%import /sabayon/molecules/spinbase-amazon-ami-image-template.common
# pre chroot command, example, for 32bit chroots on 64bit system, you always
# have to append "linux32" this is useful for inner_chroot_script
prechroot: linux32
# Path to source ISO file (MANDATORY)
source_iso: /sabayon/iso/Sabayon_Linux_SpinBase_DAILY_x86.iso
release_version: 5.3
image_name: Sabayon_Linux_SpinBase_5.3_x86_ami.img
@@ -0,0 +1,105 @@
# Define an alternative execution strategy, in this case, the value must be
execution_strategy: iso_to_image
# Error script command, executed when something went wrong and molecule has
# to terminate the execution
# error_script: /path/to/script/to/be/executed/outside/after
# Outer chroot script command, to be executed outside destination chroot before
# before entering it (and before inner_chroot_script)
# outer_chroot_script: /path/to/script/to/be/executed/outside
# Inner chroot script command, to be executed inside destination chroot before
# packing it
# inner_chroot_script: /sabayon/scripts/openvz_inner_chroot_script.sh
# Inner chroot script command, to be executed inside destination chroot after
# packages installation and removal
inner_chroot_script_after: /sabayon/scripts/image_generic_inner_chroot_script_after.sh
# Outer chroot script command, to be executed outside destination chroot before
# before entering it (and AFTER inner_chroot_script)
# outer_chroot_script_after: /path/to/script/to/be/executed/outside/after
# Pre-image building script. Hook called before image file creation
# Variables exported:
# TMP_IMAGE_PATH = path pointing to the temporary destination image file
# LOOP_DEVICE = loop device (/dev/loopN) currently in use
# pre_image_script: /sabayon/scripts/pre_image_script.sh
# Post-image building script. Hook called after image file creation and move
# into destination directory.
# Variables exported:
# IMAGE_PATH = path pointing to the destination image file
# IMAGE_CHECKSUM_PATH = path pointing to the destination image file checksum (md5)
# post_image_script: /sabayon/scripts/post_image_script.sh
# Destination directory for the image path (MANDATORY)
destination_image_directory: /sabayon/images
# Specify an alternative image file name (image file name will be automatically
# produced otherwise)
# image_name:
# Specify the image file size in Megabytes. This is mandatory.
# To avoid runtime failure, make sure the image is large enough to fit your
# chroot data.
# Example: 5000 (means: ~5GB)
# Example: 15000 (means: ~15GB)
image_mb: 5000
# Either set this to "yes" or "no" if you want your image to be filled with
# random data instead of zeroes. urandom is used, fallback is: random.
# Default is: no
# image_randomize:
# Specify an image filesystem formatter that takes a single argument , which is
# the image device (by design, a loop device is passed to this executable).
# Default is: mkfs.ext3
# image_formatter:
# Specify an image file mount command. Two arguments are passed: (1) a loop
# device (/dev/loopN), (2) a temporary destination directory.
# Default is: mount -o loop,rw
# image_mounter:
# Specify am image file unmount command. One arguments is passed: mount point.
# Default is: umount
# image_umounter:
# Alternative ISO file mount command (default is: mount -o loop -t iso9660)
# iso_mounter:
# Alternative ISO umounter command (default is: umount)
# iso_umounter:
# Alternative squashfs file mount command (default is: mount -o loop -t squashfs)
# squash_mounter:
# Alternative ISO squashfs umount command (default is: umount)
# squash_umounter:
# List of packages that would be removed from chrooted system (comma separated)
# packages_to_remove:
# Custom shell call to packages removal (default is: equo remove)
# custom_packages_remove_cmd:
# List of packages that would be added from chrooted system (comma separated)
# packages_to_add:
# Custom shell call to packages add (default is: equo install)
# custom_packages_add_cmd:
# Custom command for updating repositories (default is: equo update)
# repositories_update_cmd:
# Determine whether repositories update should be run (if packages_to_add is set)
# (default is: no), values are: yes, no.
# execute_repositories_update: no
# Directories to remove completely (comma separated)
# paths_to_remove:
# Directories to empty (comma separated)
# paths_to_empty:
+627
View File
@@ -0,0 +1,627 @@
# -*- coding: utf-8 -*-
# Molecule Disc Image builder for Sabayon Linux
# Copyright (C) 2009 Fabio Erculiani
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import array
import os
import shutil
import tempfile
import gc
import errno
from molecule.i18n import _
from molecule.output import blue, darkred
from molecule.specs.skel import GenericExecutionStep, GenericSpec
from molecule.specs.plugins.builtin_plugin import BuiltinHandlerMixin
from molecule.specs.plugins.remaster_plugin import IsoUnpackHandler as \
RemasterIsoUnpackHandler, ChrootHandler as RemasterChrootHandler
import molecule.utils
class ImageHandler(GenericExecutionStep, BuiltinHandlerMixin):
LOSETUP_EXEC = "/sbin/losetup"
MB_IN_BYTES = 1024000
DEFAULT_IMAGE_FORMATTER = ["/sbin/mkfs.ext3"]
DEFAULT_IMAGE_MOUNTER = ["/bin/mount", "-o", "loop,rw"]
DEFAULT_IMAGE_UMOUNTER = ["/bin/umount"]
def __init__(self, *args, **kwargs):
GenericExecutionStep.__init__(self, *args, **kwargs)
# init variables
self.loop_device = None
self._tmp_loop_device_fd = None
self.tmp_loop_device_file = None
self.image_mb = 0
self.randomize = False
self.image_mounted = False
self.tmp_image_mount = None
def setup(self):
self.image_mb = self.metadata['image_mb']
if self.metadata.get('image_randomize') == "yes":
self.randomize = True
sts, loop_device = molecule.utils.exec_cmd_get_status_output(
[ImageHandler.LOSETUP_EXEC, "-f"])
if sts != 0:
# ouch
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("setup hook failed"), _("cannot setup loop device"),
)
)
return sts
try:
self._tmp_loop_device_fd, self.tmp_loop_device_file = \
tempfile.mkstemp(prefix = "molecule", dir = "/var/tmp")
except (OSError, IOError,) as err:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("setup hook failed"), _("cannot create temporary file"),
)
)
return 1
# bind loop device
args = [ImageHandler.LOSETUP_EXEC, loop_device,
self.tmp_loop_device_file]
rc = molecule.utils.exec_cmd(args)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("setup hook failed"), _("cannot bind loop device"),
)
)
return 1
self.loop_device = loop_device
self.tmp_image_mount = molecule.utils.mkdtemp()
# setup metadata for next phases
self.metadata['ImageHandler_loop_device_file'] = \
self.tmp_loop_device_file
self.metadata['ImageHandler_tmp_image_mount'] = self.tmp_image_mount
self.metadata['ImageHandler_loop_device'] = self.loop_device
self.metadata['ImageHandler_kill_loop_device'] = self._kill_loop_device
return 0
def pre_run(self):
self._output.output("[%s|%s] %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("executing pre_run"),
)
)
# run pre image script
exec_script = self.metadata.get('pre_image_script')
if exec_script:
env = os.environ.copy()
env['TMP_IMAGE_PATH'] = self.tmp_loop_device_file
env['LOOP_DEVICE'] = self.loop_device
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("spawning"), exec_script,
)
)
rc = molecule.utils.exec_cmd(exec_script, env = env)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("pre image hook failed"), rc,
)
)
return rc
return 0
def _fill_image_file(self):
"""
Fill image file (using _tmp_loop_device_fd) with either zeroes or
random data of image_mb size.
@raises IOError: if space is not enough
@raises OSError: well, sorry
"""
if self.randomize:
self._output.output("[%s|%s] %s => %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("generating random base image file"),
self.tmp_loop_device_file,
)
)
else:
self._output.output("[%s|%s] %s => %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("generating zeroed base image file"),
self.tmp_loop_device_file,
)
)
image_mb = self.image_mb
loop_f = os.fdopen(self._tmp_loop_device_fd, "wb")
self._tmp_loop_device_fd = None
arr = None
mb_bytes = ImageHandler.MB_IN_BYTES
while image_mb > 0:
image_mb -= 1
if self.randomize:
arr = array.array('c', molecule.utils.get_random_str(mb_bytes))
else:
arr = array.array('c', chr(0)*mb_bytes)
arr.tofile(loop_f)
del arr
gc.collect()
# file self._tmp_loop_device_fd is closed here.
# no more writes needed
loop_f.flush()
loop_f.close()
# last but not least, tell the loop device that the file size changed
args = [ImageHandler.LOSETUP_EXEC, "-c", self.loop_device]
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("spawning"), args,
)
)
rc = molecule.utils.exec_cmd(args)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("image file resize failed"), rc,
)
)
return rc
def run(self):
self._output.output("[%s|%s] %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("run hook called"),
)
)
# fill image file
try:
rc = self._fill_image_file()
if rc != 0:
return rc
except (IOError, OSError) as err:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("image file generation failed"), err,
)
)
return 1
# format image file
image_formatter = self.metadata.get('image_formatter',
ImageHandler.DEFAULT_IMAGE_FORMATTER)
formatter_args = image_formatter + [self.loop_device]
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("spawning"), formatter_args,
)
)
rc = molecule.utils.exec_cmd(formatter_args)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("image formatter hook failed"), rc,
)
)
return rc
# mount image file
mounter = self.metadata.get('image_mounter',
ImageHandler.DEFAULT_IMAGE_MOUNTER)
mount_args = mounter + [self.loop_device, self.tmp_image_mount]
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("spawning"), mount_args,
)
)
rc = molecule.utils.exec_cmd(mount_args)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("image mount failed"), rc,
)
)
return rc
self.image_mounted = True
return 0
def post_run(self):
""" Nothing to do """
return 0
def _kill_loop_device(self, preserve_loop_device_file = False):
if self.image_mounted:
umounter = self.metadata.get('image_umounter',
ImageHandler.DEFAULT_IMAGE_UMOUNTER)
args = umounter + [self.tmp_image_mount]
rc = molecule.utils.exec_cmd(args)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("unable to umount loop device"), self.loop_device,
)
)
else:
self.image_mounted = False
if self.tmp_image_mount is not None:
try:
os.rmdir(self.tmp_image_mount)
except OSError:
pass
# kill loop device
if self.loop_device is not None:
rc = molecule.utils.exec_cmd([ImageHandler.LOSETUP_EXEC, "-d",
self.loop_device])
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("unable to kill loop device"), self.loop_device,
)
)
else:
self.loop_device = None
if (self.tmp_loop_device_file is not None) and \
(not preserve_loop_device_file):
try:
os.remove(self.tmp_loop_device_file)
self.tmp_loop_device_file = None
except OSError as err:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("unable to remove temp. loop device file"),
err,
)
)
def kill(self, success = True):
# kill tmp files
if self._tmp_loop_device_fd is not None:
try:
os.close(self._tmp_loop_device_fd)
except IOError as err:
self._output.output("[%s|%s] %s: %s" % (
blue("ImageHandler"), darkred(self.spec_name),
_("unable to close temp fd"), err,
)
)
self._tmp_loop_device_fd = None
if not success:
self._kill_loop_device()
self._run_error_script(None, None, None)
return 0
class IsoUnpackHandler(RemasterIsoUnpackHandler):
def setup(self):
unpack_prefix = molecule.utils.mkdtemp(suffix = "chroot")
self.metadata['chroot_tmp_dir'] = unpack_prefix
self.metadata['chroot_unpack_path'] = \
self.metadata['ImageHandler_tmp_image_mount']
self.dest_root = os.path.join(unpack_prefix, "cdroot")
# setup upcoming new chroot path
self.chroot_dir = self.metadata['chroot_unpack_path']
# explicitly set this to None
self.metadata['cdroot_path'] = None
return 0
def kill(self, success = True):
# ImageHandler sets this
if not success:
self.metadata['ImageHandler_kill_loop_device']()
RemasterIsoUnpackHandler.kill(self, success = success)
# we don't need the whole dir
tmp_dir = self.metadata['chroot_tmp_dir']
if os.path.isdir(tmp_dir):
try:
shutil.rmtree(tmp_dir, True)
except (shutil.Error, OSError,):
self._output.output("[%s|%s] %s: %s" % (
blue("IsoUnpackHandler"), darkred(self.spec_name),
_("unable to remove temp. dir"), tmp_dir,
)
)
return 0
class ChrootHandler(RemasterChrootHandler):
def setup(self):
# to make superclass working
self.source_dir = self.metadata['chroot_unpack_path']
self.dest_dir = self.source_dir
return 0
def kill(self, success = True):
# ImageHandler sets this
if not success:
self.metadata['ImageHandler_kill_loop_device']()
RemasterChrootHandler.kill(self, success = success)
return 0
class FinalImageHandler(GenericExecutionStep, BuiltinHandlerMixin):
MD5_EXT = ".md5"
IMAGE_EXT = ".img"
def __init__(self, *args, **kwargs):
GenericExecutionStep.__init__(self, *args, **kwargs)
def setup(self):
# ImageHandler sets this
self.tmp_loop_device_file = \
self.metadata['ImageHandler_loop_device_file']
# umount all, but don't remove our loop device file
self.metadata['ImageHandler_kill_loop_device'](
preserve_loop_device_file = True)
image_name = self.metadata.get('image_name',
os.path.basename(self.metadata['source_iso']) + \
FinalImageHandler.IMAGE_EXT)
self.dest_path = os.path.join(
self.metadata['destination_image_directory'], image_name)
dest_path_dir = os.path.dirname(self.dest_path)
if (not os.path.lexists(dest_path_dir)) and \
(not os.path.isdir(dest_path_dir)):
os.makedirs(dest_path_dir, 0o755)
return 0
def pre_run(self):
""" Nothing to do """
return 0
def run(self):
self._output.output("[%s|%s] %s: %s %s" % (
blue("FinalImageHandler"), darkred(self.spec_name),
_("run hook called"), _("moving image file to destination"),
self.dest_path,
)
)
try:
os.rename(self.tmp_loop_device_file, self.dest_path)
except OSError as err:
if err.errno != errno.EXDEV:
raise
# cannot move atomically, fallback to shutil.move
try:
shutil.move(self.tmp_loop_device_file, self.dest_path)
except shutil.Error:
raise
self._output.output("[%s|%s] %s: %s" % (
blue("FinalImageHandler"), darkred(self.spec_name),
_("built image"), self.dest_path,
)
)
if os.path.isfile(self.dest_path) and os.access(self.dest_path, os.R_OK):
self._output.output("[%s|%s] %s: %s" % (
blue("FinalImageHandler"), darkred(self.spec_name),
_("generating md5 for"), self.dest_path,
)
)
digest = molecule.utils.md5sum(self.dest_path)
md5file = self.dest_path + FinalImageHandler.MD5_EXT
with open(md5file, "w") as f:
f.write("%s %s\n" % (digest,
os.path.basename(self.dest_path),))
f.flush()
def post_run(self):
# run post tar script
exec_script = self.metadata.get('post_image_script')
if exec_script:
env = os.environ.copy()
env['IMAGE_PATH'] = self.dest_path
env['IMAGE_CHECKSUM_PATH'] = self.dest_path + \
FinalImageHandler.MD5_EXT
self._output.output("[%s|%s] %s: %s" % (
blue("FinalImageHandler"), darkred(self.spec_name),
_("spawning"), exec_script,
)
)
rc = molecule.utils.exec_cmd(exec_script, env = env)
if rc != 0:
self._output.output("[%s|%s] %s: %s" % (
blue("FinalImageHandler"), darkred(self.spec_name),
_("post image hook failed"), rc,
)
)
return rc
return 0
def kill(self, success = True):
""" Nothing to do """
return 0
class IsoToImageSpec(GenericSpec):
PLUGIN_API_VERSION = 0
@staticmethod
def execution_strategy():
return "iso_to_image"
def vital_parameters(self):
return [
"source_iso",
"destination_image_directory",
"image_mb",
]
def parser_data_path(self):
return {
'prechroot': {
'cb': self.valid_exec_first_list_item,
've': self.ve_string_splitter,
},
'release_string': {
'cb': self.ne_string, # validation callback
've': self.ve_string_stripper, # value extractor
},
'release_version': {
'cb': self.ne_string,
've': self.ve_string_stripper,
},
'release_desc': {
'cb': self.ne_string,
've': self.ve_string_stripper,
},
'release_file': {
'cb': self.ne_string,
've': self.ve_string_stripper,
},
'source_iso': {
'cb': self.valid_path_string,
've': self.ve_string_stripper,
},
'image_name': {
'cb': self.ne_string,
've': self.ve_string_stripper,
},
'image_formatter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'image_mounter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'image_umounter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'image_mb': {
'cb': self.valid_integer,
've': self.ve_integer_converter,
},
'image_randomize': {
'cb': self.valid_ascii,
've': self.ve_string_stripper,
},
'error_script': {
'cb': self.valid_exec_first_list_item,
've': self.ve_string_splitter,
},
'outer_chroot_script': {
'cb': self.valid_exec_first_list_item,
've': self.ve_string_splitter,
},
'inner_chroot_script': {
'cb': self.valid_path_string_first_list_item,
've': self.ve_string_splitter,
},
'inner_chroot_script_after': {
'cb': self.valid_path_string_first_list_item,
've': self.ve_string_splitter,
},
'outer_chroot_script_after': {
'cb': self.valid_exec_first_list_item,
've': self.ve_string_splitter,
},
'destination_image_directory': {
'cb': self.valid_dir,
've': self.ve_string_stripper,
},
'pre_image_script': {
'cb': self.valid_exec_first_list_item,
've': self.ve_string_splitter,
},
'post_image_script': {
'cb': self.valid_exec_first_list_item,
've': self.ve_string_splitter,
},
'iso_mounter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'iso_umounter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'squash_mounter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'squash_umounter': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'custom_packages_remove_cmd': {
'cb': self.valid_ascii,
've': self.ve_string_stripper,
},
'custom_packages_add_cmd': {
'cb': self.valid_ascii,
've': self.ve_string_stripper,
},
'packages_to_remove': {
'cb': self.ne_list,
've': self.valid_comma_sep_list,
},
'packages_to_add': {
'cb': self.ne_list,
've': self.valid_comma_sep_list,
},
'repositories_update_cmd': {
'cb': self.ne_list,
've': self.ve_string_splitter,
},
'execute_repositories_update': {
'cb': self.valid_ascii,
've': self.ve_string_stripper,
},
'paths_to_remove': {
'cb': self.ne_list,
've': self.valid_path_list,
},
'paths_to_empty': {
'cb': self.ne_list,
've': self.valid_path_list,
},
}
def get_execution_steps(self):
return [ImageHandler, IsoUnpackHandler, ChrootHandler,
FinalImageHandler]