Decorator that connects to a target system (vCenter or ESXi host) using the
    proxy details and passes the connection (vim.ServiceInstance) to
    the decorated function.

    Supported proxies: esxi, esxcluster, esxdatacenter.

    Notes:
        1. The decorated function must have a ``service_instance`` parameter
        or a ``**kwarg`` type argument (name of argument is not important);
        2. If the ``service_instance`` parameter is already defined, the value
        is passed through to the decorated function;
        3. If the ``service_instance`` parameter in not defined, the
        connection is created using the proxy details and the service instance
        is returned.

    CLI Example:
        None, this is a decorator

def gets_service_instance_via_proxy(fn):
    '''
    Decorator that connects to a target system (vCenter or ESXi host) using the
    proxy details and passes the connection (vim.ServiceInstance) to
    the decorated function.

    Supported proxies: esxi, esxcluster, esxdatacenter.

    Notes:
        1. The decorated function must have a ``service_instance`` parameter
        or a ``**kwarg`` type argument (name of argument is not important);
        2. If the ``service_instance`` parameter is already defined, the value
        is passed through to the decorated function;
        3. If the ``service_instance`` parameter in not defined, the
        connection is created using the proxy details and the service instance
        is returned.

    CLI Example:
        None, this is a decorator
    '''
    fn_name = fn.__name__
    try:
        arg_names, args_name, kwargs_name, default_values, _, _, _ = \
                inspect.getfullargspec(fn)
    except AttributeError:
        # Fallback to Python 2.7
        arg_names, args_name, kwargs_name, default_values = \
                inspect.getargspec(fn)
    default_values = default_values if default_values is not None else []

    @wraps(fn)
    def _gets_service_instance_via_proxy(*args, **kwargs):
        if 'service_instance' not in arg_names and not kwargs_name:
            raise CommandExecutionError(
                'Function {0} must have either a \'service_instance\', or a '
                '\'**kwargs\' type parameter'.format(fn_name))
        connection_details = _get_proxy_connection_details()
        # Figure out how to pass in the connection value
        local_service_instance = None
        if 'service_instance' in arg_names:
            idx = arg_names.index('service_instance')
            if idx >= len(arg_names) - len(default_values):
                # 'service_instance' has a default value:
                #     we check if we need to instantiate it or
                #     pass it through
                #
                # NOTE: if 'service_instance' doesn't have a default value
                # it must be explicitly set in the function call so we pass it
                # through

                # There are two cases:
                #   1. service_instance was passed in as a positional parameter
                #   2. service_instance was passed in as a named paramter
                if len(args) > idx:
                    # case 1: The call was made with enough positional
                    # parameters to include 'service_instance'
                    if not args[idx]:
                        local_service_instance = \
                                salt.utils.vmware.get_service_instance(
                                    *connection_details)
                        # Tuples are immutable, so if we want to change what
                        # was passed in, we need to first convert to a list.
                        args = list(args)
                        args[idx] = local_service_instance
                else:
                    # case 2: Not enough positional parameters so
                    # 'service_instance' must be a named parameter
                    if not kwargs.get('service_instance'):
                        local_service_instance = \
                                salt.utils.vmware.get_service_instance(
                                    *connection_details)
                        kwargs['service_instance'] = local_service_instance
        else:
            # 'service_instance' is not a paremter in the function definition
            # but it will be caught by the **kwargs parameter
            if not kwargs.get('service_instance'):
                local_service_instance = \
                        salt.utils.vmware.get_service_instance(
                            *connection_details)
                kwargs['service_instance'] = local_service_instance
        try:
            ret = fn(*args, **salt.utils.args.clean_kwargs(**kwargs))
            # Disconnect if connected in the decorator
            if local_service_instance:
                salt.utils.vmware.disconnect(local_service_instance)
            return ret
        except Exception as e:
            # Disconnect if connected in the decorator
            if local_service_instance:
                salt.utils.vmware.disconnect(local_service_instance)
            # raise original exception and traceback
            six.reraise(*sys.exc_info())
    return _gets_service_instance_via_proxy