diff --git a/ChangeLog b/ChangeLog index 8739be1d73434e0e61a6b80f5696727cd68b4892..2b8d47f95f95c0e7f76dced37a94c30cb298b004 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2014-01-08, v0.6 + * Rename the dialog-based application to snf-image-creator and the + non-interactive command line one to snf-mkimage + * Support Windows Server 2012 R2 + * Allow image creation for unsupported media + * Make winexe an optional dependency + * Fix bugs + 2013-10-09, v0.5.3 * Fix a bug in snf-mkimage expert mode were the program could crash if the image did not have any image properties diff --git a/README b/README index a05706d6f0a6d32c27ef5e0de5e6970594d43d65..6d6dacbe069fd84765e7f34d37f1d1706c87b9f1 100644 --- a/README +++ b/README @@ -5,5 +5,5 @@ snf-image-creator is a command line tool for creating OS images to be used with synnefo. It comes in 2 variants: - * snf-image-creator: A non-interactive command line program - * snf-mkimage: A user-friendly dialog-based program + * snf-image-creator: A user-friendly dialog-based program + * snf-mkimage: A non-interactive command line program diff --git a/ci/autopkg_debian.sh b/ci/autopkg_debian.sh index 70eecdd5a4efab52848bf1de664f3d2b9fcf4305..62e678ede0e6a1dd42f34bb8e6cd6b608eb5a3b9 100755 --- a/ci/autopkg_debian.sh +++ b/ci/autopkg_debian.sh @@ -1,18 +1,18 @@ #!/bin/sh set -e -PACKAGES_DIR=$1 +PACKAGES_DIR="$1" shift -TEMP_DIR=$(mktemp -d /tmp/devflow_autopkg_XXXXXXX) +TEMP_DIR="$(mktemp -d /tmp/devflow_autopkg_XXXXXXX)" # Create the packages -devflow-autopkg snapshot -b $TEMP_DIR $@ +devflow-autopkg snapshot -b "$TEMP_DIR" "$@" # MOVE the packages -mkdir -p $PACKAGES_DIR -mv -n $TEMP_DIR/* $PACKAGES_DIR +mkdir -p "$PACKAGES_DIR" +mv -n "$TEMP_DIR"/* "$PACKAGES_DIR" echo "Moved packages to: $(pwd)/$PACKAGES_DIR" diff --git a/ci/make_docs.sh b/ci/make_docs.sh index 61a53d990e41bdcf932b6b341adf442435556fdd..f9ed0e190f8c66ecd99b3f448726b22ad88bb602 100755 --- a/ci/make_docs.sh +++ b/ci/make_docs.sh @@ -1,13 +1,13 @@ #!/bin/sh set -e -DOCS_DIR=$1 +DOCS_DIR="$1" cd docs make html cd - -mkdir -p $DOCS_DIR -mv -n docs/_build/html/* $DOCS_DIR +mkdir -p "$DOCS_DIR" +mv -n docs/_build/html/* "$DOCS_DIR" echo "Moved docs to to: $(pwd)/$DOCS_DIR" diff --git a/docs/conf.py b/docs/conf.py index 5521dad44030936861b161410e2781ca4d2bd84a..e4c80a747e78f3558170649b67e4c989fc219c3e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2012, 2013 GRNET S.A. All rights reserved' # built documents. # # The short X.Y version. -version = '0.5.3' +version = '0.6' # The full version, including alpha/beta/rc tags. -release = '0.5.3' +release = '0.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -238,10 +238,10 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ ('man/snf-image-creator', 'snf-image-creator', - 'Command line image creator for Synnefo', + 'Dialog-based image creator for Synnefo', 'Synnefo development team <synnefo-devel@googlegroups.com>', 1), ('man/snf-mkimage', 'snf-mkimage', - 'Dialog-based image creator for Synnefo', + 'Command line image creator for Synnefo', 'Synnefo development team <synnefo-devel@googlegroups.com>', 1) ] diff --git a/docs/man/snf-image-creator.rst b/docs/man/snf-image-creator.rst index 9d388595c62c9a8f551d8240957d68b6bf52f425..cd1704c6daa7bb624c3d7982e947df996bccb3fd 100644 --- a/docs/man/snf-image-creator.rst +++ b/docs/man/snf-image-creator.rst @@ -6,69 +6,22 @@ snf-image-creator manual page Synopsis -------- -**snf-image-creator** [OPTION] <INPUT MEDIA> +**snf-image-creator** [OPTION] [<INPUT MEDIA>] Description ----------- Create image out of an <INPUT MEDIA>. The <INPUT MEDIA> may be a block device, a regular file that represents a hard disk or \`/' to bundle the host system -itself. +itself. If the <INPUT MEDIA> argument is missing, the user will be asked during +the program initializaton to specify one. Options ------- --a URL, --authentication-url=URL - use this authentication URL when uploading/registering images - --c CLOUD, --cloud=CLOUD - use this saved cloud account to authenticate against a cloud when - uploading/registering images - ---disable-sysprep=SYSPREP - prevent SYSPREP operation from running on the input media - ---enable-sysprep=SYSPREP - run SYSPREP operation on the input media - --f, --force - overwrite output files if they exist - +--version + show program's version number and exit -h, --help show this help message and exit - --m KEY=VALUE, --metadata=KEY=VALUE - add custom KEY=VALUE metadata to the image - ---no-shrink - don't shrink any partition - ---no-sysprep - don't perform any system preparation operation - --o FILE, --outfile=FILE - dump image to FILE - ---public - register image with the storage service as public - ---print-sysprep - print the enabled and disabled system preparation operations for this - input media - --r IMAGENAME, --register=IMAGENAME - register the image with the compute service with name IMAGENAME - --s, --silent - output only errors - --t TOKEN, --token=TOKEN - use this token when uploading/registering images - +-l FILE, --logfile=FILE + log all messages to FILE --tmpdir=DIR create large temporary image files under DIR - --u FILENAME, --upload=FILENAME - save the image to the storage service with remote name FILENAME - ---version - show program's version number and exit - diff --git a/docs/man/snf-mkimage.rst b/docs/man/snf-mkimage.rst index 87bbf107d9221de15fdedd1e05004ef316cdae9a..3d7e6cfd39e95945fbb721692f857126a2d60f36 100644 --- a/docs/man/snf-mkimage.rst +++ b/docs/man/snf-mkimage.rst @@ -6,22 +6,78 @@ snf-mkimage manual page Synopsis -------- -**snf-mkimage** [OPTION] [<INPUT MEDIA>] +**snf-mkimage** [OPTION] <INPUT MEDIA> Description ----------- Create image out of an <INPUT MEDIA>. The <INPUT MEDIA> may be a block device, a regular file that represents a hard disk or \`/' to bundle the host system -itself. If the <INPUT MEDIA> argument is missing, the user will be asked during -the program initializaton to specify one. +itself. Options ------- ---version - show program's version number and exit +-a URL, --authentication-url=URL + use this authentication URL when uploading/registering images + +--allow-unsupported + Proceed with the image creation even if the media is not supported + +-c CLOUD, --cloud=CLOUD + use this saved cloud account to authenticate against a cloud when + uploading/registering images + +--disable-sysprep=SYSPREP + prevent SYSPREP operation from running on the input media + +--enable-sysprep=SYSPREP + run SYSPREP operation on the input media + +-f, --force + overwrite output files if they exist + -h, --help show this help message and exit --l FILE, --logfile=FILE - log all messages to FILE + +-m KEY=VALUE, --metadata=KEY=VALUE + add custom KEY=VALUE metadata to the image + +--no-shrink + don't shrink any partition + +--no-sysprep + don't perform any system preparation operation + +-o FILE, --outfile=FILE + dump image to FILE + +--public + register image with the storage service as public + +--print-syspreps + print the enabled and disabled system preparation operations for this + input media + +--print-sysprep-params + print the needed sysprep parameters for this input media + +-r IMAGENAME, --register=IMAGENAME + register the image with the compute service with name IMAGENAME + +-s, --silent + output only errors + +--sysprep-param=SYSPREP_PARAMS + add KEY=VALUE system preparation parameter + +-t TOKEN, --token=TOKEN + use this token when uploading/registering images + --tmpdir=DIR create large temporary image files under DIR + +-u FILENAME, --upload=FILENAME + save the image to the storage service with remote name FILENAME + +--version + show program's version number and exit + diff --git a/docs/usage.rst b/docs/usage.rst index fc991728c2e3a49acb65c4ece6032898fcba4017..6e436a705f5f915c02fc984bb402dfcd8ea14235 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -3,8 +3,8 @@ Usage snf-image-creator comes in 2 variants: - * snf-image-creator: A non-interactive command line program - * snf-mkimage: A user-friendly dialog-based program + * snf-mkimage: A non-interactive command line program + * snf-image-creator: A user-friendly dialog-based program Both expect the input media as first argument. The input media may be a local file, a block device or *"/"* if you want to create an image out of the running @@ -13,12 +13,12 @@ system (see `host bundling operation`_). Non-interactive version ======================= -snf-image-creator receives the following options: +snf-mkimage receives the following options: .. code-block:: console - $ snf-image-creator --help - Usage: snf-image-creator [options] <input_media> + $ snf-mkimage --help + Usage: snf-mkimage [options] <input_media> Options: --version show program's version number and exit @@ -70,7 +70,7 @@ registered as *private*. Only the user that registers the image can create VM's out of it. If you want the image to be visible by other user too, use the *--public* option. -By default, before extracting the image, snf-image-creator will perform a +By default, before extracting the image, snf-mkimage will perform a number of system preparation operations on the snapshot of the media and will shrink the last partition found. Both actions can be disabled by specifying *--no-sysprep* and *--no-shrink* respectively. @@ -81,15 +81,15 @@ input media. The user can enable or disable specific *syspreps*, using *-{enable,disable}-sysprep* options. The user may specify those options multiple times. -Running *snf-image-creator* with *--print-sysprep* on a raw file that hosts a +Running *snf-mkimage* with *--print-sysprep* on a raw file that hosts a debian system, we print the following output: .. _sysprep: .. code-block:: console - $ snf-image-creator --print-sysprep ubuntu.raw - snf-image-creator 0.3 + $ snf-mkimage --print-syspreps ubuntu.raw + snf-image-creator 0.6 ===================== Examining source media `ubuntu_hd.raw' ... looks like an image file Snapshotting media source ... done @@ -150,17 +150,17 @@ removed, you should use *--enable-sysprep* option like this: .. code-block:: console - $ snf-image-creator --enable-sysprep cleanup-mail --enable-sysprep remove-user-accounts ... + $ snf-mkimage --enable-sysprep cleanup-mail --enable-sysprep remove-user-accounts ... Dialog-based version ==================== -*snf-mkimage* receives the following options: +*snf-image-creator* receives the following options: .. code-block:: console - $ snf-mkimage --help - Usage: snf-mkimage [options] [<input_media>] + $ snf-image-creator --help + Usage: snf-image-creator [options] [<input_media>] Options: --version show program's version number and exit @@ -179,12 +179,12 @@ button to create an image out of the running system (see `Host bundling operation`_). After the input media is examined and the program is initialized, the user will -be given the choice to run *snf-mkimage* in *wizard* or *expert* mode. +be given the choice to run *snf-image-creator* in *wizard* or *expert* mode. Wizard mode ----------- -When *snf-mkimage* runs in *wizard* mode, the user is just asked to provide the +When *snf-image-creator* runs in *wizard* mode, the user is just asked to provide the following basic information: * Cloud: The cloud account to use to upload and register the resulting image @@ -278,13 +278,13 @@ You will be able to boot your installed OS and make any changes you want $ sudo kvm -m 1G -boot c -drive file=ubuntu.raw,format=raw,cache=none,if=virtio -After you're done, you may use *snf-mkimage* as root to create and upload the +After you're done, you may use *snf-image-creator* as root to create and upload the image: .. code-block:: console $ sudo -s - $ snf-mkimage ubuntu.raw + $ snf-image-creator ubuntu.raw In the first screen you will be asked to choose if you want to run the program in *Wizard* or *Expert* mode. Choose *Wizard*. diff --git a/image_creator/dialog_main.py b/image_creator/dialog_main.py index 7895287c4febae036fd0a7b975c04b624c615226..2a56a1c2a0238a39c6d1516a7e16cc2dd1573d8f 100644 --- a/image_creator/dialog_main.py +++ b/image_creator/dialog_main.py @@ -101,6 +101,25 @@ def create_image(d, media, out, tmp): "image": image, "metadata": metadata} + if image.is_unsupported(): + + session['excluded_tasks'] = [-1] + session['task_metadata'] = ["EXCLUDE_ALL_TASKS"] + + msg = "The system on the input media is not supported." \ + "\n\nReason: %s\n\n" \ + "We highly recommend not to create an image out of this, " \ + "since the image won't be cleaned up and you will not be " \ + "able to configure it during the deployment. Press <YES> if " \ + "you still want to continue with the image creation process." \ + % image._unsupported + + if not d.yesno(msg, width=WIDTH, defaultno=1, height=12): + main_menu(session) + + d.infobox("Thank you for using snf-image-creator. Bye", width=53) + return 0 + msg = "snf-image-creator detected a %s system on the input media. " \ "Would you like to run a wizard to assist you through the " \ "image creation process?\n\nChoose <Wizard> to run the wizard," \ diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py index 25fcb6c3089e99048bd4071f6d99d65bf235743e..4f24b1e008420faf53d9353665f82c4a374249e9 100644 --- a/image_creator/dialog_menu.py +++ b/image_creator/dialog_menu.py @@ -217,13 +217,26 @@ def register_image(session): "register it", width=SMALL_WIDTH) return False + name = "" + description = session['metadata']['DESCRIPTION'] if 'DESCRIPTION' in \ + session['metadata'] else "" + while 1: - (code, answer) = d.inputbox("Please provide a registration name:", - width=WIDTH) + fields = [ + ("Registration name:", name, 60), + ("Description (optional):", description, 80)] + + (code, output) = d.form( + "Please provide the following registration info:", height=11, + width=WIDTH, form_height=2, fields=fields) + if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): return False - name = answer.strip() + name, description = output + name = name.strip() + description = description.strip() + if len(name) == 0: d.msgbox("Registration name cannot be empty", width=SMALL_WIDTH) continue @@ -238,6 +251,7 @@ def register_image(session): break + session['metadata']['DESCRIPTION'] = description metadata = {} metadata.update(session['metadata']) if 'task_metadata' in session: @@ -583,6 +597,12 @@ def delete_properties(session): def exclude_tasks(session): """Exclude specific tasks from running during image deployment""" d = session['dialog'] + image = session['image'] + + if image.is_unsupported(): + d.msgbox("Image deployment configuration is disabled for unsupported " + "images.", width=SMALL_WIDTH) + return False index = 0 displayed_index = 1 diff --git a/image_creator/image.py b/image_creator/image.py index 7eb8ad84d567bfc602bc6a01afee5f03ba9ffcb8..aebd2c9ff3de734f76b0831598232af335340c8c 100644 --- a/image_creator/image.py +++ b/image_creator/image.py @@ -88,16 +88,26 @@ class Image(object): self.out.output('Inspecting Operating System ...', False) roots = self.g.inspect_os() - if len(roots) == 0: - raise FatalError("No operating system found") - if len(roots) > 1: - raise FatalError("Multiple operating systems found." - "We only support images with one OS.") + + if len(roots) == 0 or len(roots) > 1: + self.root = None + self.ostype = "unsupported" + self.distro = "unsupported" + self.guestfs_device = '/dev/sda' + self.size = self.g.blockdev_getsize64(self.guestfs_device) + + if len(roots) > 1: + reason = "Multiple operating systems found on the media." + else: + reason = "Unable to detect any operating system on the media." + + self.set_unsupported(reason) + return + self.root = roots[0] - self.guestfs_device = self.g.part_to_dev(self.root) + self.meta['PARTITION_TABLE'] = self.g.part_get_parttype('/dev/sda') + self.guestfs_device = '/dev/sda' # self.g.part_to_dev(self.root) self.size = self.g.blockdev_getsize64(self.guestfs_device) - self.meta['PARTITION_TABLE'] = \ - self.g.part_get_parttype(self.guestfs_device) self.ostype = self.g.inspect_get_type(self.root) self.distro = self.g.inspect_get_distro(self.root) @@ -105,6 +115,20 @@ class Image(object): 'found a(n) %s system' % self.ostype if self.distro == "unknown" else self.distro) + # Inspect the OS + self.os.inspect() + + def set_unsupported(self, reason): + """Flag this image us ansupported""" + + self._unsupported = reason + self.meta['UNSUPPORTED'] = reason + self.out.warn('Media is not supported. Reason: %s' % reason) + + def is_unsupported(self): + """Returns if this image is unsupported""" + return hasattr(self, '_unsupported') + def enable_guestfs(self): """Enable the guestfs handler""" @@ -264,6 +288,10 @@ class Image(object): self.out.output("Shrinking image (this may take a while) ...", False) + if self.is_unsupported(): + self.out.warn("Shrinking is disabled for unsupported images") + return self.size + sector_size = self.g.blockdev_getss(self.guestfs_device) last_part = None diff --git a/image_creator/main.py b/image_creator/main.py index c3281460e0bdeffe2fda1f74cf5e731ba4d4a3e9..ccf51258860d6f9abbae5ca816cb185601e03d45 100644 --- a/image_creator/main.py +++ b/image_creator/main.py @@ -50,6 +50,7 @@ import optparse import StringIO import signal import json +import textwrap def check_writable_dir(option, opt_str, value, parser): @@ -140,6 +141,10 @@ def parse_options(input_args): help="register image with the cloud as public", action="store_true") + parser.add_option("--allow-unsupported", dest="allow_unsupported", + help="Proceed with the image creation even if the media " + "is not supported", default=False, action="store_true") + parser.add_option("--tmpdir", dest="tmp", type="string", default=None, help="create large temporary image files under DIR", metavar="DIR") @@ -273,6 +278,16 @@ def image_creator(): image = disk.get_image(snapshot, sysprep_params=options.sysprep_params) + if image.is_unsupported() and not options.allow_unsupported: + raise FatalError( + "The media seems to be unsupported.\n\n" + + textwrap.fill("To create an image from an unsupported media, " + "you'll need to use the`--allow-unsupported' " + "command line option. Using this is highly " + "discouraged, since the resulting image will " + "not be cleared out of sensitive data and will " + "not get customized during the deployment.")) + for sysprep in options.disabled_syspreps: image.os.disable_sysprep(image.os.get_sysprep_by_name(sysprep)) @@ -298,6 +313,9 @@ def image_creator(): size = options.shrink and image.shrink() or image.size metadata.update(image.meta) + if image.is_unsupported(): + metadata['EXCLUDE_ALL_TASKS'] = "yes" + # Add command line metadata to the collected ones... metadata.update(options.metadata) @@ -384,6 +402,10 @@ def main(): sys.exit(ret) except FatalError as e: colored = sys.stderr.isatty() + warning = \ + "The name of the executable has changed. If you want to use the " \ + "user-friendly dialog-based program try `snf-image-creator'" + SimpleOutput(colored).warn(warning) SimpleOutput(colored).error(e) sys.exit(1) diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py index 529856996516defa28dd9f4e5f9cdd5a07ac0e0a..ce8413034d0601f554131f655d0fa45c4a21406d 100644 --- a/image_creator/os_type/__init__.py +++ b/image_creator/os_type/__init__.py @@ -144,6 +144,22 @@ class OSBase(object): except RuntimeError: self._scrub_support = False + def inspect(self): + """Inspect the media to check if it is supported""" + + if self.image.is_unsupported(): + return + + self.out.output('Running OS inspection:') + try: + if not self.mount(readonly=True): + raise FatalError("Unable to mount the media read-only") + self._do_inspect() + finally: + self.umount() + + self.out.output() + def collect_metadata(self): """Collect metadata about the OS""" try: @@ -239,7 +255,7 @@ class OSBase(object): return for name, param in self.needed_sysprep_params.items(): - self.out.output("\t%s (%s): %s" % + self.out.output("\t%s [%s]: %s" % (param.description, name, self.sysprep_params[name] if name in self.sysprep_params else "(none)")) @@ -247,12 +263,17 @@ class OSBase(object): def do_sysprep(self): """Prepare system for image creation.""" + self.out.output('Preparing system for image creation:') + + if self.image.is_unsupported(): + self.out.warn( + "System preparation is disabled for unsupported media") + return + try: if not self.mount(readonly=False): raise FatalError("Unable to mount the media read-write") - self.out.output('Preparing system for image creation:') - enabled = [task for task in self.list_syspreps() if task.enabled] size = len(enabled) @@ -351,10 +372,20 @@ class OSBase(object): if has_ftype(f, ftype): action(full_path) + def _do_inspect(self): + """helper method for inspect""" + pass + def _do_collect_metadata(self): """helper method for collect_metadata""" - self.meta['ROOT_PARTITION'] = \ - "%d" % self.image.g.part_to_partnum(self.root) + + try: + self.meta['ROOT_PARTITION'] = \ + "%d" % self.image.g.part_to_partnum(self.root) + except RuntimeError: + self.out.warn("Unable to identify the partition number from root " + "partition: %s" % self.root) + self.meta['OSFAMILY'] = self.image.g.inspect_get_type(self.root) self.meta['OS'] = self.image.g.inspect_get_distro(self.root) if self.meta['OS'] == "unknown": diff --git a/image_creator/os_type/freebsd.py b/image_creator/os_type/freebsd.py index 5f814818eb8a243a26b141be7d70c9284ab60446..145be3e4080e13964efe4f5d899acfbb16df6a12 100644 --- a/image_creator/os_type/freebsd.py +++ b/image_creator/os_type/freebsd.py @@ -82,6 +82,18 @@ class Freebsd(Unix): self.out.warn("No passworded users found!") del self.meta['USERS'] + def _do_inspect(self): + """Run various diagnostics to check if media is supported""" + + self.out.output('Checking partition table type...', False) + ptype = self.image.g.part_get_parttype(self.image.guestfs_device) + if ptype != 'gpt': + self.out.warn("partition table type is: `%s'" % ptype) + self.image.set_unsupported( + 'On FreeBSD only GUID partition tables are supported') + else: + self.out.success(ptype) + def _get_passworded_users(self): """Returns a list of non-locked user accounts""" users = [] diff --git a/image_creator/os_type/linux.py b/image_creator/os_type/linux.py index 31b660f01fcedf275f336671073109bc707917ff..53e8a5a8ba79416f97ae693cae1dc23f613dc6a6 100644 --- a/image_creator/os_type/linux.py +++ b/image_creator/os_type/linux.py @@ -116,6 +116,9 @@ class Linux(Unix): self.image.g.write('/etc/shadow', "\n".join(shadow) + '\n') + # Remove backup file for /etc/shadow + self.image.g.rm_rf('/etc/shadow-') + @sysprep('Fixing acpid powerdown action') def fix_acpid(self): """Replace acpid powerdown action scripts to immediately shutdown the @@ -298,6 +301,20 @@ class Linux(Unix): return orig, dev, mpoint + def _do_inspect(self): + """Run various diagnostics to check if media is supported""" + + self.out.output( + 'Checking if the media contains logical volumes (LVM)...', False) + + has_lvm = True if len(self.image.g.lvs()) else False + + if has_lvm: + self.out.output() + self.image.set_unsupported('The media contains logical volumes') + else: + self.out.success('no') + def _do_collect_metadata(self): """Collect metadata about the OS""" super(Linux, self)._do_collect_metadata() diff --git a/image_creator/os_type/unsupported.py b/image_creator/os_type/unsupported.py new file mode 100644 index 0000000000000000000000000000000000000000..9b27b7bea17fd4125fe389fdf68edcae308fcbee --- /dev/null +++ b/image_creator/os_type/unsupported.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 GRNET S.A. All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and +# documentation are those of the authors and should not be +# interpreted as representing official policies, either expressed +# or implied, of GRNET S.A. + +"""This module hosts code to handle unknown OSs.""" + +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 collect_metadata(self): + """Collect metadata about the OS""" + self.out.warn("Unable to collect metadata for unsupported media") + + def _do_mount(self, readonly): + """Mount partitions in corrent order""" + self.out.warn('not supported on this media.') + return False + +# vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/windows.py b/image_creator/os_type/windows.py index 96efdab53e2140e5c65d4c3d1e0c95e0aefa21ee..64757a6f5f93324bde5d1bf538d91015a67a3c63 100644 --- a/image_creator/os_type/windows.py +++ b/image_creator/os_type/windows.py @@ -49,9 +49,17 @@ import random import string import subprocess import struct +import re # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx KMS_CLIENT_SETUP_KEYS = { + "Windows 8.1 Professional": "GCRJD-8NW9H-F2CDX-CCM8D-9D6T9", + "Windows 8.1 Professional N": "HMCNV-VVBFX-7HMBH-CTY9B-B4FXY", + "Windows 8.1 Enterprise": "MHF9N-XY6XB-WVXMC-BTDCT-MKKG7", + "Windows 8.1 Enterprise N": "TT4HM-HN7YT-62K67-RGRQJ-JFFXW", + "Windows Server 2012 R2 Server Standard": "D2N9P-3P6X9-2R39C-7RTCD-MDVJX", + "Windows Server 2012 R2 Datacenter": "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9", + "Windows Server 2012 R2 Essentials": "KNC87-3J2TX-XB4WP-VCPJV-M4FWM", "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4", "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ", "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7", @@ -111,6 +119,10 @@ class Windows(OSBase): 'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT) @add_sysprep_param( 'connection_retries', int, 5, "Connection Retries", _POSINT) + @add_sysprep_param( + 'smp', int, 1, "Number of CPUs for the helper VM", _POSINT) + @add_sysprep_param( + 'mem', int, 1024, "Virtual RAM size for the helper VM (MiB)", _POSINT) @add_sysprep_param('password', str, None, 'Image Administrator Password') def __init__(self, image, **kargs): super(Windows, self).__init__(image, **kargs) @@ -131,6 +143,11 @@ class Windows(OSBase): raise FatalError( 'For windows support libguestfs 1.16.11 or above is required') + # Check if winexe is installed + if not WinEXE.is_installed(): + raise FatalError( + "For windows support `Winexe' needs to be installed") + device = self.image.g.part_to_dev(self.root) self.last_part_num = self.image.g.part_list(device)[-1]['part_num'] @@ -194,7 +211,7 @@ class Windows(OSBase): self._guest_exec( r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l") - @sysprep('Executing Sysprep on the image (may take more that 10 minutes)') + @sysprep('Executing Sysprep on the image (may take more that 10 min)') def microsoft_sysprep(self): """Run the Microsoft System Preparation Tool. This will remove system-specific data and will make the image ready to be deployed. @@ -248,9 +265,14 @@ class Windows(OSBase): # The maximum number of reclaimable bytes is: xxxx MB # if line.find('reclaimable') >= 0: - querymax = line.split(':')[1].split()[0].strip() - assert querymax.isdigit(), \ - "Number of reclaimable bytes not a number" + answer = line.split(':')[1].strip() + m = re.search('(\d+) MB', answer) + if m: + querymax = m.group(1) + else: + FatalError( + "Unexpected output for `shrink querymax' command: %s" % + line) if querymax is None: FatalError("Error in shrinking! " @@ -265,7 +287,7 @@ class Windows(OSBase): querymax -= 100 if querymax < 0: - self.out.warn("Not enought available space to shrink the image!") + self.out.warn("Not enough available space to shrink the image!") return self.out.output("\tReclaiming %dMB ..." % querymax) @@ -329,7 +351,7 @@ class Windows(OSBase): monitorfd, monitor = tempfile.mkstemp() os.close(monitorfd) vm = _VM(self.image.device, monitor, self.sysprep_params) - self.out.success("started (console on vnc display: %d)." % + self.out.success("started (console on VNC display: %d)" % vm.display) self.out.output("Waiting for OS to boot ...", False) @@ -782,8 +804,8 @@ class _VM(object): args.extend(needed_args) args.extend([ - '-smp', '1', '-m', '1024', '-drive', - 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk, + '-smp', str(self.params['smp']), '-m', str(self.params['mem']), + '-drive', 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk, '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0', '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(), '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial, diff --git a/image_creator/output/dialog.py b/image_creator/output/dialog.py index f2b10e84a7697ffa04fa21220ce57ca928a32b05..a82b986d7334bac54774734634917143e07b1b26 100644 --- a/image_creator/output/dialog.py +++ b/image_creator/output/dialog.py @@ -131,7 +131,9 @@ class InfoBoxOutput(Output): self.msg += "%s%s" % (msg, nl) # If output is long, only output the last lines that fit in the box lines = self.msg.splitlines() - h = self.height + # The height of the active region is 2 lines shorter that the height of + # the dialog + h = self.height - 2 display = self.msg if len(lines) <= h else "\n".join(lines[-h:]) self.d.infobox(display, title=self.title, height=self.height, width=self.width) diff --git a/image_creator/version.py b/image_creator/version.py index 351ff305c1fc972910ab67cd16d58d05d142be0f..069265b5bd2fa3f944f0a37dbf4c6bbf3a6ec5ca 100644 --- a/image_creator/version.py +++ b/image_creator/version.py @@ -1,8 +1,8 @@ -__version__ = "0.5.3" +__version__ = "0.6" __version_vcs_info__ = { - 'branch': 'hotfix-0.5.3', - 'revid': 'dc0ecb2', - 'revno': 402} + 'branch': 'master', + 'revid': '37d1ea1', + 'revno': 426} __version_user_email__ = "skalkoto@grnet.gr" __version_user_name__ = "Nikos Skalkotos" diff --git a/image_creator/winexe.py b/image_creator/winexe.py index ebac40931b0671cc08fea8518d58411b13faeb1c..b6c00bc3a5c30a96919d9bb40bd0e9282bb3042e 100644 --- a/image_creator/winexe.py +++ b/image_creator/winexe.py @@ -38,6 +38,7 @@ import subprocess import time import signal +import distutils from image_creator.util import FatalError @@ -50,6 +51,10 @@ class WinexeTimeout(FatalError): class WinEXE: """Wrapper class for the winexe command""" + @staticmethod + def is_installed(program='winexe'): + return distutils.spawn.find_executable(program) is not None + def __init__(self, username, password, hostname, program='winexe'): self._host = hostname self._user = username diff --git a/setup.py b/setup.py index 3c673663109a1bf9c128a7fa57a7c522865ff11c..3a6dd27063970093d3d8818ee078ae5882e551dc 100755 --- a/setup.py +++ b/setup.py @@ -52,8 +52,8 @@ setup( install_requires=['sh', 'ansicolors', 'progress>=1.0.2'], entry_points={ 'console_scripts': [ - 'snf-image-creator = image_creator.main:main', - 'snf-mkimage = image_creator.dialog_main:main'] + 'snf-mkimage = image_creator.main:main', + 'snf-image-creator = image_creator.dialog_main:main'] } ) # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/version b/version index be14282b7fffb9ba95d51c6546ed9816dc8f3ff8..5a2a5806df6e909afe3609b5706cb1012913ca0e 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.3 +0.6