Initialize a new vm

    :param name: name of the virtual machine to create
    :param cpu: Number of virtual CPUs to assign to the virtual machine
    :param mem: Amount of memory to allocate to the virtual machine in MiB.
    :param image: Path to a disk image to use as the first disk (Default: ``None``).
        Deprecated in favor of the ``disks`` parameter. To set (or change) the
        image of a disk, add the following to the disks definitions:

        .. code-block:: python

            {
              'name': 'name_of_disk_to_change',
              'image': '/path/to/the/image'
            }

    :param nic: NIC profile to use (Default: ``'default'``).
        The profile interfaces can be customized / extended with the interfaces
        parameter. If set to ``None``, no profile will be used.

    :param interfaces:
        List of dictionaries providing details on the network interfaces to create.
        These data are merged with the ones from the nic profile. The structure of
        each dictionary is documented in init-nic-def_.

        .. versionadded:: 2019.2.0

    :param hypervisor: the virtual machine type. By default the value will be
        computed according to the virtual host capabilities.

    :param start: ``True`` to start the virtual machine after having defined it
        (Default: ``True``)

    :param disk: Disk profile to use (Default: ``'default'``). If set to
        ``None``, no profile will be used.

    :param disks: List of dictionaries providing details on the disk devices to
        create. These data are merged with the ones from the disk profile. The
        structure of each dictionary is documented in init-disk-def_.

        .. versionadded:: 2019.2.0

    :param saltenv: Fileserver environment (Default: ``'base'``)

    :param seed: ``True`` to seed the disk image. Only used when the ``image``
        parameter is provided. (Default: ``True``)

    :param install: install salt minion if absent (Default: ``True``)
    :param pub_key: public key to seed with (Default: ``None``)
    :param priv_key: public key to seed with (Default: ``None``)
    :param seed_cmd: Salt command to execute to seed the image. (Default:
        ``'seed.apply'``)

    :param enable_vnc:
        ``True`` to setup a vnc display for the VM (Default: ``False``)

        Deprecated in favor of the ``graphics`` parameter. Could be replaced with
        the following:

        .. code-block:: python

            graphics={'type': 'vnc'}

        .. deprecated:: 2019.2.0

    :param graphics:
        Dictionary providing details on the graphics device to create. (Default: ``None``)
        See init-graphics-def_ for more details on the possible values.

        .. versionadded:: 2019.2.0

    :param loader:
        Dictionary providing details on the BIOS firmware loader. (Default: ``None``)
        See init-loader-def_ for more details on the possible values.

        .. versionadded:: 2019.2.0
    :param os_type:
        type of virtualization as found in the ``//os/type`` element of the libvirt definition.
        The default value is taken from the host capabilities, with a preference for ``hvm``.

        .. versionadded:: 2019.2.0

    :param arch:
        architecture of the virtual machine. The default value is taken from the host capabilities,
        but ``x86_64`` is prefed over ``i686``.

        .. versionadded:: 2019.2.0

    :param enable_qcow:
        ``True`` to create a QCOW2 overlay image, rather than copying the image
        (Default: ``False``).

        Deprecated in favor of ``disks`` parameter. Add the following to the disks
        definitions to create an overlay image of a template disk image with an
        image set:

        .. code-block:: python

            {
                'name': 'name_of_disk_to_change',
                'overlay_image': True
            }

        .. deprecated:: 2019.2.0

    :param pool:
        Path of the folder where the image files are located for vmware/esx hypervisors.

        Deprecated in favor of ``disks`` parameter. Add the following to the disks
        definitions to set the vmware datastore of a disk image:

        .. code-block:: python

            {
                'name': 'name_of_disk_to_change',
                'pool': 'mydatastore'
            }

        .. deprecated:: Flurorine

    :param dmac:
        Default MAC address to use for the network interfaces. By default MAC addresses are
        automatically generated.

        Deprecated in favor of ``interfaces`` parameter. Add the following to the interfaces
        definitions to force the mac address of a NIC:

        .. code-block:: python

            {
                'name': 'name_of_nic_to_change',
                'mac': 'MY:MA:CC:ADD:RE:SS'
            }

        .. deprecated:: 2019.2.0

    :param config: minion configuration to use when seeding.  See :mod:`seed
        module <salt.modules.seed>` for more details

    :param boot_dev: String of space-separated devices to boot from (Default: ``'hd'``)
    :param serial_type: Serial device type. One of ``'pty'``, ``'tcp'``
        (Default: ``None``)
    :param telnet_port: Telnet port to use for serial device of type ``tcp``.
    :param console: ``True`` to add a console device along with serial one
        (Default: ``True``)
    :param connection: libvirt connection URI, overriding defaults

    .. versionadded:: 2019.2.0

    :param username: username to connect with, overriding defaults

    .. versionadded:: 2019.2.0

    :param password: password to connect with, overriding defaults

    .. versionadded:: 2019.2.0

    .. _init-nic-def:

    **Network Interface Definitions**

    Network interfaces dictionaries can contain the following properties:

    name
        Name of the network interface. This is only used as a key to merge with the profile data

    type
        Network type. One of ``'bridge'``, ``'network'``

    source
        The network source, typically the bridge or network name

    mac
        The desired mac address, computed if ``None`` (Default: ``None``).

    model
        The network card model (Default: depends on the hypervisor)

    .. _init-disk-def:

    **Disk Definitions**

    Disk dictionaries can contain the following properties:

    name
        Name of the disk. This is mostly used in the name of the disk image and as a key to merge
        with the profile data.

    format
        Format of the disk image, like ``'qcow2'``, ``'raw'``, ``'vmdk'``.
        (Default: depends on the hypervisor)

    size
        Disk size in MiB

    pool
        Path to the folder or name of the pool where disks should be created.
        (Default: depends on hypervisor)

    model
        One of the disk busses allowed by libvirt (Default: depends on hypervisor)

        See the libvirt `disk element`_ documentation for the allowed bus types.

    image
        Path to the image to use for the disk. If no image is provided, an empty disk will be created
        (Default: ``None``)

    overlay_image
        ``True`` to create a QCOW2 disk image with ``image`` as backing file. If ``False``
        the file pointed to by the ``image`` property will simply be copied. (Default: ``False``)

    hostname_property
        When using ZFS volumes, setting this value to a ZFS property ID will make Salt store the name of the
        virtual machine inside this property. (Default: ``None``)

    sparse_volume
        Boolean to specify whether to use a thin provisioned ZFS volume.

        Example profile for a bhyve VM with two ZFS disks. The first is
        cloned from the specified image. The second disk is a thin
        provisioned volume.

        .. code-block:: yaml

            virt:
              disk:
                two_zvols:
                  - system:
                      image: zroot/bhyve/CentOS-7-x86_64-v1@v1.0.5
                      hostname_property: virt:hostname
                      pool: zroot/bhyve/guests
                  - data:
                      pool: tank/disks
                      size: 20G
                      hostname_property: virt:hostname
                      sparse_volume: True

    source_file
        Absolute path to the disk image to use. Not to be confused with ``image`` parameter. This
        parameter is useful to use disk images that are created outside of this module. Can also
        be ``None`` for devices that have no associated image like cdroms.

    device
        Type of device of the disk. Can be one of 'disk', 'cdrom', 'floppy' or 'lun'.
        (Default: ``'disk'``)

    .. _init-graphics-def:

    **Graphics Definition**

    The graphics dictionnary can have the following properties:

    type
        Graphics type. The possible values are ``none``, ``'spice'``, ``'vnc'`` and other values
        allowed as a libvirt graphics type (Default: ``None``)

        See the libvirt `graphics element`_ documentation for more details on the possible types.

    port
        Port to export the graphics on for ``vnc``, ``spice`` and ``rdp`` types.

    tls_port
        Port to export the graphics over a secured connection for ``spice`` type.

    listen
        Dictionary defining on what address to listen on for ``vnc``, ``spice`` and ``rdp``.
        It has a ``type`` property with ``address`` and ``None`` as possible values, and an
        ``address`` property holding the IP or hostname to listen on.

        By default, not setting the ``listen`` part of the dictionary will default to
        listen on all addresses.

    .. _init-loader-def:

    **Loader Definition**

    The loader dictionary must have the following property:

    path
        Path to the UEFI firmware binary

    Optionally, you can provide arbitrary attributes such as ``readonly`` or ``type``. See
    the libvirt documentation for all supported loader parameters.

    CLI Example:

    .. code-block:: bash

        salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
        salt 'hypervisor' virt.init vm_name 4 512 /var/lib/libvirt/images/img.raw
        salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile

    The disk images will be created in an image folder within the directory
    defined by the ``virt:images`` option. Its default value is
    ``/srv/salt-images/`` but this can changed with such a configuration:

    .. code-block:: yaml

        virt:
            images: /data/my/vm/images/

    .. _disk element: https://libvirt.org/formatdomain.html#elementsDisks
    .. _graphics element: https://libvirt.org/formatdomain.html#elementsGraphics

