List all detectable jobs and associated functions

    ext_source
        If provided, specifies which external job cache to use.

    **FILTER OPTIONS**

    .. note::
        If more than one of the below options are used, only jobs which match
        *all* of the filters will be returned.

    search_metadata
        Specify a dictionary to match to the job's metadata. If any of the
        key-value pairs in this dictionary match, the job will be returned.
        Example:

        .. code-block:: bash

            salt-run jobs.list_jobs search_metadata='{"foo": "bar", "baz": "qux"}'

    search_function
        Can be passed as a string or a list. Returns jobs which match the
        specified function. Globbing is allowed. Example:

        .. code-block:: bash

            salt-run jobs.list_jobs search_function='test.*'
            salt-run jobs.list_jobs search_function='["test.*", "pkg.install"]'

        .. versionchanged:: 2015.8.8
            Multiple targets can now also be passed as a comma-separated list.
            For example:

            .. code-block:: bash

                salt-run jobs.list_jobs search_function='test.*,pkg.install'

    search_target
        Can be passed as a string or a list. Returns jobs which match the
        specified minion name. Globbing is allowed. Example:

        .. code-block:: bash

            salt-run jobs.list_jobs search_target='*.mydomain.tld'
            salt-run jobs.list_jobs search_target='["db*", "myminion"]'

        .. versionchanged:: 2015.8.8
            Multiple targets can now also be passed as a comma-separated list.
            For example:

            .. code-block:: bash

                salt-run jobs.list_jobs search_target='db*,myminion'

    start_time
        Accepts any timestamp supported by the dateutil_ Python module (if this
        module is not installed, this argument will be ignored). Returns jobs
        which started after this timestamp.

    end_time
        Accepts any timestamp supported by the dateutil_ Python module (if this
        module is not installed, this argument will be ignored). Returns jobs
        which started before this timestamp.

    .. _dateutil: https://pypi.python.org/pypi/python-dateutil

    CLI Example:

    .. code-block:: bash

        salt-run jobs.list_jobs
        salt-run jobs.list_jobs search_function='test.*' search_target='localhost' search_metadata='{"bar": "foo"}'
        salt-run jobs.list_jobs start_time='2015, Mar 16 19:00' end_time='2015, Mar 18 22:00'

def list_jobs(ext_source=None,
              outputter=None,
              search_metadata=None,
              search_function=None,
              search_target=None,
              start_time=None,
              end_time=None,
              display_progress=False):
    '''
    List all detectable jobs and associated functions

    ext_source
        If provided, specifies which external job cache to use.

    **FILTER OPTIONS**

    .. note::
        If more than one of the below options are used, only jobs which match
        *all* of the filters will be returned.

    search_metadata
        Specify a dictionary to match to the job's metadata. If any of the
        key-value pairs in this dictionary match, the job will be returned.
        Example:

        .. code-block:: bash

            salt-run jobs.list_jobs search_metadata='{"foo": "bar", "baz": "qux"}'

    search_function
        Can be passed as a string or a list. Returns jobs which match the
        specified function. Globbing is allowed. Example:

        .. code-block:: bash

            salt-run jobs.list_jobs search_function='test.*'
            salt-run jobs.list_jobs search_function='["test.*", "pkg.install"]'

        .. versionchanged:: 2015.8.8
            Multiple targets can now also be passed as a comma-separated list.
            For example:

            .. code-block:: bash

                salt-run jobs.list_jobs search_function='test.*,pkg.install'

    search_target
        Can be passed as a string or a list. Returns jobs which match the
        specified minion name. Globbing is allowed. Example:

        .. code-block:: bash

            salt-run jobs.list_jobs search_target='*.mydomain.tld'
            salt-run jobs.list_jobs search_target='["db*", "myminion"]'

        .. versionchanged:: 2015.8.8
            Multiple targets can now also be passed as a comma-separated list.
            For example:

            .. code-block:: bash

                salt-run jobs.list_jobs search_target='db*,myminion'

    start_time
        Accepts any timestamp supported by the dateutil_ Python module (if this
        module is not installed, this argument will be ignored). Returns jobs
        which started after this timestamp.

    end_time
        Accepts any timestamp supported by the dateutil_ Python module (if this
        module is not installed, this argument will be ignored). Returns jobs
        which started before this timestamp.

    .. _dateutil: https://pypi.python.org/pypi/python-dateutil

    CLI Example:

    .. code-block:: bash

        salt-run jobs.list_jobs
        salt-run jobs.list_jobs search_function='test.*' search_target='localhost' search_metadata='{"bar": "foo"}'
        salt-run jobs.list_jobs start_time='2015, Mar 16 19:00' end_time='2015, Mar 18 22:00'

    '''
    returner = _get_returner((
        __opts__['ext_job_cache'],
        ext_source,
        __opts__['master_job_cache']
    ))
    if display_progress:
        __jid_event__.fire_event(
            {'message': 'Querying returner {0} for jobs.'.format(returner)},
            'progress'
        )
    mminion = salt.minion.MasterMinion(__opts__)

    ret = mminion.returners['{0}.get_jids'.format(returner)]()

    mret = {}
    for item in ret:
        _match = True
        if search_metadata:
            _match = False
            if 'Metadata' in ret[item]:
                if isinstance(search_metadata, dict):
                    for key in search_metadata:
                        if key in ret[item]['Metadata']:
                            if ret[item]['Metadata'][key] == search_metadata[key]:
                                _match = True
                else:
                    log.info('The search_metadata parameter must be specified'
                             ' as a dictionary.  Ignoring.')
        if search_target and _match:
            _match = False
            if 'Target' in ret[item]:
                targets = ret[item]['Target']
                if isinstance(targets, six.string_types):
                    targets = [targets]
                for target in targets:
                    for key in salt.utils.args.split_input(search_target):
                        if fnmatch.fnmatch(target, key):
                            _match = True

        if search_function and _match:
            _match = False
            if 'Function' in ret[item]:
                for key in salt.utils.args.split_input(search_function):
                    if fnmatch.fnmatch(ret[item]['Function'], key):
                        _match = True

        if start_time and _match:
            _match = False
            if DATEUTIL_SUPPORT:
                parsed_start_time = dateutil_parser.parse(start_time)
                _start_time = dateutil_parser.parse(ret[item]['StartTime'])
                if _start_time >= parsed_start_time:
                    _match = True
            else:
                log.error(
                    '\'dateutil\' library not available, skipping start_time '
                    'comparison.'
                )

        if end_time and _match:
            _match = False
            if DATEUTIL_SUPPORT:
                parsed_end_time = dateutil_parser.parse(end_time)
                _start_time = dateutil_parser.parse(ret[item]['StartTime'])
                if _start_time <= parsed_end_time:
                    _match = True
            else:
                log.error(
                    '\'dateutil\' library not available, skipping end_time '
                    'comparison.'
                )

        if _match:
            mret[item] = ret[item]

    if outputter:
        return {'outputter': outputter, 'data': mret}
    else:
        return mret