diff --git a/image_creator/dialog_main.py b/image_creator/dialog_main.py index 4c456a94e222aae7dd9be41a2f408ad782447bec..cf9d4b07fb8cc841804cac2f9385aff75a894db9 100644 --- a/image_creator/dialog_main.py +++ b/image_creator/dialog_main.py @@ -21,11 +21,13 @@ snf-image-creator program. The main function will create a dialog where the user is asked if he wants to use the program in expert or wizard mode. """ +from __future__ import unicode_literals + import dialog import sys import os import signal -import optparse +import argparse import types import termios import traceback @@ -278,27 +280,22 @@ def main(): sys.stderr.write("Error: You must run %s as root\n" % PROGNAME) sys.exit(2) - usage = "Usage: %prog [options] [<input_media>]" - parser = optparse.OptionParser(version=version, usage=usage) - parser.add_option("-l", "--logfile", type="string", dest="logfile", - default=None, help="log all messages to FILE", - metavar="FILE") - parser.add_option("--no-snapshot", dest="snapshot", default=True, - help="don't snapshot the input media. (THIS IS " - "DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)", - action="store_false") - parser.add_option("--syslog", dest="syslog", default=False, - help="log to syslog", action="store_true") - parser.add_option("--tmpdir", type="string", dest="tmp", default=None, - help="create large temporary image files under DIR", - metavar="DIR") - - opts, args = parser.parse_args(sys.argv[1:]) - - if len(args) > 1: - parser.error("Wrong number of arguments") - - media = args[0] if len(args) == 1 else None + description = "Dialog-based tool for creating OS images" + parser = argparse.ArgumentParser(version=version, description=description) + parser.add_argument("-l", "--logfile", dest="logfile", metavar="FILE", + default=None, help="log all messages to FILE") + parser.add_argument("--no-snapshot", dest="snapshot", default=True, + help="don't snapshot the input media. (THIS IS " + "DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)", + action="store_false") + parser.add_argument("--syslog", dest="syslog", default=False, + help="log to syslog", action="store_true") + parser.add_argument("--tmpdir", dest="tmp", default=None, metavar="DIR", + help="create large temporary image files under DIR") + parser.add_argument("source", metavar="SOURCE", default=None, nargs='?', + help="Image file, block device or /") + + opts = parser.parse_args() if opts.tmp is not None and not os.path.isdir(opts.tmp): parser.error("Directory: `%s' specified with --tmpdir is not valid" @@ -314,7 +311,7 @@ def main(): # Save the terminal attributes attr = termios.tcgetattr(sys.stdin.fileno()) try: - ret = dialog_main(media, logfile=logfile, tmpdir=opts.tmp, + ret = dialog_main(opts.source, logfile=logfile, tmpdir=opts.tmp, snapshot=opts.snapshot, syslog=opts.syslog) finally: # Restore the terminal attributes. If an error occurs make sure diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py index ff83652b32490a9b4ed277ce87efcbe512b58371..077d4eeda7db06a3ea7fbba4af2b95cde1e1d79b 100644 --- a/image_creator/dialog_menu.py +++ b/image_creator/dialog_menu.py @@ -287,8 +287,8 @@ def register_image(session): # Upload metadata file out.info("Uploading metadata file ...", False) - metastring = unicode(json.dumps(cloud['registered'], - indent=4, ensure_ascii=False)) + metastring = json.dumps(cloud['registered'], indent=4, + ensure_ascii=False) kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), remote_path="%s.meta" % remote, diff --git a/image_creator/dialog_util.py b/image_creator/dialog_util.py index fe9001e18a9ce0e18701772b96c6abb0c3658d29..987bc99a8fa1028374c584cb8798c881e6b4c4be 100644 --- a/image_creator/dialog_util.py +++ b/image_creator/dialog_util.py @@ -148,8 +148,8 @@ def extract_metadata_string(session): for key in session['task_metadata']: metadata[key] = 'yes' - return unicode(json.dumps({'properties': metadata, - 'disk-format': 'diskdump'}, ensure_ascii=False)) + return json.dumps({'properties': metadata, 'disk-format': 'diskdump'}, + ensure_ascii=False) def extract_image(session): diff --git a/image_creator/main.py b/image_creator/main.py index 13be9ab3f1e24cc48fe1bba47e9e81fed2c72c68..ecc2a3fd35788342829d27cbea7ec458ab42a9e6 100644 --- a/image_creator/main.py +++ b/image_creator/main.py @@ -20,6 +20,8 @@ snf-image-creator program. """ +from __future__ import unicode_literals + from image_creator import __version__ as version from image_creator.disk import Disk from image_creator.util import FatalError @@ -30,7 +32,7 @@ from image_creator.output.syslog import SyslogOutput from image_creator.kamaki_wrapper import Kamaki, ClientError, CONTAINER import sys import os -import optparse +import argparse import StringIO import signal import json @@ -41,136 +43,167 @@ import time import re -def check_writable_dir(option, opt_str, value, parser): +class CheckWritableDir(argparse.Action): """Check if a directory is writable""" - dirname = os.path.dirname(value) - name = os.path.basename(value) - if dirname and not os.path.isdir(dirname): - raise FatalError("`%s' is not an existing directory" % dirname) - - if not name: - raise FatalError("`%s' is not a valid file name" % dirname) - - setattr(parser.values, option.dest, value) - - -def parse_options(input_args): - """Parse input parameters""" - usage = "Usage: %prog [options] <input_media>" - parser = optparse.OptionParser(version=version, usage=usage) - - parser.add_option("-a", "--authentication-url", dest="url", type="string", - default=None, help="use this authentication URL when " - "uploading/registering images") - - parser.add_option("--add-timestamp", dest="timestamp", default=False, - help="Add a timestamp when outputting messages", - 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("-c", "--cloud", dest="cloud", type="string", - default=None, help="use this saved cloud account to " - "authenticate against a cloud when " - "uploading/registering images") - - parser.add_option("--container", dest="container", type="string", - default=CONTAINER, help="Upload files to CONTAINER " - "[default: %s]" % CONTAINER) - - parser.add_option("--disable-sysprep", dest="disabled_syspreps", - help="prevent SYSPREP operation from running on the " - "input media", default=[], action="append", - metavar="SYSPREP") - - parser.add_option("--enable-sysprep", dest="enabled_syspreps", default=[], - help="run SYSPREP operation on the input media", - action="append", metavar="SYSPREP") + def __call__(self, parser, namespace, value, option): - parser.add_option("-f", "--force", dest="force", default=False, - action="store_true", - help="overwrite output files if they exist") + if getattr(namespace, self.dest): + raise argparse.ArgumentError(self, + "Argument defined multiple times") - parser.add_option("--host-run", dest="host_run", default=[], - help="mount the media in the host and run a script " - "against the guest media. This option may be defined " - "multiple times. The script's working directory will be " - "the guest's root directory. BE CAREFUL! DO NOT USE " - "ABSOLUTE PATHS INSIDE THE SCRIPT! YOU MAY HARM YOUR " - "SYSTEM!", metavar="SCRIPT", action="append") + dirname = os.path.dirname(value) + name = os.path.basename(value) - parser.add_option("--install-virtio", dest="virtio", type="string", - help="install VirtIO drivers hosted under DIR " - "(Windows only)", metavar="DIR") + if dirname and not os.path.isdir(dirname): + raise argparse.ArgumentError( + self, "`%s' is not an existing directory" % dirname) + if not os.access(dirname, os.W_OK): + raise argparse.ArgumentError( + self, "`%s' is not writable" % dirname) + if not name: + raise argparse.ArgumentError( + self, "`%s' is not a valid file name" % dirname) - parser.add_option("-m", "--metadata", dest="metadata", default=[], - help="add custom KEY=VALUE metadata to the image", - action="append", metavar="KEY=VALUE") + setattr(namespace, self.dest, value.decode(sys.stdin.encoding)) - parser.add_option("--no-snapshot", dest="snapshot", default=True, - help="don't snapshot the input media. (THIS IS " - "DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)", - action="store_false") - parser.add_option("--no-sysprep", dest="sysprep", default=True, - help="don't perform any system preparation operation", - action="store_false") +class AddKeyValue(argparse.Action): + """Add key value options""" + def __call__(self, parser, namespace, value, option): - parser.add_option("-o", "--outfile", type="string", dest="outfile", - default=None, action="callback", metavar="FILE", - callback=check_writable_dir, help="dump image to FILE") + dest = getattr(namespace, self.dest) + if not dest: + dest = {} - parser.add_option("--print-metadata", dest="print_metadata", default=False, - help="print the detected image metadata", - action='store_true') - - parser.add_option("--print-syspreps", dest="print_syspreps", default=False, - help="print the enabled and disabled system preparation " - "operations for this input media", action="store_true") - - parser.add_option("--print-sysprep-params", dest="print_sysprep_params", - default=False, action="store_true", - help="print the defined system preparation parameters " - "for this input media") - - parser.add_option("--public", dest="public", default=False, - help="register image with the cloud as public", - action="store_true") - - parser.add_option("-r", "--register", dest="register", type="string", - default=False, metavar="IMAGENAME", - help="register the image with a cloud as IMAGENAME") - - parser.add_option("-s", "--silent", dest="silent", default=False, - help="output only errors", action="store_true") - - parser.add_option('--syslog', dest="syslog", default=False, - help="log to syslog", action="store_true") - - parser.add_option("--sysprep-param", dest="sysprep_params", default=[], - help="add KEY=VALUE system preparation parameter", - action="append") - - parser.add_option("-t", "--token", dest="token", type="string", - default=None, help="use this authentication token when " - "uploading/registering images") - - parser.add_option("--tmpdir", dest="tmp", type="string", default=None, - help="create large temporary image files under DIR", - metavar="DIR") + value = value.decode(sys.stdin.encoding) + try: + key, val = value.split('=', 1) + except ValueError: + raise argparse.ArgumentError( + self, "`%s' is not in KEY=VALUE format." % value) + dest[key.upper()] = val - parser.add_option("-u", "--upload", dest="upload", type="string", - default=False, metavar="FILENAME", - help="upload the image to the cloud with name FILENAME") + setattr(namespace, self.dest, dest) - options, args = parser.parse_args(input_args) - if len(args) != 1: - parser.error('Wrong number of arguments') +def parse_options(): + """Parse input parameters""" + description = "Command-line tool for creating OS images" + parser = argparse.ArgumentParser(version=version, description=description) + + parser.add_argument("source", metavar="SOURCE", + help="Image file, block device or /") + parser.add_argument( + "-a", "--authentication-url", dest="url", default=None, + help="use this authentication URL when uploading/registering images") + + parser.add_argument( + "--add-timestamp", dest="timestamp", default=False, + help="Add a timestamp when outputting messages", action="store_true") + + parser.add_argument( + "--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_argument( + "-c", "--cloud", dest="cloud", default=None, + help="use this saved cloud account to authenticate against a cloud " + "when uploading/registering images") + + parser.add_argument( + "--container", dest="container", default=CONTAINER, + help="Upload files to CONTAINER [default: %s]" % CONTAINER) + + parser.add_argument( + "--disable-sysprep", dest="disabled_syspreps", + help="prevent SYSPREP operation from running on the input media", + default=[], action="append", metavar="SYSPREP") + + parser.add_argument( + "--enable-sysprep", dest="enabled_syspreps", default=[], + help="run SYSPREP operation on the input media", action="append", + metavar="SYSPREP") + + parser.add_argument( + "-f", "--force", dest="force", default=False, action="store_true", + help="overwrite output files if they exist") + + parser.add_argument( + "--host-run", dest="host_run", default=[], + help="mount the media in the host and run a script against the guest " + "media. This option may be defined multiple times. The script's " + "working directory will be the guest's root directory. BE " + "CAREFUL! DO NOT USE ABSOLUTE PATHS INSIDE THE SCRIPT! YOU MAY " + "HARM YOUR SYSTEM!", + metavar="SCRIPT", action="append") + + parser.add_argument( + "--install-virtio", dest="virtio", metavar="DIR", + help="install VirtIO drivers hosted under DIR (Windows only)") + + parser.add_argument( + "-m", "--metadata", dest="metadata", default={}, action=AddKeyValue, + help="add custom KEY=VALUE metadata to the image", metavar="KEY=VALUE") + + parser.add_argument( + "--no-snapshot", dest="snapshot", default=True, + help="don't snapshot the input media. (THIS IS DANGEROUS AS IT WILL " + "ALTER THE ORIGINAL MEDIA!!!)", action="store_false") + + parser.add_argument("--no-sysprep", dest="sysprep", default=True, + help="don't perform any system preparation operation", + action="store_false") + + parser.add_argument("-o", "--outfile", dest="outfile", default=None, + action=CheckWritableDir, metavar="FILE", + help="dump image to FILE") + + parser.add_argument("--print-metadata", dest="print_metadata", + action='store_true', default=False, + help="print the detected image metadata") + + parser.add_argument( + "--print-syspreps", dest="print_syspreps", default=False, + help="print the enabled and disabled system preparation operations " + "for this input media", action="store_true") + + parser.add_argument( + "--print-sysprep-params", dest="print_sysprep_params", default=False, + help="print the defined system preparation parameters for this input " + "media", action="store_true") + + parser.add_argument("--public", dest="public", default=False, + help="register image with the cloud as public", + action="store_true") + + parser.add_argument("-r", "--register", dest="register", default=False, + metavar="IMAGENAME", + help="register the image with a cloud as IMAGENAME") + + parser.add_argument("-s", "--silent", dest="silent", default=False, + help="output only errors", action="store_true") + + parser.add_argument('--syslog', dest="syslog", default=False, + help="log to syslog", action="store_true") + + parser.add_argument("--sysprep-param", dest="sysprep_params", default={}, + help="add KEY=VALUE system preparation parameter", + action=AddKeyValue) + + parser.add_argument( + "-t", "--token", dest="token", default=None, + help="use this authentication token when uploading/registering images") + + parser.add_argument("--tmpdir", dest="tmp", default=None, metavar="DIR", + help="create large temporary image files under DIR") + + parser.add_argument( + "-u", "--upload", dest="upload", default=None, metavar="FILENAME", + help="upload the image to the cloud with name FILENAME") + + options = parser.parse_args() - options.source = args[0] if not os.path.exists(options.source): parser.error("Input media `%s' is not accessible" % options.source) @@ -188,32 +221,23 @@ def parse_options(input_args): parser.error("The directory `%s' specified with --tmpdir is not valid" % options.tmp) + # Convert input attributes to unicode + for opt in ('url', 'cloud', 'container', 'outfile', 'register', 'token', + 'tmp', 'upload', 'virtio'): + attr = getattr(options, opt) + if attr: + setattr(options, opt, attr.decode(sys.stdin.encoding)) + + options.host_run = [h.decode(sys.stdin.encoding) for h in options.host_run] + metadata_regexp = re.compile('^[A-Za-z_]+$') - meta = {} for m in options.metadata: - try: - key, value = m.split('=', 1) - except ValueError: - parser.error("Metadata option: `%s' is not in KEY=VALUE format." % - m) - if not re.match(metadata_regexp, key): + if not re.match(metadata_regexp, m): parser.error("Metadata key: `%s' is not valid. Allowed characters " - "for key: [a-zA-Z0-9_]." % key) - meta[key.upper()] = value - options.metadata = meta - - sysprep_params = {} - for p in options.sysprep_params: - try: - key, value = p.split('=', 1) - except ValueError: - parser.error("Sysprep parameter option: `%s' is not in KEY=VALUE " - "format." % p) - sysprep_params[key] = value + "for key: [a-zA-Z0-9_]." % m) if options.virtio is not None: - sysprep_params['virtio'] = options.virtio - options.sysprep_params = sysprep_params + options.sysprep_params['virtio'] = options.virtio if options.outfile is None and not options.upload and not \ options.print_syspreps and not options.print_sysprep_params \ @@ -331,6 +355,11 @@ def image_creator(options, out): else: out.warn("Sysprep: `%s' does not exist. Can't enable it." % name) + if image.is_unsupported(): + image.meta['EXCLUDE_ALL_TASKS'] = "yes" + + # Add command line metadata to the collected ones... + image.meta.update(options.metadata) if options.print_syspreps: image.os.print_syspreps() @@ -386,17 +415,11 @@ def image_creator(options, out): if options.sysprep: image.os.do_sysprep() - if image.is_unsupported(): - image.meta['EXCLUDE_ALL_TASKS'] = "yes" - - # Add command line metadata to the collected ones... - image.meta.update(options.metadata) - checksum = image.md5() - metastring = unicode(json.dumps( - {'properties': image.meta, - 'disk-format': 'diskdump'}, ensure_ascii=False)) + metastring = json.dumps( + {'properties': image.meta, 'disk-format': 'diskdump'}, + ensure_ascii=False) if options.outfile is not None: if os.path.realpath(options.outfile) == '/dev/null': @@ -476,7 +499,7 @@ def image_creator(options, out): def main(): """Main entry point""" - options = parse_options(sys.argv[1:]) + options = parse_options() if options.silent: out = SilentOutput(colored=sys.stderr.isatty(), diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py index c40d2080dcfdcaf94d5fb4c51916f7c28c4810ab..c9cd73375434f7c5ca87b6a23f6992084abc77e8 100644 --- a/image_creator/os_type/__init__.py +++ b/image_creator/os_type/__init__.py @@ -445,7 +445,7 @@ class OSBase(object): "%s" % param.description)) self.out.info("TYPE:".ljust(13) + "%s%s" % ("list:" if param.is_list else "", param.type)) - self.out.info("VALUE:".ljust(13) + + self.out.info("VALUE:".ljust(13) + "%s" % ("\n".ljust(14).join(param.value) if param.is_list else param.value)) self.out.info() diff --git a/setup.py b/setup.py index 2d7e10a26aa9f63b82ecf6ff6044d88465337cc7..4bf663a11c4d2b6c5fc3681e8b3da24c92764da9 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,8 @@ setup( license='GNU GPLv3', packages=find_packages(), include_package_data=True, - install_requires=['sh', 'ansicolors', 'progress>=1.0.2', 'kamaki>=0.9'], + install_requires=['sh', 'ansicolors', 'progress>=1.0.2', 'kamaki>=0.9', + 'argparse'], # Unresolvable dependencies: # pysendfile|py-sendfile, hivex, guestfs, parted, rsync, entry_points={