PK œqhYî¶J‚ßFßF)nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/ $#$#$#

Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64
IP: 209.182.202.254
Choose File :

Url:
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/smartos_vmadm.py

"""
Module for running vmadm command on SmartOS
"""

import logging
import os
import shlex

import salt.utils.args
import salt.utils.files
import salt.utils.json
import salt.utils.path
import salt.utils.platform
import salt.utils.stringutils
from salt.utils.odict import OrderedDict

log = logging.getLogger(__name__)

# Function aliases
__func_alias__ = {"list_vms": "list"}

# Define the module's virtual name
__virtualname__ = "vmadm"


def __virtual__():
    """
    Provides vmadm on SmartOS
    """
    if (
        salt.utils.platform.is_smartos_globalzone()
        and salt.utils.path.which("vmadm")
        and salt.utils.path.which("zfs")
    ):
        return __virtualname__
    return (
        False,
        f"{__virtualname__} module can only be loaded on SmartOS compute nodes",
    )


def _exit_status(retcode):
    """
    Translate exit status of vmadm
    """
    ret = {
        0: "Successful completion.",
        1: "An error occurred.",
        2: "Usage error.",
    }
    return ret[retcode]


def _create_update_from_file(mode="create", uuid=None, path=None):
    """
    Create vm from file
    """
    ret = {}
    if not os.path.isfile(path) or path is None:
        ret["Error"] = f"File ({path}) does not exists!"
        return ret
    # vmadm validate create|update [-f <filename>]
    cmd = "vmadm validate {mode} {brand} -f {path}".format(
        mode=mode, brand=get(uuid)["brand"] if uuid is not None else "", path=path
    )
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = _exit_status(retcode)
        if "stderr" in res:
            if res["stderr"][0] == "{":
                ret["Error"] = salt.utils.json.loads(res["stderr"])
            else:
                ret["Error"] = res["stderr"]
        return ret
    # vmadm create|update [-f <filename>]
    cmd = "vmadm {mode} {uuid} -f {path}".format(
        mode=mode, uuid=uuid if uuid is not None else "", path=path
    )
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = _exit_status(retcode)
        if "stderr" in res:
            if res["stderr"][0] == "{":
                ret["Error"] = salt.utils.json.loads(res["stderr"])
            else:
                ret["Error"] = res["stderr"]
        return ret
    else:
        if res["stderr"].startswith("Successfully created VM"):
            return res["stderr"][24:]
    return True


def _create_update_from_cfg(mode="create", uuid=None, vmcfg=None):
    """
    Create vm from configuration
    """
    ret = {}

    # write json file
    vmadm_json_file = __salt__["temp.file"](prefix="vmadm-")
    with salt.utils.files.fopen(vmadm_json_file, "w") as vmadm_json:
        salt.utils.json.dump(vmcfg, vmadm_json)

    # vmadm validate create|update [-f <filename>]
    cmd = "vmadm validate {mode} {brand} -f {vmadm_json_file}".format(
        mode=mode,
        brand=get(uuid)["brand"] if uuid is not None else "",
        vmadm_json_file=vmadm_json_file,
    )
    res = __salt__["cmd.run_all"](cmd, python_shell=True)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = _exit_status(retcode)
        if "stderr" in res:
            if res["stderr"][0] == "{":
                ret["Error"] = salt.utils.json.loads(res["stderr"])
            else:
                ret["Error"] = res["stderr"]
        return ret
    # vmadm create|update [-f <filename>]
    cmd = "vmadm {mode} {uuid} -f {vmadm_json_file}".format(
        mode=mode,
        uuid=uuid if uuid is not None else "",
        vmadm_json_file=vmadm_json_file,
    )
    res = __salt__["cmd.run_all"](cmd, python_shell=True)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = _exit_status(retcode)
        if "stderr" in res:
            if res["stderr"][0] == "{":
                ret["Error"] = salt.utils.json.loads(res["stderr"])
            else:
                ret["Error"] = res["stderr"]
        return ret
    else:
        # cleanup json file (only when successful to help troubleshooting)
        salt.utils.files.safe_rm(vmadm_json_file)

        # return uuid
        if res["stderr"].startswith("Successfully created VM"):
            return res["stderr"][24:]

    return True


