This decorator is used to make the execution module functions
    available outside a proxy minion, or when running inside a proxy
    minion. If we are running in a proxy, retrieve the connection details
    from the __proxy__ injected variable.  If we are not, then
    use the connection information from the opts.
    :param func:
    :return:

def proxy_napalm_wrap(func):
    '''
    This decorator is used to make the execution module functions
    available outside a proxy minion, or when running inside a proxy
    minion. If we are running in a proxy, retrieve the connection details
    from the __proxy__ injected variable.  If we are not, then
    use the connection information from the opts.
    :param func:
    :return:
    '''
    @wraps(func)
    def func_wrapper(*args, **kwargs):
        wrapped_global_namespace = func.__globals__
        # get __opts__ and __proxy__ from func_globals
        proxy = wrapped_global_namespace.get('__proxy__')
        opts = copy.deepcopy(wrapped_global_namespace.get('__opts__'))
        # in any case, will inject the `napalm_device` global
        # the execution modules will make use of this variable from now on
        # previously they were accessing the device properties through the __proxy__ object
        always_alive = opts.get('proxy', {}).get('always_alive', True)
        # force_reconnect is a magic keyword arg that allows one to establish
        # a separate connection to the network device running under an always
        # alive Proxy Minion, using new credentials (overriding the ones
        # configured in the opts / pillar.
        force_reconnect = kwargs.get('force_reconnect', False)
        if force_reconnect:
            log.debug('Usage of reconnect force detected')
            log.debug('Opts before merging')
            log.debug(opts['proxy'])
            opts['proxy'].update(**kwargs)
            log.debug('Opts after merging')
            log.debug(opts['proxy'])
        if is_proxy(opts) and always_alive:
            # if it is running in a NAPALM Proxy and it's using the default
            # always alive behaviour, will get the cached copy of the network
            # device object which should preserve the connection.
            if force_reconnect:
                wrapped_global_namespace['napalm_device'] = get_device(opts)
            else:
                wrapped_global_namespace['napalm_device'] = proxy['napalm.get_device']()
        elif is_proxy(opts) and not always_alive:
            # if still proxy, but the user does not want the SSH session always alive
            # get a new device instance
            # which establishes a new connection
            # which is closed just before the call() function defined above returns
            if 'inherit_napalm_device' not in kwargs or ('inherit_napalm_device' in kwargs and
                                                         not kwargs['inherit_napalm_device']):
                # try to open a new connection
                # but only if the function does not inherit the napalm driver
                # for configuration management this is very important,
                # in order to make sure we are editing the same session.
                try:
                    wrapped_global_namespace['napalm_device'] = get_device(opts)
                except napalm_base.exceptions.ConnectionException as nce:
                    log.error(nce)
                    return '{base_msg}. See log for details.'.format(
                        base_msg=six.text_type(nce.msg)
                    )
            else:
                # in case the `inherit_napalm_device` is set
                # and it also has a non-empty value,
                # the global var `napalm_device` will be overridden.
                # this is extremely important for configuration-related features
                # as all actions must be issued within the same configuration session
                # otherwise we risk to open multiple sessions
                wrapped_global_namespace['napalm_device'] = kwargs['inherit_napalm_device']
        else:
            # if not a NAPLAM proxy
            # thus it is running on a regular minion, directly on the network device
            # or another flavour of Minion from where we can invoke arbitrary
            # NAPALM commands
            # get __salt__ from func_globals
            log.debug('Not running in a NAPALM Proxy Minion')
            _salt_obj = wrapped_global_namespace.get('__salt__')
            napalm_opts = _salt_obj['config.get']('napalm', {})
            napalm_inventory = _salt_obj['config.get']('napalm_inventory', {})
            log.debug('NAPALM opts found in the Minion config')
            log.debug(napalm_opts)
            clean_kwargs = salt.utils.args.clean_kwargs(**kwargs)
            napalm_opts.update(clean_kwargs)  # no need for deeper merge
            log.debug('Merging the found opts with the CLI args')
            log.debug(napalm_opts)
            host = napalm_opts.get('host') or napalm_opts.get('hostname') or\
                   napalm_opts.get('fqdn') or napalm_opts.get('ip')
            if host and napalm_inventory and isinstance(napalm_inventory, dict) and\
               host in napalm_inventory:
                inventory_opts = napalm_inventory[host]
                log.debug('Found %s in the NAPALM inventory:', host)
                log.debug(inventory_opts)
                napalm_opts.update(inventory_opts)
                log.debug('Merging the config for %s with the details found in the napalm inventory:', host)
                log.debug(napalm_opts)
            opts = copy.deepcopy(opts)  # make sure we don't override the original
            # opts, but just inject the CLI args from the kwargs to into the
            # object manipulated by ``get_device_opts`` to extract the
            # connection details, then use then to establish the connection.
            opts['napalm'] = napalm_opts
            if 'inherit_napalm_device' not in kwargs or ('inherit_napalm_device' in kwargs and
                                                         not kwargs['inherit_napalm_device']):
                # try to open a new connection
                # but only if the function does not inherit the napalm driver
                # for configuration management this is very important,
                # in order to make sure we are editing the same session.
                try:
                    wrapped_global_namespace['napalm_device'] = get_device(opts, salt_obj=_salt_obj)
                except napalm_base.exceptions.ConnectionException as nce:
                    log.error(nce)
                    return '{base_msg}. See log for details.'.format(
                        base_msg=six.text_type(nce.msg)
                    )
            else:
                # in case the `inherit_napalm_device` is set
                # and it also has a non-empty value,
                # the global var `napalm_device` will be overridden.
                # this is extremely important for configuration-related features
                # as all actions must be issued within the same configuration session
                # otherwise we risk to open multiple sessions
                wrapped_global_namespace['napalm_device'] = kwargs['inherit_napalm_device']
        if not_always_alive(opts):
            # inject the __opts__ only when not always alive
            # otherwise, we don't want to overload the always-alive proxies
            wrapped_global_namespace['napalm_device']['__opts__'] = opts
        ret = func(*args, **kwargs)
        if force_reconnect:
            log.debug('That was a forced reconnect, gracefully clearing up')
            device = wrapped_global_namespace['napalm_device']
            closing = call(device, 'close', __retry=False)
        return ret
    return func_wrapper