def init(name,
         cpu,
         mem,
         image=None,
         nic='default',
         interfaces=None,
         hypervisor=None,
         start=True,  # pylint: disable=redefined-outer-name
         disk='default',
         disks=None,
         saltenv='base',
         seed=True,
         install=True,
         pub_key=None,
         priv_key=None,
         seed_cmd='seed.apply',
         enable_vnc=False,
         enable_qcow=False,
         graphics=None,
         os_type=None,
         arch=None,
         loader=None,
         **kwargs):
    '''
    Initialize a new vm

    :param name: name of the virtual machine to create
    :param cpu: Number of virtual CPUs to assign to the virtual machine
    :param mem: Amount of memory to allocate to the virtual machine in MiB.
    :param image: Path to a disk image to use as the first disk (Default: ``None``).
        Deprecated in favor of the ``disks`` parameter. To set (or change) the
        image of a disk, add the following to the disks definitions:

        .. code-block:: python

            {
              'name': 'name_of_disk_to_change',
              'image': '/path/to/the/image'
            }

    :param nic: NIC profile to use (Default: ``'default'``).
        The profile interfaces can be customized / extended with the interfaces
        parameter. If set to ``None``, no profile will be used.

    :param interfaces:
        List of dictionaries providing details on the network interfaces to create.
        These data are merged with the ones from the nic profile. The structure of
        each dictionary is documented in init-nic-def_.

        .. versionadded:: 2019.2.0

    :param hypervisor: the virtual machine type. By default the value will be
        computed according to the virtual host capabilities.

    :param start: ``True`` to start the virtual machine after having defined it
        (Default: ``True``)

    :param disk: Disk profile to use (Default: ``'default'``). If set to
        ``None``, no profile will be used.

    :param disks: List of dictionaries providing details on the disk devices to
        create. These data are merged with the ones from the disk profile. The
        structure of each dictionary is documented in init-disk-def_.

        .. versionadded:: 2019.2.0

    :param saltenv: Fileserver environment (Default: ``'base'``)

    :param seed: ``True`` to seed the disk image. Only used when the ``image``
        parameter is provided. (Default: ``True``)

    :param install: install salt minion if absent (Default: ``True``)
    :param pub_key: public key to seed with (Default: ``None``)
    :param priv_key: public key to seed with (Default: ``None``)
    :param seed_cmd: Salt command to execute to seed the image. (Default:
        ``'seed.apply'``)

    :param enable_vnc:
        ``True`` to setup a vnc display for the VM (Default: ``False``)

        Deprecated in favor of the ``graphics`` parameter. Could be replaced with
        the following:

        .. code-block:: python

            graphics={'type': 'vnc'}

        .. deprecated:: 2019.2.0

    :param graphics:
        Dictionary providing details on the graphics device to create. (Default: ``None``)
        See init-graphics-def_ for more details on the possible values.

        .. versionadded:: 2019.2.0

    :param loader:
        Dictionary providing details on the BIOS firmware loader. (Default: ``None``)
        See init-loader-def_ for more details on the possible values.

        .. versionadded:: 2019.2.0
    :param os_type:
        type of virtualization as found in the ``//os/type`` element of the libvirt definition.
        The default value is taken from the host capabilities, with a preference for ``hvm``.

        .. versionadded:: 2019.2.0

    :param arch:
        architecture of the virtual machine. The default value is taken from the host capabilities,
        but ``x86_64`` is prefed over ``i686``.

        .. versionadded:: 2019.2.0

    :param enable_qcow:
        ``True`` to create a QCOW2 overlay image, rather than copying the image
        (Default: ``False``).

        Deprecated in favor of ``disks`` parameter. Add the following to the disks
        definitions to create an overlay image of a template disk image with an
        image set:

        .. code-block:: python

            {
                'name': 'name_of_disk_to_change',
                'overlay_image': True
            }

        .. deprecated:: 2019.2.0

    :param pool:
        Path of the folder where the image files are located for vmware/esx hypervisors.

        Deprecated in favor of ``disks`` parameter. Add the following to the disks
        definitions to set the vmware datastore of a disk image:

        .. code-block:: python

            {
                'name': 'name_of_disk_to_change',
                'pool': 'mydatastore'
            }

        .. deprecated:: Flurorine

    :param dmac:
        Default MAC address to use for the network interfaces. By default MAC addresses are
        automatically generated.

        Deprecated in favor of ``interfaces`` parameter. Add the following to the interfaces
        definitions to force the mac address of a NIC:

        .. code-block:: python

            {
                'name': 'name_of_nic_to_change',
                'mac': 'MY:MA:CC:ADD:RE:SS'
            }

        .. deprecated:: 2019.2.0

    :param config: minion configuration to use when seeding.  See :mod:`seed
        module <salt.modules.seed>` for more details

    :param boot_dev: String of space-separated devices to boot from (Default: ``'hd'``)
    :param serial_type: Serial device type. One of ``'pty'``, ``'tcp'``
        (Default: ``None``)
    :param telnet_port: Telnet port to use for serial device of type ``tcp``.
    :param console: ``True`` to add a console device along with serial one
        (Default: ``True``)
    :param connection: libvirt connection URI, overriding defaults

    .. versionadded:: 2019.2.0

    :param username: username to connect with, overriding defaults

    .. versionadded:: 2019.2.0

    :param password: password to connect with, overriding defaults

    .. versionadded:: 2019.2.0

    .. _init-nic-def:

    **Network Interface Definitions**

    Network interfaces dictionaries can contain the following properties:

    name
        Name of the network interface. This is only used as a key to merge with the profile data

    type
        Network type. One of ``'bridge'``, ``'network'``

    source
        The network source, typically the bridge or network name

    mac
        The desired mac address, computed if ``None`` (Default: ``None``).

    model
        The network card model (Default: depends on the hypervisor)

    .. _init-disk-def:

    **Disk Definitions**

    Disk dictionaries can contain the following properties:

    name
        Name of the disk. This is mostly used in the name of the disk image and as a key to merge
        with the profile data.

    format
        Format of the disk image, like ``'qcow2'``, ``'raw'``, ``'vmdk'``.
        (Default: depends on the hypervisor)

    size
        Disk size in MiB

    pool
        Path to the folder or name of the pool where disks should be created.
        (Default: depends on hypervisor)

    model
        One of the disk busses allowed by libvirt (Default: depends on hypervisor)

        See the libvirt `disk element`_ documentation for the allowed bus types.

    image
        Path to the image to use for the disk. If no image is provided, an empty disk will be created
        (Default: ``None``)

    overlay_image
        ``True`` to create a QCOW2 disk image with ``image`` as backing file. If ``False``
        the file pointed to by the ``image`` property will simply be copied. (Default: ``False``)

    hostname_property
        When using ZFS volumes, setting this value to a ZFS property ID will make Salt store the name of the
        virtual machine inside this property. (Default: ``None``)

    sparse_volume
        Boolean to specify whether to use a thin provisioned ZFS volume.

        Example profile for a bhyve VM with two ZFS disks. The first is
        cloned from the specified image. The second disk is a thin
        provisioned volume.

        .. code-block:: yaml

            virt:
              disk:
                two_zvols:
                  - system:
                      image: zroot/bhyve/CentOS-7-x86_64-v1@v1.0.5
                      hostname_property: virt:hostname
                      pool: zroot/bhyve/guests
                  - data:
                      pool: tank/disks
                      size: 20G
                      hostname_property: virt:hostname
                      sparse_volume: True

    source_file
        Absolute path to the disk image to use. Not to be confused with ``image`` parameter. This
        parameter is useful to use disk images that are created outside of this module. Can also
        be ``None`` for devices that have no associated image like cdroms.

    device
        Type of device of the disk. Can be one of 'disk', 'cdrom', 'floppy' or 'lun'.
        (Default: ``'disk'``)

    .. _init-graphics-def:

    **Graphics Definition**

    The graphics dictionnary can have the following properties:

    type
        Graphics type. The possible values are ``none``, ``'spice'``, ``'vnc'`` and other values
        allowed as a libvirt graphics type (Default: ``None``)

        See the libvirt `graphics element`_ documentation for more details on the possible types.

    port
        Port to export the graphics on for ``vnc``, ``spice`` and ``rdp`` types.

    tls_port
        Port to export the graphics over a secured connection for ``spice`` type.

    listen
        Dictionary defining on what address to listen on for ``vnc``, ``spice`` and ``rdp``.
        It has a ``type`` property with ``address`` and ``None`` as possible values, and an
        ``address`` property holding the IP or hostname to listen on.

        By default, not setting the ``listen`` part of the dictionary will default to
        listen on all addresses.

    .. _init-loader-def:

    **Loader Definition**

    The loader dictionary must have the following property:

    path
        Path to the UEFI firmware binary

    Optionally, you can provide arbitrary attributes such as ``readonly`` or ``type``. See
    the libvirt documentation for all supported loader parameters.

    CLI Example:

    .. code-block:: bash

        salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
        salt 'hypervisor' virt.init vm_name 4 512 /var/lib/libvirt/images/img.raw
        salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile

    The disk images will be created in an image folder within the directory
    defined by the ``virt:images`` option. Its default value is
    ``/srv/salt-images/`` but this can changed with such a configuration:

    .. code-block:: yaml

        virt:
            images: /data/my/vm/images/

    .. _disk element: https://libvirt.org/formatdomain.html#elementsDisks
    .. _graphics element: https://libvirt.org/formatdomain.html#elementsGraphics
    '''
    caps = capabilities(**kwargs)
    os_types = sorted({guest['os_type'] for guest in caps['guests']})
    arches = sorted({guest['arch']['name'] for guest in caps['guests']})
    if not hypervisor:
        hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
        if hypervisor is not None:
            salt.utils.versions.warn_until(
                'Sodium',
                '\'libvirt:hypervisor\' configuration property has been deprecated. '
                'Rather use the \'virt:connection:uri\' to properly define the libvirt '
                'URI or alias of the host to connect to. \'libvirt:hypervisor\' will '
                'stop being used in {version}.'
            )
        else:
            # Use the machine types as possible values
            # Prefer 'kvm' over the others if available
            hypervisors = sorted({x for y in [guest['arch']['domains'].keys() for guest in caps['guests']] for x in y})
            hypervisor = 'kvm' if 'kvm' in hypervisors else hypervisors[0]

    # esxi used to be a possible value for the hypervisor: map it to vmware since it's the same
    hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor

    log.debug('Using hypervisor %s', hypervisor)

    # the NICs are computed as follows:
    # 1 - get the default NICs from the profile
    # 2 - Complete the users NICS
    # 3 - Update the default NICS list to the users one, matching key is the name
    dmac = kwargs.get('dmac', None)
    if dmac:
        salt.utils.versions.warn_until(
            'Sodium',
            '\'dmac\' parameter has been deprecated. Rather use the \'interfaces\' parameter '
            'to properly define the desired MAC address. \'dmac\' will be removed in {version}.'
        )
    nicp = _get_merged_nics(hypervisor, nic, interfaces, dmac=dmac)

    # the disks are computed as follows:
    # 1 - get the disks defined in the profile
    # 2 - set the image on the first disk (will be removed later)
    # 3 - update the disks from the profile with the ones from the user. The matching key is the name.
    pool = kwargs.get('pool', None)
    if pool:
        salt.utils.versions.warn_until(
            'Sodium',
            '\'pool\' parameter has been deprecated. Rather use the \'disks\' parameter '
            'to properly define the vmware datastore of disks. \'pool\' will be removed in {version}.'
        )

    if image:
        salt.utils.versions.warn_until(
            'Sodium',
            '\'image\' parameter has been deprecated. Rather use the \'disks\' parameter '
            'to override or define the image. \'image\' will be removed in {version}.'
        )

    diskp = _disk_profile(disk, hypervisor, disks, name, image=image, pool=pool, **kwargs)

    # Create multiple disks, empty or from specified images.
    for _disk in diskp:
        log.debug("Creating disk for VM [ %s ]: %s", name, _disk)

        if hypervisor == 'vmware':
            if 'image' in _disk:
                # TODO: we should be copying the image file onto the ESX host
                raise SaltInvocationError(
                    'virt.init does not support image '
                    'template in conjunction with esxi hypervisor'
                )
            else:
                # assume libvirt manages disks for us
                log.debug('Generating libvirt XML for %s', _disk)
                vol_xml = _gen_vol_xml(
                    name,
                    _disk['name'],
                    _disk['format'],
                    _disk['size'],
                    _disk['pool']
                )
                define_vol_xml_str(vol_xml)

        elif hypervisor in ['qemu', 'kvm', 'xen']:

            create_overlay = enable_qcow
            if create_overlay:
                salt.utils.versions.warn_until(
                    'Sodium',
                    '\'enable_qcow\' parameter has been deprecated. Rather use the \'disks\' '
                    'parameter to override or define the image. \'enable_qcow\' will be removed '
                    'in {version}.'
                )
            else:
                create_overlay = _disk.get('overlay_image', False)

            if _disk['source_file']:
                if os.path.exists(_disk['source_file']):
                    img_dest = _disk['source_file']
                else:
                    img_dest = _qemu_image_create(_disk, create_overlay, saltenv)
            else:
                img_dest = None

            # Seed only if there is an image specified
            if seed and img_dest and _disk.get('image', None):
                log.debug('Seed command is %s', seed_cmd)
                __salt__[seed_cmd](
                    img_dest,
                    id_=name,
                    config=kwargs.get('config'),
                    install=install,
                    pub_key=pub_key,
                    priv_key=priv_key,
                )

        elif hypervisor in ['bhyve']:
            img_dest = _zfs_image_create(
                vm_name=name,
                pool=_disk.get('pool'),
                disk_name=_disk.get('name'),
                disk_size=_disk.get('size'),
                disk_image_name=_disk.get('image'),
                hostname_property_name=_disk.get('hostname_property'),
                sparse_volume=_disk.get('sparse_volume'),
            )

        else:
            # Unknown hypervisor
            raise SaltInvocationError(
                'Unsupported hypervisor when handling disk image: {0}'
                .format(hypervisor)
            )

    log.debug('Generating VM XML')

    if enable_vnc:
        salt.utils.versions.warn_until(
            'Sodium',
            '\'enable_vnc\' parameter has been deprecated in favor of '
            '\'graphics\'. Use graphics={\'type\': \'vnc\'} for the same behavior. '
            '\'enable_vnc\' will be removed in {version}. ')
        graphics = {'type': 'vnc'}

    if os_type is None:
        os_type = 'hvm' if 'hvm' in os_types else os_types[0]
    if arch is None:
        arch = 'x86_64' if 'x86_64' in arches else arches[0]

    vm_xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, os_type, arch, graphics, loader, **kwargs)
    conn = __get_conn(**kwargs)
    try:
        conn.defineXML(vm_xml)
    except libvirtError as err:
        # check if failure is due to this domain already existing
        if "domain '{}' already exists".format(name) in six.text_type(err):
            # continue on to seeding
            log.warning(err)
        else:
            conn.close()
            raise err  # a real error we should report upwards

    if start:
        log.debug('Starting VM %s', name)
        _get_domain(conn, name).create()
    conn.close()

    return True