def start(vm, options=None, key="uuid"):
    """
    Start a vm

    vm : string
        vm to be started
    options : string
        optional additional options
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.start 186da9ab-7392-4f55-91a5-b8f1fe770543
        salt '*' vmadm.start 186da9ab-7392-4f55-91a5-b8f1fe770543 'order=c,once=d cdrom=/path/to/image.iso,ide'
        salt '*' vmadm.start vm=nacl key=alias
        salt '*' vmadm.start vm=nina.example.org key=hostname
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm start <uuid> [option=value ...]
    cmd = "vmadm start {uuid} {options}".format(
        uuid=vm, options=options if options else ""
    )
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def stop(vm, force=False, key="uuid"):
    """
    Stop a vm

    vm : string
        vm to be stopped
    force : boolean
        force stop of vm if true
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.stop 186da9ab-7392-4f55-91a5-b8f1fe770543
        salt '*' vmadm.stop 186da9ab-7392-4f55-91a5-b8f1fe770543 True
        salt '*' vmadm.stop vm=nacl key=alias
        salt '*' vmadm.stop vm=nina.example.org key=hostname
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm stop <uuid> [-F]
    cmd = "vmadm stop {force} {uuid}".format(force="-F" if force else "", uuid=vm)
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = _exit_status(retcode)
        return ret
    return True


