diff --git a/ChangeLog b/ChangeLog index 92c5c4fa4a06afbc5435f70dbb5745d1874e486e..22962c4bdeb4120787decb76a24d0ef1608d9cda 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2014-11-04, v0.7.4 + * Handle cases where qemu-nbd command is missing + 2014-10-21, v0.7.3 * Instruct kamaki to ignore the ssl certificates (kamaki >= 0.13rc5). diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py index 2c4e5d62f611b0c2eba448fe9a7ba515ec0e897e..6104c66209be7b492916bffcdd49175d885a1c6b 100644 --- a/image_creator/dialog_wizard.py +++ b/image_creator/dialog_wizard.py @@ -105,18 +105,19 @@ class WizardPage(object): NEXT = 1 PREV = -1 - def __init__(self, name, text, **kargs): + def __init__(self, name, text, **kwargs): self.name = name self.answer = None self.text = text - self.print_name = kargs['print_name'] if 'print_name' in kargs \ + self.print_name = kwargs['print_name'] if 'print_name' in kwargs \ else " ".join(re.findall('[A-Z][^A-Z]*', name)) - self.title = kargs['title'] if 'title' in kargs else self.print_name - self.default = kargs['default'] if 'default' in kargs else "" - self.extra = kargs['extra'] if 'extra' in kargs else None + self.title = kwargs['title'] if 'title' in kwargs else self.print_name + self.default = kwargs['default'] if 'default' in kwargs else "" + self.extra = kwargs['extra'] if 'extra' in kwargs else None self.validate = \ - kargs['validate'] if 'validate' in kargs else lambda x: x - self.display = kargs['display'] if 'display' in kargs else lambda x: x + kwargs['validate'] if 'validate' in kwargs else lambda x: x + self.display = \ + kwargs['display'] if 'display' in kwargs else lambda x: x self.dargs = {} self.dargs['ok_label'] = 'Next' @@ -124,10 +125,10 @@ class WizardPage(object): self.dargs['width'] = PAGE_WIDTH self.dargs['height'] = PAGE_HEIGHT - if 'extra' in kargs: + if 'extra' in kwargs: self.dargs['extra_button'] = 1 - self.extra_label = kargs['extra_label'] if 'extra_label' in kargs \ + self.extra_label = kwargs['extra_label'] if 'extra_label' in kwargs \ else lambda: "extra" def __str__(self): @@ -167,9 +168,9 @@ class WizardInfoPage(WizardPage): The user-defined information is created by the info function. """ - def __init__(self, name, text, info, **kargs): + def __init__(self, name, text, info, **kwargs): """Initialize the WizardInfoPage instance""" - super(WizardInfoPage, self).__init__(name, text, **kargs) + super(WizardInfoPage, self).__init__(name, text, **kwargs) self.info = info def show(self, dialog, title): @@ -194,9 +195,9 @@ class WizardInfoPage(WizardPage): class WizardFormPage(WizardPage): """Represents a Form in a wizard""" - def __init__(self, name, text, fields, **kargs): + def __init__(self, name, text, fields, **kwargs): """Initialize the WizardFormPage instance""" - super(WizardFormPage, self).__init__(name, text, **kargs) + super(WizardFormPage, self).__init__(name, text, **kwargs) self.fields = fields def show(self, dialog, title): @@ -227,11 +228,11 @@ class WizardPageWthChoices(WizardPage): the choices variable. If the choices function returns an empty list, a fallback function is executed if available. """ - def __init__(self, name, text, choices, **kargs): + def __init__(self, name, text, choices, **kwargs): """Initialize the WizardPageWthChoices instance""" - super(WizardPageWthChoices, self).__init__(name, text, **kargs) + super(WizardPageWthChoices, self).__init__(name, text, **kwargs) self.choices = choices - self.fallback = kargs['fallback'] if 'fallback' in kargs else None + self.fallback = kwargs['fallback'] if 'fallback' in kwargs else None class WizardRadioListPage(WizardPageWthChoices): diff --git a/image_creator/disk.py b/image_creator/disk.py index 0f1fdc46bd616d0a52a8ca2a317ec8a61bb9d514..81bb5be4e097e2271ce353d567c6f375e64f3242 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -18,7 +18,7 @@ """Module hosting the Disk class.""" from image_creator.util import get_command, try_fail_repeat, free_space, \ - FatalError, create_snapshot + FatalError, create_snapshot, image_info from image_creator.bundle_volume import BundleVolume from image_creator.image import Image @@ -80,7 +80,7 @@ class Disk(object): self._add_cleanup(shutil.rmtree, self.tmp) def _add_cleanup(self, job, *args): - """Add a new job in the cleanup list""" + """Add a new job in the cleanup list.""" self._cleanup_jobs.append((job, args)) def _losetup(self, fname): @@ -93,7 +93,7 @@ class Disk(object): return loop def _dir_to_disk(self): - """Create a disk out of a directory""" + """Create a disk out of a directory.""" if self.source == '/': bundle = BundleVolume(self.out, self.meta) image = '%s/%s.raw' % (self.tmp, uuid.uuid4().hex) @@ -125,7 +125,7 @@ class Disk(object): @property def file(self): - """Convert the source media into a file""" + """Convert the source media into a file.""" if self._file is not None: return self._file @@ -157,19 +157,21 @@ class Disk(object): return self.file # Examine media file - mode = os.stat(self.file).st_mode + info = image_info(self.file) self.out.output("Snapshotting media source ...", False) - # Create a qcow2 snapshot for image files - if not stat.S_ISBLK(mode): + # Create a qcow2 snapshot for image files that are not raw + if info['format'] != 'raw': snapshot = create_snapshot(self.file, self.tmp) self._add_cleanup(os.unlink, snapshot) self.out.success('done') return snapshot - # Create a device-mapper snapshot for block devices - size = int(blockdev('--getsz', self.file)) + # Create a device-mapper snapshot for raw image files and block devices + mode = os.stat(self.file).st_mode + device = self.file if stat.S_ISBLK(mode) else self._losetup(self.file) + size = int(blockdev('--getsz', device)) cowfd, cow = tempfile.mkstemp(dir=self.tmp) os.close(cowfd) @@ -183,7 +185,7 @@ class Disk(object): try: try: os.write(tablefd, "0 %d snapshot %s %s n 8\n" % - (size, self.file, cowdev)) + (size, device, cowdev)) finally: os.close(tablefd) @@ -194,17 +196,19 @@ class Disk(object): self.out.success('done') return "/dev/mapper/%s" % snapshot - def get_image(self, media, **kargs): + def get_image(self, media, **kwargs): """Returns a newly created Image instance.""" - - image = Image(media, self.out, **kargs) + info = image_info(media) + image = Image(media, self.out, format=info['format'], **kwargs) self._images.append(image) image.enable() return image def destroy_image(self, image): - """Destroys an Image instance previously created by get_image method. + """Destroys an Image instance previously created with the get_image() + method. """ + self._images.remove(image) image.destroy() diff --git a/image_creator/gpt.py b/image_creator/gpt.py index c02312a2d6bd32789f63018a50378e4cfefa15ad..b473ec32a07e2468ab8523f9e01a1726283e8ca5 100644 --- a/image_creator/gpt.py +++ b/image_creator/gpt.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module provides the code for handling GUID partition tables""" +"""This module provides the code for handling GUID partition tables.""" import struct import sys diff --git a/image_creator/image.py b/image_creator/image.py index da3777df927ee34c828b19044972863e2514ebe9..2e243e86d88a588afad7b878b99a90ca3dee000f 100644 --- a/image_creator/image.py +++ b/image_creator/image.py @@ -15,7 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from image_creator.util import FatalError, QemuNBD, image_info +"""Module hosting the Image class.""" + +from image_creator.util import FatalError, QemuNBD from image_creator.gpt import GPTPartitionTable from image_creator.os_type import os_cls @@ -28,16 +30,16 @@ from sendfile import sendfile class Image(object): """The instances of this class can create images out of block devices.""" - def __init__(self, device, output, **kargs): + def __init__(self, device, output, **kwargs): """Create a new Image instance""" self.device = device self.out = output - self.info = image_info(device) + self.format = kwargs['format'] if 'format' in kwargs else 'raw' - self.meta = kargs['meta'] if 'meta' in kargs else {} + self.meta = kwargs['meta'] if 'meta' in kwargs else {} self.sysprep_params = \ - kargs['sysprep_params'] if 'sysprep_params' in kargs else {} + kwargs['sysprep_params'] if 'sysprep_params' in kwargs else {} self.progress_bar = None self.guestfs_device = None @@ -50,6 +52,10 @@ class Image(object): # This is needed if the image format is not raw self.nbd = QemuNBD(device) + if self.nbd.qemu_nbd is None and self.format != 'raw': + raise FatalError("qemu-nbd command is missing, only raw input " + "media are supported") + def check_guestfs_version(self, major, minor, release): """Checks if the version of the used libguestfs is smaller, equal or greater than the one specified by the major, minor and release triplet @@ -222,11 +228,11 @@ class Image(object): class RawImage: """The RawImage context manager""" def __enter__(self): - return img.device if img.info['format'] == 'raw' else \ + return img.device if img.format == 'raw' else \ img.nbd.connect(readonly) def __exit__(self, exc_type, exc_value, traceback): - if img.info['format'] != 'raw': + if img.format != 'raw': img.nbd.disconnect() return RawImage() diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py index 818ad6589b73286cd024f9c4ff96fdf7f0d53603..a3b9325d28d0792f642e51c697b67c85c64aa165 100644 --- a/image_creator/os_type/__init__.py +++ b/image_creator/os_type/__init__.py @@ -87,7 +87,7 @@ def sysprep(message, enabled=True, **kwargs): class SysprepParam(object): """This class represents a system preparation parameter""" - def __init__(self, type, default, description, **kargs): + def __init__(self, type, default, description, **kwargs): assert hasattr(self, "_check_%s" % type), "Invalid type: %s" % type @@ -96,8 +96,8 @@ class SysprepParam(object): self.description = description self.value = default self.error = None - self.check = kargs['check'] if 'check' in kargs else lambda x: x - self.hidden = kargs['hidden'] if 'hidden' in kargs else False + self.check = kwargs['check'] if 'check' in kwargs else lambda x: x + self.hidden = kwargs['hidden'] if 'hidden' in kwargs else False def set_value(self, value): """Update the value of the parameter""" @@ -160,11 +160,11 @@ class SysprepParam(object): raise ValueError("Invalid dirname") -def add_sysprep_param(name, type, default, descr, **kargs): +def add_sysprep_param(name, type, default, descr, **kwargs): """Decorator for __init__ that adds the definition for a system preparation parameter in an instance of an os_type class """ - extra = kargs + extra = kwargs def wrapper(init): @wraps(init) @@ -196,7 +196,7 @@ def del_sysprep_param(name): class OSBase(object): """Basic operating system class""" - def __init__(self, image, **kargs): + def __init__(self, image, **kwargs): self.image = image self.root = image.root @@ -206,8 +206,8 @@ class OSBase(object): if not hasattr(self, 'sysprep_params'): self.sysprep_params = {} - if 'sysprep_params' in kargs: - for key, val in kargs['sysprep_params'].items(): + if 'sysprep_params' in kwargs: + for key, val in kwargs['sysprep_params'].items(): if key not in self.sysprep_params: self.out.warn("Ignoring invalid `%s' parameter." % key) continue @@ -504,7 +504,7 @@ class OSBase(object): """List the name of all files recursively under a directory""" return self.image.g.find(directory) - def _foreach_file(self, directory, action, **kargs): + def _foreach_file(self, directory, action, **kwargs): """Perform an action recursively on all files under a directory. The following options are allowed: @@ -524,17 +524,17 @@ class OSBase(object): self.out.warn("Directory: `%s' does not exist!" % directory) return - maxdepth = None if 'maxdepth' not in kargs else kargs['maxdepth'] + maxdepth = None if 'maxdepth' not in kwargs else kwargs['maxdepth'] if maxdepth == 0: return # maxdepth -= 1 maxdepth = None if maxdepth is None else maxdepth - 1 - kargs['maxdepth'] = maxdepth + kwargs['maxdepth'] = maxdepth - exclude = None if 'exclude' not in kargs else kargs['exclude'] - include = None if 'include' not in kargs else kargs['include'] - ftype = None if 'ftype' not in kargs else kargs['ftype'] + exclude = None if 'exclude' not in kwargs else kwargs['exclude'] + include = None if 'include' not in kwargs else kwargs['include'] + ftype = None if 'ftype' not in kwargs else kwargs['ftype'] has_ftype = lambda x, y: y is None and True or x['ftyp'] == y for f in self.image.g.readdir(directory): @@ -550,7 +550,7 @@ class OSBase(object): continue if has_ftype(f, 'd'): - self._foreach_file(full_path, action, **kargs) + self._foreach_file(full_path, action, **kwargs) if has_ftype(f, ftype): action(full_path) diff --git a/image_creator/os_type/linux.py b/image_creator/os_type/linux.py index 8569d03eb4344019eb963e692a4dd0c6268172ee..00d630063de256eda316891b8a8652a2e1354b27 100644 --- a/image_creator/os_type/linux.py +++ b/image_creator/os_type/linux.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module hosts OS-specific code for Linux""" +"""This module hosts OS-specific code for Linux.""" from image_creator.os_type.unix import Unix, sysprep @@ -38,8 +38,8 @@ X2GO_EXECUTABLE = "x2goruncommand" class Linux(Unix): """OS class for Linux""" - def __init__(self, image, **kargs): - super(Linux, self).__init__(image, **kargs) + def __init__(self, image, **kwargs): + super(Linux, self).__init__(image, **kwargs) self._uuid = dict() self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*') diff --git a/image_creator/os_type/rhel.py b/image_creator/os_type/rhel.py index 5d37a37df50d4d4a5f0f5778dff0a84acca4530e..df8372092556950e5af972f0c6766792a082e9bb 100644 --- a/image_creator/os_type/rhel.py +++ b/image_creator/os_type/rhel.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module hosts OS-specific code for Red Hat Enterprise Linux""" +"""This module hosts OS-specific code for Red Hat Enterprise Linux.""" from image_creator.os_type.linux import Linux diff --git a/image_creator/os_type/slackware.py b/image_creator/os_type/slackware.py index 475f69f240d7cec3d78c453119e72ffafbf78c3d..bf284765395d0273ecda7a622fe61b9377a51c18 100644 --- a/image_creator/os_type/slackware.py +++ b/image_creator/os_type/slackware.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module hosts OS-specific code for Slackware Linux""" +"""This module hosts OS-specific code for Slackware Linux.""" from image_creator.os_type.linux import Linux, sysprep diff --git a/image_creator/os_type/ubuntu.py b/image_creator/os_type/ubuntu.py index 1c6724bfa10e82e292b28f6977c4b792ae7a9ce9..e7349d3446f53620ebfc4339ae7c8286e7e2c33c 100644 --- a/image_creator/os_type/ubuntu.py +++ b/image_creator/os_type/ubuntu.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module hosts OS-specific code for Ubuntu Linux""" +"""This module hosts OS-specific code for Ubuntu Linux.""" import re diff --git a/image_creator/os_type/unsupported.py b/image_creator/os_type/unsupported.py index 93b711f92ef8c16f42aa97f4a1fdaab4f388b9ef..6b8d9cba54ae306096f61ba267b6b19e3aed20d2 100644 --- a/image_creator/os_type/unsupported.py +++ b/image_creator/os_type/unsupported.py @@ -22,8 +22,8 @@ from image_creator.os_type import OSBase class Unsupported(OSBase): """OS class for unsupported OSs""" - def __init__(self, image, **kargs): - super(Unsupported, self).__init__(image, **kargs) + def __init__(self, image, **kwargs): + super(Unsupported, self).__init__(image, **kwargs) def collect_metadata(self): """Collect metadata about the OS""" diff --git a/image_creator/os_type/windows/__init__.py b/image_creator/os_type/windows/__init__.py index fb12e98b9d5c91ab4689483c3f22db1eea2d8ffb..a7a47289e5d3226d3f2719967611d822f69f8a7e 100644 --- a/image_creator/os_type/windows/__init__.py +++ b/image_creator/os_type/windows/__init__.py @@ -241,8 +241,8 @@ class Windows(OSBase): check=virtio_dir_check, hidden=True) @add_sysprep_param( 'virtio_timeout', 'posint', 900, DESCR['virtio_timeout']) - def __init__(self, image, **kargs): - super(Windows, self).__init__(image, **kargs) + def __init__(self, image, **kwargs): + super(Windows, self).__init__(image, **kwargs) # The commit with the following message was added in # libguestfs 1.17.18 and was backported in version 1.16.11: diff --git a/image_creator/os_type/windows/powershell.py b/image_creator/os_type/windows/powershell.py index 81585b965a28ab7d0eb892ddf0225ebaba2d93f9..010c411cd4c39ac82dc8c2ca5d6b7f2e39d154d6 100644 --- a/image_creator/os_type/windows/powershell.py +++ b/image_creator/os_type/windows/powershell.py @@ -16,7 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """This module hosts Windows PowerShell scripts that need to be injected into -the windows image""" +the windows image.""" # Just a random 16 character long token from image_creator.os_type.windows.vm import RANDOM_TOKEN diff --git a/image_creator/os_type/windows/registry.py b/image_creator/os_type/windows/registry.py index b4f6f516dabad6b3f311bbf0c29b4ea60858a77f..df93236132dde8326d5ed79d70a603067f024b0a 100644 --- a/image_creator/os_type/windows/registry.py +++ b/image_creator/os_type/windows/registry.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This package hosts code for accessing the windows registry""" +"""This package hosts code for manipulating the windows registry.""" from image_creator.util import FatalError @@ -506,7 +506,7 @@ class Registry(object): return old - def _foreach_account(self, write=False, **kargs): + def _foreach_account(self, write=False, **kwargs): """Performs an action on the RID node of a user or a group in the registry, for every user/group found in the userlist/grouplist. If userlist/grouplist is empty, it performs the action on all @@ -539,11 +539,12 @@ class Registry(object): action(hive, name, rid_node) - userlist = kargs['userlist'] if 'userlist' in kargs else None - useraction = kargs['useraction'] if 'useraction' in kargs else None + userlist = kwargs['userlist'] if 'userlist' in kwargs else None + useraction = kwargs['useraction'] if 'useraction' in kwargs else None - grouplist = kargs['grouplist'] if 'grouplist' in kargs else None - groupaction = kargs['groupaction'] if 'groupaction' in kargs else None + grouplist = kwargs['grouplist'] if 'grouplist' in kwargs else None + groupaction = \ + kwargs['groupaction'] if 'groupaction' in kwargs else None if userlist is not None: assert useraction is not None diff --git a/image_creator/os_type/windows/vm.py b/image_creator/os_type/windows/vm.py index 024d19f78241468ca0c20c1fac9ac601dc134cd7..6122b8c28fe2aefc883a3ed681d846d887fec107 100644 --- a/image_creator/os_type/windows/vm.py +++ b/image_creator/os_type/windows/vm.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module provides an interface for launching windows VMs""" +"""This module provides an interface for launching windows VMs.""" import random import subprocess diff --git a/image_creator/os_type/windows/winexe.py b/image_creator/os_type/windows/winexe.py index 990d849c05067253a6c680be61b3e96035e309c8..37df80df087e5a7b99e0122434ab24ebe9e6cc64 100644 --- a/image_creator/os_type/windows/winexe.py +++ b/image_creator/os_type/windows/winexe.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module provides an interface for the WinEXE utility""" +"""This module provides an interface for the WinEXE utility.""" import subprocess import time diff --git a/image_creator/rsync.py b/image_creator/rsync.py index deef53195d94dfd2748f635c016a52241a551980..c37055e3c6b409f55ee1365426058bd170be3038 100644 --- a/image_creator/rsync.py +++ b/image_creator/rsync.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""This module provides an interface for the rsync utility""" +"""This module provides an interface for the rsync utility.""" import subprocess import time diff --git a/image_creator/util.py b/image_creator/util.py index 35d2ed152088657ff223e8e7fcd4188ec692873c..b57f026bd13751bf0a27e6a842b7b9aa0e524be1 100644 --- a/image_creator/util.py +++ b/image_creator/util.py @@ -141,7 +141,10 @@ class QemuNBD(object): self.device = None self.pattern = re.compile('^nbd\d+$') self.modprobe = get_command('modprobe') - self.qemu_nbd = get_command('qemu-nbd') + try: + self.qemu_nbd = get_command('qemu-nbd') + except sh.CommandNotFound: + self.qemu_nbd = None def _list_devices(self): """Returns all the NBD block devices""" @@ -149,6 +152,9 @@ class QemuNBD(object): def connect(self, ro=True): """Connect the image to a free NBD device""" + + assert self.qemu_nbd is not None, "qemu-nbd command not found" + devs = self._list_devices() if len(devs) == 0: # Is nbd module loaded? diff --git a/image_creator/version.py b/image_creator/version.py index 1b458be5d4656ff969c978e8670657eca19a4fa7..61575fa5487a316198ed631b38cd224a9f2a2d42 100644 --- a/image_creator/version.py +++ b/image_creator/version.py @@ -1,8 +1,8 @@ -__version__ = "0.7.3next" +__version__ = "0.7.4next" __version_vcs_info__ = { 'branch': 'develop', - 'revid': 'bcce9f1', - 'revno': 582} + 'revid': '3c1fe78', + 'revno': 596} __version_user_email__ = "skalkoto@grnet.gr" __version_user_name__ = "Nikos Skalkotos" diff --git a/version b/version index 5c8b454f233d0010f5df0cf5a63955755d51b672..7d96b9172bdf4b9984c9ef2d4e5c174a7a4f26ee 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.7.3next +0.7.4next