#!/usr/bin/env python3 # # Copyright 2005-2014 Red Hat, Inc. # # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. import argparse import atexit import logging import sys import time import libvirt import virtinst from virtinst import cli from virtinst.cli import fail, print_stdout, print_stderr ############################## # Validation utility helpers # ############################## INSTALL_METHODS = "--location URL, --cdrom CD/ISO, --pxe, --import, --boot hd|cdrom|..." def supports_pxe(guest): """ Return False if we are pretty sure the config doesn't support PXE """ for nic in guest.devices.interface: if nic.type == nic.TYPE_USER: continue if nic.type != nic.TYPE_VIRTUAL: return True try: netobj = nic.conn.networkLookupByName(nic.source) xmlobj = virtinst.Network(nic.conn, parsexml=netobj.XMLDesc(0)) return xmlobj.can_pxe() except Exception: # pragma: no cover logging.debug("Error checking if PXE supported", exc_info=True) return True return False def check_cdrom_option_error(options): if options.cdrom_short and options.cdrom: fail("Cannot specify both -c and --cdrom") if options.cdrom_short: if "://" in options.cdrom_short: fail("-c specified with what looks like a libvirt URI. " "Did you mean to use --connect? If not, use --cdrom instead") options.cdrom = options.cdrom_short ################################# # Back compat option conversion # ################################# def convert_old_printxml(options): if options.xmlstep: options.xmlonly = options.xmlstep del(options.xmlstep) def convert_old_sound(options): if not options.sound: return for idx in range(len(options.sound)): if options.sound[idx] is None: options.sound[idx] = "default" def convert_old_init(options): if not options.init: return if not options.boot: options.boot = [""] options.boot[-1] += ",init=%s" % options.init logging.debug("Converted old --init to --boot %s", options.boot[-1]) def _do_convert_old_disks(options): paths = virtinst.xmlutil.listify(options.file_paths) sizes = virtinst.xmlutil.listify(options.disksize) def padlist(l, padsize): l = virtinst.xmlutil.listify(l) l.extend((padsize - len(l)) * [None]) return l disklist = padlist(paths, max(0, len(sizes))) sizelist = padlist(sizes, len(disklist)) opts = [] for idx, path in enumerate(disklist): optstr = "" if path: optstr += "path=%s" % path if sizelist[idx]: if optstr: optstr += "," optstr += "size=%s" % sizelist[idx] if options.sparse is False: if optstr: optstr += "," optstr += "sparse=no" logging.debug("Converted to new style: --disk %s", optstr) opts.append(optstr) options.disk = opts def convert_old_disks(options): if options.nodisks and (options.file_paths or options.disk or options.disksize): fail(_("Cannot specify storage and use --nodisks")) if ((options.file_paths or options.disksize or not options.sparse) and options.disk): fail(_("Cannot mix --file, --nonsparse, or --file-size with --disk " "options. Use --disk PATH[,size=SIZE][,sparse=yes|no]")) if not options.disk: if options.nodisks: options.disk = ["none"] else: _do_convert_old_disks(options) del(options.file_paths) del(options.disksize) del(options.sparse) del(options.nodisks) logging.debug("Distilled --disk options: %s", options.disk) def convert_old_os_options(options): # Default to distro autodetection distkey = "auto" if options.os_variant: distkey = options.os_variant elif options.old_os_type: distkey = options.old_os_type options.os_variant = cli.parse_os_variant(distkey) del(options.old_os_type) def convert_old_memory(options): if options.memory: return if not options.oldmemory: return options.memory = str(options.oldmemory) def convert_old_cpuset(options): if not options.cpuset: return newvcpus = options.vcpus or [] newvcpus.append(",cpuset=%s" % options.cpuset) options.vcpus = newvcpus logging.debug("Generated compat cpuset: --vcpus %s", options.vcpus[-1]) def convert_old_networks(options): if options.nonetworks: options.network = ["none"] macs = virtinst.xmlutil.listify(options.mac) networks = virtinst.xmlutil.listify(options.network) bridges = virtinst.xmlutil.listify(options.bridge) if bridges and networks: fail(_("Cannot mix both --bridge and --network arguments")) if bridges: # Convert old --bridges to --networks networks = ["bridge:" + b for b in bridges] def padlist(l, padsize): l = virtinst.xmlutil.listify(l) l.extend((padsize - len(l)) * [None]) return l # If a plain mac is specified, have it imply a default network networks = padlist(networks, max(len(macs), 1)) macs = padlist(macs, len(networks)) for idx, ignore in enumerate(networks): if networks[idx] is None: networks[idx] = "default" if macs[idx]: networks[idx] += ",mac=%s" % macs[idx] # Handle old format of bridge:foo instead of bridge=foo for prefix in ["network", "bridge"]: if networks[idx].startswith(prefix + ":"): networks[idx] = networks[idx].replace(prefix + ":", prefix + "=") del(options.mac) del(options.bridge) del(options.nonetworks) options.network = networks logging.debug("Distilled --network options: %s", options.network) def convert_old_graphics(options): vnc = options.vnc vncport = options.vncport vnclisten = options.vnclisten nographics = options.nographics sdl = options.sdl keymap = options.keymap graphics = options.graphics if graphics and (vnc or sdl or keymap or vncport or vnclisten): fail(_("Cannot mix --graphics and old style graphical options")) optnum = sum([bool(g) for g in [vnc, nographics, sdl, graphics]]) if optnum > 1: raise ValueError(_("Can't specify more than one of VNC, SDL, " "--graphics or --nographics")) if options.graphics: return if optnum == 0: return # Build a --graphics command line from old style opts optstr = ((vnc and "vnc") or (sdl and "sdl") or (nographics and ("none"))) if vnclisten: optstr += ",listen=%s" % vnclisten if vncport: optstr += ",port=%s" % vncport if keymap: optstr += ",keymap=%s" % keymap logging.debug("--graphics compat generated: %s", optstr) options.graphics = [optstr] def convert_old_features(options): if options.features: return opts = [] if options.noacpi: opts.append("acpi=off") if options.noapic: opts.append("apic=off") if opts: options.features = [",".join(opts)] ################################## # Install media setup/validation # ################################## def do_test_media_detection(conn, options): url = options.test_media_detection guest = virtinst.Guest(conn) if options.arch: guest.os.arch = options.arch if options.os_type: guest.os.os_type = options.os_type guest.set_capabilities_defaults() installer = virtinst.Installer(conn, location=url) print_stdout(installer.detect_distro(guest), do_force=True) ############################# # General option validation # ############################# def storage_specified(options, guest): if guest.os.is_container(): return True return options.disk or options.filesystem def memory_specified(guest): return guest.memory or guest.currentMemory or guest.cpu.cells def validate_required_options(options, guest, installer): # Required config. Don't error right away if nothing is specified, # aggregate the errors to help first time users get it right msg = "" if not memory_specified(guest): msg += "\n" + _("--memory amount in MiB is required") if not storage_specified(options, guest): msg += "\n" + ( _("--disk storage must be specified (override with --disk none)")) if not guest.os.is_container() and not installer.options_specified(): msg += "\n" + ( _("An install method must be specified\n(%(methods)s)") % {"methods": INSTALL_METHODS}) if msg: fail(msg) _cdrom_location_man_page = _("See the man page for examples of " "using --location with CDROM media") def _show_nographics_warnings(options, guest, installer): if guest.devices.graphics: return if not options.autoconsole: return if installer.cdrom: logging.warning(_("CDROM media does not print to the text console " "by default, so you likely will not see text install output. " "You might want to use --location.") + " " + _cdrom_location_man_page) return if not options.location: return # Trying --location --nographics with console connect. Warn if # they likely won't see any output. if not guest.devices.console: logging.warning(_("No --console device added, you likely will not " "see text install output from the guest.")) return def show_warnings(options, guest, installer): if options.pxe and not supports_pxe(guest): logging.warning(_("The guest's network configuration does not support " "PXE")) # Limit it to hvm x86 guests which presently our defaults # only really matter for if (guest.osinfo.name == "generic" and not options.os_variant.is_none and not options.os_variant.name == "generic" and guest.os.is_x86() and guest.os.is_hvm()): logging.warning(_("No operating system detected, VM performance may " "suffer. Specify an OS with --os-variant for optimal results.")) _show_nographics_warnings(options, guest, installer) ########################## # Guest building helpers # ########################## def build_installer(options, guest): cdrom = None location = None location_kernel = None location_initrd = None install_bootdev = None installdata = None install_kernel = None install_initrd = None install_kernel_args = None if options.install: installdata = cli.parse_install(options.install) if options.unattended: if options.os_variant.is_none or options.os_variant.is_auto: fail(_("--unattended requires an explicit --os-variant")) if not guest.osinfo.is_windows(): if options.cdrom: options.location = options.cdrom options.cdrom = None options.os_variant.install = "location" INSTALL_VALUES = ["location"] if options.os_variant.install not in INSTALL_VALUES + [None]: fail(_("Unknown --os-variant install value '%s'. Must be one of: %s") % (options.os_variant.install, ", ".join(INSTALL_VALUES))) if options.os_variant.install == "location": if not options.location: location = guest.osinfo.get_location(guest.os.arch) logging.debug( "Generated default libosinfo '--location %s'", location) options.location = location extra_args = options.extra_args if installdata: install_bootdev = installdata.bootdev install_kernel = installdata.kernel install_initrd = installdata.initrd install_kernel_args = installdata.kernel_args if installdata.kernel_args: if installdata.kernel_args_overwrite: install_kernel_args = installdata.kernel_args else: extra_args = [installdata.kernel_args] no_install = None if options.location: (location, location_kernel, location_initrd) = cli.parse_location(options.location) elif options.cdrom: cdrom = options.cdrom if options.livecd: no_install = True elif options.pxe: install_bootdev = "network" elif installdata: pass elif (options.import_install or options.xmlonly or options.boot): no_install = True installer = virtinst.Installer(guest.conn, cdrom=cdrom, location=location, location_kernel=location_kernel, location_initrd=location_initrd, install_bootdev=install_bootdev, install_kernel=install_kernel, install_initrd=install_initrd, install_kernel_args=install_kernel_args, no_install=no_install) if options.unattended: unattended_data = cli.parse_unattended(options.unattended) installer.set_unattended_data(unattended_data) if extra_args: installer.set_extra_args(extra_args) if options.initrd_inject: installer.set_initrd_injections(options.initrd_inject) if options.autostart: installer.autostart = True distro = None try: # This also validates the install location autodistro = installer.detect_distro(guest) if options.os_variant.is_auto: distro = autodistro except ValueError as e: fail(_("Error validating install location: %s") % str(e)) if distro: guest.set_os_name(distro) return installer def set_cli_defaults(options, guest): if not guest.name: default_name = virtinst.Guest.generate_name(guest) cli.print_stdout(_("Using default --name {vm_name}").format( vm_name=default_name)) guest.name = default_name if guest.os.is_container(): return res = guest.osinfo.get_recommended_resources() storage = res.get_recommended_storage(guest.os.arch) ram = res.get_recommended_ram(guest.os.arch) ncpus = res.get_recommended_ncpus(guest.os.arch) if ram and not memory_specified(guest): mbram = str(ram / (1024 * 1024)).rstrip("0").rstrip(".") cli.print_stdout( _("Using {os_name} default --memory {megabytes}").format( os_name=guest.osinfo.name, megabytes=mbram)) guest.currentMemory = ram // 1024 if ncpus: # We need to do this upfront, so we don't incorrectly set guest.vcpus guest.sync_vcpus_topology() if not guest.vcpus: # I don't think we need to print anything here as this was never # a required value. guest.vcpus = ncpus if storage and not storage_specified(options, guest): diskstr = 'size=%d' % (storage // (1024 ** 3)) cli.print_stdout( _("Using {os_name} default --disk {disk_options}".format( os_name=guest.osinfo.name, disk_options=diskstr))) options.disk = [diskstr] cli.ParserDisk(diskstr, guest=guest).parse(None) def build_guest_instance(conn, options): guest = virtinst.Guest(conn) if options.name: guest.name = options.name if options.uuid: guest.uuid = options.uuid if options.description: guest.description = options.description if options.os_type: guest.os.os_type = options.os_type if options.virt_type: guest.type = options.virt_type if options.arch: guest.os.arch = options.arch if options.machine: guest.os.machine = options.machine # If explicit os-variant requested, set it early since it will # provide more defaults in the future options.os_variant.set_os_name(guest) cli.parse_option_strings(options, guest, None) # Call set_capabilities_defaults explicitly here rather than depend # on set_defaults calling it. Installer setup needs filled in values. # However we want to do it after parse_option_strings to ensure # we are operating on any arch/os/type values passed in with --boot guest.set_capabilities_defaults() installer = build_installer(options, guest) set_cli_defaults(options, guest) installer.set_install_defaults(guest) for path in installer.get_search_paths(guest): cli.check_path_search(guest.conn, path) # cli specific disk validation for disk in guest.devices.disk: cli.validate_disk(disk) validate_required_options(options, guest, installer) show_warnings(options, guest, installer) return guest, installer ########################### # Install process helpers # ########################### def _sleep(secs): if not cli.in_testsuite(): time.sleep(secs) # pragma: no cover class WaitHandler: """ Helper class for handling the --wait option sleeping and time tracking """ def __init__(self, wait): self.wait_is_requested = False self._wait_mins = 0 self._start_time = 0 if wait is not None: self.wait_is_requested = True self._wait_mins = wait @property def wait_for_console_to_exit(self): # If --wait specified, we don't want the default behavior of waiting # for virt-viewer to exit, we want to launch it, then manually count # down time for ourselves return not self.wait_is_requested @property def _wait_forever(self): return self._wait_mins < 0 @property def _wait_secs(self): return self._wait_mins * 60 def start(self): self._start_time = time.time() def get_time_string(self): timestr = _(" %d minutes") % self._wait_secs if self._wait_forever: timestr = "" ret = _("Waiting%(time_string)s for installation to complete.") % { "time_string": timestr} return ret def wait(self): """ sleep 1 second, then teturn True if wait time has expired """ _sleep(1) if self._wait_forever: if cli.in_testsuite(): return True return False # pragma: no cover time_elapsed = (time.time() - self._start_time) return (time_elapsed >= self._wait_secs) or cli.in_testsuite() def start_install(guest, installer, options): conscb = None if options.autoconsole: conscb = cli.get_console_cb(guest) if not conscb and options.wait is None: # If there isn't any console to actually connect up, # default to --wait -1 to get similarish behavior logging.warning(_("No console to launch for the guest, " "defaulting to --wait -1")) options.wait = -1 waithandler = WaitHandler(options.wait) meter = cli.get_meter() logging.debug("Guest.has_install_phase: %s", installer.has_install_phase()) # we've got everything -- try to start the install print_stdout(_("\nStarting install...")) domain = None try: waithandler.start() domain = installer.start_install(guest, meter=meter, doboot=not options.noreboot, transient=options.transient) if options.destroy_on_exit: atexit.register(_destroy_on_exit, domain) cli.connect_console(guest, domain, conscb, waithandler.wait_for_console_to_exit, options.destroy_on_exit) check_domain(installer, domain, conscb, options.transient, waithandler) print_stdout(_("Domain creation completed.")) if not options.transient and not domain.isActive(): if options.noreboot or not installer.has_install_phase(): print_stdout( # pragma: no cover _("You can restart your domain by running:\n %s") % cli.virsh_start_cmd(guest)) else: print_stdout(_("Restarting guest.")) domain.create() cli.connect_console(guest, domain, conscb, True, options.destroy_on_exit) except KeyboardInterrupt: # pragma: no cover logging.debug("", exc_info=True) print_stderr(_("Domain install interrupted.")) raise except Exception as e: fail(e, do_exit=False) if domain is None: installer.cleanup_created_disks(guest, meter) cli.install_fail(guest) if cli.in_testsuite() and options.destroy_on_exit: # Helps with unit testing _destroy_on_exit(domain) def check_domain(installer, domain, conscb, transient, waithandler): """ Make sure domain ends up in expected state, and wait if for install to complete if requested """ def check_domain_inactive(): try: dominfo = domain.info() state = dominfo[0] logging.debug("Domain state after install: %s", state) if state == libvirt.VIR_DOMAIN_CRASHED: fail(_("Domain has crashed.")) # pragma: no cover return not domain.isActive() except libvirt.libvirtError as e: if transient and e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: logging.debug("transient VM shutdown and disappeared.") return True raise # pragma: no cover if check_domain_inactive(): return if bool(conscb): # We are trying to detect if the VM shutdown, or the user # just closed the console and the VM is still running. In the # the former case, libvirt may not have caught up yet with the # VM having exited, so wait a bit and check again _sleep(2) if check_domain_inactive(): return # pragma: no cover # If we reach here, the VM still appears to be running. if not waithandler.wait_is_requested: # User either: # used --noautoconsole # killed console and guest is still running if not installer.has_install_phase(): return print_stdout( _("Domain installation still in progress. You can reconnect" " to \nthe console to complete the installation process.")) sys.exit(0) print_stdout(_("Domain installation still in progress.")) print_stdout(waithandler.get_time_string()) # Wait loop while True: if not domain.isActive(): # pragma: no cover print_stdout(_("Domain has shutdown. Continuing.")) break done = waithandler.wait() if done: print_stdout( _("Installation has exceeded specified time limit. " "Exiting application.")) sys.exit(1) ######################## # XML printing helpers # ######################## def xml_to_print(guest, installer, xmlonly, dry): start_xml, final_xml = installer.start_install( guest, dry=dry, return_xml=True) if not start_xml: start_xml = final_xml final_xml = None if dry and not xmlonly: print_stdout(_("Dry run completed successfully")) return if xmlonly not in [False, "1", "2", "all"]: fail(_("Unknown XML step request '%s', must be 1, 2, or all") % xmlonly) if xmlonly == "1": return start_xml if xmlonly == "2": if not final_xml: fail(_("Requested installation does not have XML step 2")) return final_xml # "all" case xml = start_xml if final_xml: xml += final_xml return xml ####################### # CLI option handling # ####################### def parse_args(): parser = cli.setupParser( "%(prog)s --name NAME --memory MB STORAGE INSTALL [options]", _("Create a new virtual machine from specified install media."), introspection_epilog=True) cli.add_connect_option(parser) geng = parser.add_argument_group(_("General Options")) geng.add_argument("-n", "--name", help=_("Name of the guest instance")) cli.add_memory_option(geng, backcompat=True) cli.vcpu_cli_options(geng) cli.add_metadata_option(geng) geng.add_argument("-u", "--uuid", help=argparse.SUPPRESS) geng.add_argument("--description", help=argparse.SUPPRESS) insg = parser.add_argument_group(_("Installation Method Options")) insg.add_argument("-c", dest="cdrom_short", help=argparse.SUPPRESS) insg.add_argument("--cdrom", help=_("CD-ROM installation media")) insg.add_argument("-l", "--location", help=_("Distro install URL, eg. https://host/path. See man " "page for specific distro examples.")) insg.add_argument("--pxe", action="store_true", help=_("Boot from the network using the PXE protocol")) insg.add_argument("--import", action="store_true", dest="import_install", help=_("Build guest around an existing disk image")) insg.add_argument("--livecd", action="store_true", help=_("Treat the CD-ROM media as a Live CD")) insg.add_argument("-x", "--extra-args", action="append", help=_("Additional arguments to pass to the install kernel " "booted from --location")) insg.add_argument("--initrd-inject", action="append", help=_("Add given file to root of initrd from --location")) insg.add_argument("--unattended", nargs="?", const=1, help=_("Perform an unattended installation")) insg.add_argument("--install", help=_("Specify fine grained install options")) # Takes a URL and just prints to stdout the detected distro name insg.add_argument("--test-media-detection", help=argparse.SUPPRESS) # Helper for cli testing, fills in standard stub options insg.add_argument("--test-stub-command", action="store_true", help=argparse.SUPPRESS) cli.add_boot_options(insg) insg.add_argument("--init", help=argparse.SUPPRESS) osg = cli.add_os_variant_option(parser, virtinstall=True) osg.add_argument("--os-type", dest="old_os_type", help=argparse.SUPPRESS) devg = parser.add_argument_group(_("Device Options")) cli.add_disk_option(devg) cli.add_net_option(devg) cli.add_gfx_option(devg) cli.add_device_options(devg, sound_back_compat=True) # Deprecated device options devg.add_argument("-f", "--file", dest="file_paths", action="append", help=argparse.SUPPRESS) devg.add_argument("-s", "--file-size", type=float, action="append", dest="disksize", help=argparse.SUPPRESS) devg.add_argument("--nonsparse", action="store_false", default=True, dest="sparse", help=argparse.SUPPRESS) devg.add_argument("--nodisks", action="store_true", help=argparse.SUPPRESS) devg.add_argument("--nonetworks", action="store_true", help=argparse.SUPPRESS) devg.add_argument("-b", "--bridge", action="append", help=argparse.SUPPRESS) devg.add_argument("-m", "--mac", action="append", help=argparse.SUPPRESS) devg.add_argument("--vnc", action="store_true", help=argparse.SUPPRESS) devg.add_argument("--vncport", type=int, help=argparse.SUPPRESS) devg.add_argument("--vnclisten", help=argparse.SUPPRESS) devg.add_argument("-k", "--keymap", help=argparse.SUPPRESS) devg.add_argument("--sdl", action="store_true", help=argparse.SUPPRESS) devg.add_argument("--nographics", action="store_true", help=argparse.SUPPRESS) gxmlg = parser.add_argument_group(_("Guest Configuration Options")) cli.add_guest_xml_options(gxmlg) virg = parser.add_argument_group(_("Virtualization Platform Options")) ostypeg = virg.add_mutually_exclusive_group() ostypeg.add_argument("-v", "--hvm", action="store_const", const="hvm", dest="os_type", help=_("This guest should be a fully virtualized guest")) ostypeg.add_argument("-p", "--paravirt", action="store_const", const="xen", dest="os_type", help=_("This guest should be a paravirtualized guest")) ostypeg.add_argument("--container", action="store_const", const="exe", dest="os_type", help=_("This guest should be a container guest")) virg.add_argument("--virt-type", help=_("Hypervisor name to use (kvm, qemu, xen, ...)")) virg.add_argument("--arch", help=_("The CPU architecture to simulate")) virg.add_argument("--machine", help=_("The machine type to emulate")) virg.add_argument("--accelerate", action="store_true", help=argparse.SUPPRESS) virg.add_argument("--noapic", action="store_true", default=False, help=argparse.SUPPRESS) virg.add_argument("--noacpi", action="store_true", default=False, help=argparse.SUPPRESS) misc = parser.add_argument_group(_("Miscellaneous Options")) misc.add_argument("--autostart", action="store_true", default=False, help=_("Have domain autostart on host boot up.")) misc.add_argument("--transient", action="store_true", default=False, help=_("Create a transient domain.")) misc.add_argument("--destroy-on-exit", action="store_true", default=False, help=_("Force power off the domain when the console " "viewer is closed.")) misc.add_argument("--wait", type=int, help=_("Minutes to wait for install to complete.")) cli.add_misc_options(misc, prompt=True, printxml=True, printstep=True, noreboot=True, dryrun=True, noautoconsole=True) cli.autocomplete(parser) return parser.parse_args() ################### # main() handling # ################### # Catchall for destroying the VM on ex. ctrl-c def _destroy_on_exit(domain): try: isactive = bool(domain and domain.isActive()) if isactive: domain.destroy() # pragma: no cover except libvirt.libvirtError as e: # pragma: no cover if e.get_error_code() != libvirt.VIR_ERR_NO_DOMAIN: logging.debug("Error invoking atexit destroy_on_exit", exc_info=True) def set_test_stub_options(options): # pragma: no cover # Set some basic options that will let virt-install succeed. Helps # save boiler plate typing when testing new command line additions if not options.test_stub_command: return options.import_install = True if not options.connect: options.connect = "test:///default" if not options.name: options.name = "test-stub-command" if not options.memory: options.memory = "256" if not options.disk: options.disk = "none" if not options.graphics: options.graphics = "none" if not options.os_variant: options.os_variant = "fedora27" def main(conn=None): cli.earlyLogging() options = parse_args() # Default setup options convert_old_printxml(options) options.quiet = (options.xmlonly or options.test_media_detection or options.quiet) cli.setupLogging("virt-install", options.debug, options.quiet) if cli.check_option_introspection(options): return 0 check_cdrom_option_error(options) cli.convert_old_force(options) cli.parse_check(options.check) cli.set_prompt(options.prompt) convert_old_memory(options) convert_old_sound(options) convert_old_networks(options) convert_old_graphics(options) convert_old_disks(options) convert_old_features(options) convert_old_cpuset(options) convert_old_init(options) set_test_stub_options(options) convert_old_os_options(options) conn = cli.getConnection(options.connect, conn=conn) if options.test_media_detection: do_test_media_detection(conn, options) return 0 guest, installer = build_guest_instance(conn, options) if options.xmlonly or options.dry: xml = xml_to_print(guest, installer, options.xmlonly, options.dry) if xml: print_stdout(xml, do_force=True) else: start_install(guest, installer, options) return 0 if __name__ == "__main__": # pragma: no cover try: sys.exit(main()) except SystemExit as sys_e: sys.exit(sys_e.code) except KeyboardInterrupt: logging.debug("", exc_info=True) print_stderr(_("Installation aborted at user request")) except Exception as main_e: fail(main_e)