def reboot(vm, force=False, key="uuid"):
    """
    Reboot a vm

    vm : string
        vm to be rebooted
    force : boolean
        force reboot of vm if true
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.reboot 186da9ab-7392-4f55-91a5-b8f1fe770543
        salt '*' vmadm.reboot 186da9ab-7392-4f55-91a5-b8f1fe770543 True
        salt '*' vmadm.reboot vm=nacl key=alias
        salt '*' vmadm.reboot vm=nina.example.org key=hostname
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm reboot <uuid> [-F]
    cmd = "vmadm reboot {force} {uuid}".format(force="-F" if force else "", uuid=vm)
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def list_vms(search=None, sort=None, order="uuid,type,ram,state,alias", keyed=True):
    """
    Return a list of VMs

    search : string
        vmadm filter property
    sort : string
        vmadm sort (-s) property
    order : string
        vmadm order (-o) property -- Default: uuid,type,ram,state,alias
    keyed : boolean
        specified if the output should be an array (False) or dict (True)
            For a dict the key is the first item from the order parameter.
            Note: If key is not unique last vm wins.

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.list
        salt '*' vmadm.list order=alias,ram,cpu_cap sort=-ram,-cpu_cap
        salt '*' vmadm.list search='type=KVM'
    """
    ret = {}
    # vmadm list [-p] [-H] [-o field,...] [-s field,...] [field=value ...]
    cmd = "vmadm list -p -H {order} {sort} {search}".format(
        order=f"-o {order}" if order else "",
        sort=f"-s {sort}" if sort else "",
        search=search if search else "",
    )
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    result = OrderedDict() if keyed else []
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret

    fields = order.split(",")

    for vm in res["stdout"].splitlines():
        vm_data = OrderedDict()
        vm = vm.split(":")
        if keyed:
            for field in fields:
                if fields.index(field) == 0:
                    continue
                vm_data[field.strip()] = vm[fields.index(field)].strip()
            result[vm[0]] = vm_data
        else:
            if len(vm) > 1:
                for field in fields:
                    vm_data[field.strip()] = vm[fields.index(field)].strip()
            else:
                vm_data = vm[0]
            result.append(vm_data)
    return result


def lookup(search=None, order=None, one=False):
    """
    Return a list of VMs using lookup

    search : string
        vmadm filter property
    order : string
        vmadm order (-o) property -- Default: uuid,type,ram,state,alias
    one : boolean
        return only one result (vmadm's -1)

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.lookup search='state=running'
        salt '*' vmadm.lookup search='state=running' order=uuid,alias,hostname
        salt '*' vmadm.lookup search='alias=nacl' one=True
    """
    ret = {}
    # vmadm lookup [-j|-1] [-o field,...] [field=value ...]
    cmd = "vmadm lookup {one} {order} {search}".format(
        one="-1" if one else "-j",
        order=f"-o {order}" if order else "",
        search=search if search else "",
    )
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    result = []
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret

    if one:
        result = res["stdout"]
    else:
        for vm in salt.utils.json.loads(res["stdout"]):
            result.append(vm)

    return result


def sysrq(vm, action="nmi", key="uuid"):
    """
    Send non-maskable interrupt to vm or capture a screenshot

    vm : string
        vm to be targeted
    action : string
        nmi or screenshot -- Default: nmi
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.sysrq 186da9ab-7392-4f55-91a5-b8f1fe770543 nmi
        salt '*' vmadm.sysrq 186da9ab-7392-4f55-91a5-b8f1fe770543 screenshot
        salt '*' vmadm.sysrq nacl nmi key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    if action not in ["nmi", "screenshot"]:
        ret["Error"] = "Action must be either nmi or screenshot"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm sysrq <uuid> <nmi|screenshot>
    cmd = f"vmadm sysrq {vm} {action}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def delete(vm, key="uuid"):
    """
    Delete a vm

    vm : string
        vm to be deleted
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.delete 186da9ab-7392-4f55-91a5-b8f1fe770543
        salt '*' vmadm.delete nacl key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm delete <uuid>
    cmd = f"vmadm delete {vm}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def get(vm, key="uuid"):
    """
    Output the JSON object describing a VM

    vm : string
        vm to be targeted
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.get 186da9ab-7392-4f55-91a5-b8f1fe770543
        salt '*' vmadm.get nacl key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm get <uuid>
    cmd = f"vmadm get {vm}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return salt.utils.json.loads(res["stdout"])


def info(vm, info_type="all", key="uuid"):
    """
    Lookup info on running kvm

    vm : string
        vm to be targeted
    info_type : string [all|block|blockstats|chardev|cpus|kvm|pci|spice|version|vnc]
        info type to return
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.info 186da9ab-7392-4f55-91a5-b8f1fe770543
        salt '*' vmadm.info 186da9ab-7392-4f55-91a5-b8f1fe770543 vnc
        salt '*' vmadm.info nacl key=alias
        salt '*' vmadm.info nacl vnc key=alias
    """
    ret = {}
    if info_type not in [
        "all",
        "block",
        "blockstats",
        "chardev",
        "cpus",
        "kvm",
        "pci",
        "spice",
        "version",
        "vnc",
    ]:
        ret["Error"] = "Requested info_type is not available"
        return ret
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm info <uuid> [type,...]
    cmd = f"vmadm info {vm} {info_type}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return salt.utils.json.loads(res["stdout"])


def create_snapshot(vm, name, key="uuid"):
    """
    Create snapshot of a vm

    vm : string
        vm to be targeted
    name : string
        snapshot name
            The snapname must be 64 characters or less
            and must only contain alphanumeric characters and
            characters in the set [-_.:%] to comply with ZFS restrictions.
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.create_snapshot 186da9ab-7392-4f55-91a5-b8f1fe770543 baseline
        salt '*' vmadm.create_snapshot nacl baseline key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    vmobj = get(vm)
    if "datasets" in vmobj:
        ret["Error"] = "VM cannot have datasets"
        return ret
    if vmobj["brand"] in ["kvm"]:
        ret["Error"] = "VM must be of type OS"
        return ret
    if vmobj["zone_state"] not in ["running"]:  # work around a vmadm bug
        ret["Error"] = "VM must be running to take a snapshot"
        return ret
    # vmadm create-snapshot <uuid> <snapname>
    cmd = f"vmadm create-snapshot {vm} {name}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def delete_snapshot(vm, name, key="uuid"):
    """
    Delete snapshot of a vm

    vm : string
        vm to be targeted
    name : string
        snapshot name
            The snapname must be 64 characters or less
            and must only contain alphanumeric characters and
            characters in the set [-_.:%] to comply with ZFS restrictions.
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.delete_snapshot 186da9ab-7392-4f55-91a5-b8f1fe770543 baseline
        salt '*' vmadm.delete_snapshot nacl baseline key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    vmobj = get(vm)
    if "datasets" in vmobj:
        ret["Error"] = "VM cannot have datasets"
        return ret
    if vmobj["brand"] in ["kvm"]:
        ret["Error"] = "VM must be of type OS"
        return ret
    # vmadm delete-snapshot <uuid> <snapname>
    cmd = f"vmadm delete-snapshot {vm} {name}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def rollback_snapshot(vm, name, key="uuid"):
    """
    Rollback snapshot of a vm

    vm : string
        vm to be targeted
    name : string
        snapshot name
            The snapname must be 64 characters or less
            and must only contain alphanumeric characters and
            characters in the set [-_.:%] to comply with ZFS restrictions.
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.rollback_snapshot 186da9ab-7392-4f55-91a5-b8f1fe770543 baseline
        salt '*' vmadm.rollback_snapshot nacl baseline key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    vmobj = get(vm)
    if "datasets" in vmobj:
        ret["Error"] = "VM cannot have datasets"
        return ret
    if vmobj["brand"] in ["kvm"]:
        ret["Error"] = "VM must be of type OS"
        return ret
    # vmadm rollback-snapshot <uuid> <snapname>
    cmd = f"vmadm rollback-snapshot {vm} {name}"
    res = __salt__["cmd.run_all"](cmd)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def reprovision(vm, image, key="uuid"):
    """
    Reprovision a vm

    vm : string
        vm to be reprovisioned
    image : string
        uuid of new image
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.reprovision 186da9ab-7392-4f55-91a5-b8f1fe770543 c02a2044-c1bd-11e4-bd8c-dfc1db8b0182
        salt '*' vmadm.reprovision nacl c02a2044-c1bd-11e4-bd8c-dfc1db8b0182 key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    if image not in __salt__["imgadm.list"]():
        ret["Error"] = f"Image ({image}) is not present on this host"
        return ret
    # vmadm reprovision <uuid> [-f <filename>]
    cmd = "echo {image} | vmadm reprovision {uuid}".format(
        uuid=salt.utils.stringutils.to_unicode(vm),
        image=shlex.quote(salt.utils.json.dumps({"image_uuid": image})),
    )
    res = __salt__["cmd.run_all"](cmd, python_shell=True)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True


def create(from_file=None, **kwargs):
    """
    Create a new vm

    from_file : string
        json file to create the vm from -- if present, all other options will be ignored
    kwargs : string|int|...
        options to set for the vm

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.create from_file=/tmp/new_vm.json
        salt '*' vmadm.create image_uuid='...' alias='...' nics='[{ "nic_tag": "admin", "ip": "198.51.100.123", ...}, {...}]' [...]
    """
    ret = {}
    # prepare vmcfg
    vmcfg = {}
    kwargs = salt.utils.args.clean_kwargs(**kwargs)
    for k, v in kwargs.items():
        vmcfg[k] = v

    if from_file:
        return _create_update_from_file("create", path=from_file)
    else:
        return _create_update_from_cfg("create", vmcfg=vmcfg)


def update(vm, from_file=None, key="uuid", **kwargs):
    """
    Update a new vm

    vm : string
        vm to be updated
    from_file : string
        json file to update the vm with -- if present, all other options will be ignored
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter
    kwargs : string|int|...
        options to update for the vm

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.update vm=186da9ab-7392-4f55-91a5-b8f1fe770543 from_file=/tmp/new_vm.json
        salt '*' vmadm.update vm=nacl key=alias from_file=/tmp/new_vm.json
        salt '*' vmadm.update vm=186da9ab-7392-4f55-91a5-b8f1fe770543 max_physical_memory=1024
    """
    ret = {}
    # prepare vmcfg
    vmcfg = {}
    kwargs = salt.utils.args.clean_kwargs(**kwargs)
    for k, v in kwargs.items():
        vmcfg[k] = v

    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    uuid = lookup(f"{key}={vm}", one=True)
    if "Error" in uuid:
        return uuid

    if from_file:
        return _create_update_from_file("update", uuid, path=from_file)
    else:
        return _create_update_from_cfg("update", uuid, vmcfg=vmcfg)


def send(vm, target, key="uuid"):
    """
    Send a vm to a directory

    vm : string
        vm to be sent
    target : string
        target directory
    key : string [uuid|alias|hostname]
        value type of 'vm' parameter

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.send 186da9ab-7392-4f55-91a5-b8f1fe770543 /opt/backups
        salt '*' vmadm.send vm=nacl target=/opt/backups key=alias
    """
    ret = {}
    if key not in ["uuid", "alias", "hostname"]:
        ret["Error"] = "Key must be either uuid, alias or hostname"
        return ret
    if not os.path.isdir(target):
        ret["Error"] = "Target must be a directory or host"
        return ret
    vm = lookup(f"{key}={vm}", one=True)
    if "Error" in vm:
        return vm
    # vmadm send <uuid> [target]
    cmd = "vmadm send {uuid} > {target}".format(
        uuid=vm, target=os.path.join(target, f"{vm}.vmdata")
    )
    res = __salt__["cmd.run_all"](cmd, python_shell=True)
    retcode = res["retcode"]
    if retcode != 0:
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    vmobj = get(vm)
    if "datasets" not in vmobj:
        return True
    log.warning("one or more datasets detected, this is not supported!")
    log.warning("trying to zfs send datasets...")
    for dataset in vmobj["datasets"]:
        name = dataset.split("/")
        name = name[-1]
        cmd = "zfs send {dataset} > {target}".format(
            dataset=dataset,
            target=os.path.join(target, f"{vm}-{name}.zfsds"),
        )
        res = __salt__["cmd.run_all"](cmd, python_shell=True)
        retcode = res["retcode"]
        if retcode != 0:
            ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
            return ret
    return True


def receive(uuid, source):
    """
    Receive a vm from a directory

    uuid : string
        uuid of vm to be received
    source : string
        source directory

    CLI Example:

    .. code-block:: bash

        salt '*' vmadm.receive 186da9ab-7392-4f55-91a5-b8f1fe770543 /opt/backups
    """
    ret = {}
    if not os.path.isdir(source):
        ret["Error"] = "Source must be a directory or host"
        return ret
    if not os.path.exists(os.path.join(source, f"{uuid}.vmdata")):
        ret["Error"] = f"Unknow vm with uuid in {source}"
        return ret
    # vmadm receive
    cmd = "vmadm receive < {source}".format(
        source=os.path.join(source, f"{uuid}.vmdata")
    )
    res = __salt__["cmd.run_all"](cmd, python_shell=True)
    retcode = res["retcode"]
    if retcode != 0 and not res["stderr"].endswith("datasets"):
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    vmobj = get(uuid)
    if "datasets" not in vmobj:
        return True
    log.warning("one or more datasets detected, this is not supported!")
    log.warning("trying to restore datasets, mountpoints will need to be set again...")
    for dataset in vmobj["datasets"]:
        name = dataset.split("/")
        name = name[-1]
        cmd = "zfs receive {dataset} < {source}".format(
            dataset=dataset,
            source=os.path.join(source, f"{uuid}-{name}.zfsds"),
        )
        res = __salt__["cmd.run_all"](cmd, python_shell=True)
        retcode = res["retcode"]
        if retcode != 0:
            ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
            return ret
    cmd = f"vmadm install {uuid}"
    res = __salt__["cmd.run_all"](cmd, python_shell=True)
    retcode = res["retcode"]
    if retcode != 0 and not res["stderr"].endswith("datasets"):
        ret["Error"] = res["stderr"] if "stderr" in res else _exit_status(retcode)
        return ret
    return True