Instruction:
Implement a function in Python 3 to
get the yum config options and values from the yum. conf file.

Response:
def _get_yum_config():
    '''
    Returns a dict representing the yum config options and values.

    We try to pull all of the yum config options into a standard dict object.
    This is currently only used to get the reposdir settings, but could be used
    for other things if needed.

    If the yum python library is available, use that, which will give us all of
    the options, including all of the defaults not specified in the yum config.
    Additionally, they will all be of the correct object type.

    If the yum library is not available, we try to read the yum.conf
    directly ourselves with a minimal set of "defaults".
    '''
    # in case of any non-fatal failures, these defaults will be used
    conf = {
        'reposdir': ['/etc/yum/repos.d', '/etc/yum.repos.d'],
    }

    if HAS_YUM:
        try:
            yb = yum.YumBase()
            yb.preconf.init_plugins = False
            for name, value in six.iteritems(yb.conf):
                conf[name] = value
        except (AttributeError, yum.Errors.ConfigError) as exc:
            raise CommandExecutionError(
                'Could not query yum config: {0}'.format(exc)
            )
        except yum.Errors.YumBaseError as yum_base_error:
            raise CommandExecutionError(
                'Error accessing yum or rpmdb: {0}'.format(yum_base_error)
            )
    else:
        # fall back to parsing the config ourselves
        # Look for the config the same order yum does
        fn = None
        paths = ('/etc/yum/yum.conf', '/etc/yum.conf', '/etc/dnf/dnf.conf', '/etc/tdnf/tdnf.conf')
        for path in paths:
            if os.path.exists(path):
                fn = path
                break

        if not fn:
            raise CommandExecutionError(
                'No suitable yum config file found in: {0}'.format(paths)
            )

        cp = configparser.ConfigParser()
        try:
            cp.read(fn)
        except (IOError, OSError) as exc:
            raise CommandExecutionError(
                'Unable to read from {0}: {1}'.format(fn, exc)
            )

        if cp.has_section('main'):
            for opt in cp.options('main'):
                if opt in ('reposdir', 'commands', 'excludes'):
                    # these options are expected to be lists
                    conf[opt] = [x.strip()
                                 for x in cp.get('main', opt).split(',')]
                else:
                    conf[opt] = cp.get('main', opt)
        else:
            log.warning(
                'Could not find [main] section in %s, using internal '
                'defaults',
                fn
            )

    return conf


Instruction:
Can you write a function in Python 3 where it
looks for a specific config variable and return its value

Response:
def _get_yum_config_value(name):
    '''
    Look for a specific config variable and return its value
    '''
    conf = _get_yum_config()
    if name in conf.keys():
        return conf.get(name)
    return None


Instruction:
How would you explain what the following Python 3 function does
def _normalize_basedir(basedir=None):
    '''
    Takes a basedir argument as a string or a list. If the string or list is
    empty, then look up the default from the 'reposdir' option in the yum
    config.

    Returns a list of directories.
    '''
    # if we are passed a string (for backward compatibility), convert to a list
    if isinstance(basedir, six.string_types):
        basedir = [x.strip() for x in basedir.split(',')]

    if basedir is None:
        basedir = []

    # nothing specified, so use the reposdir option as the default
    if not basedir:
        basedir = _get_yum_config_value('reposdir')

    if not isinstance(basedir, list) or not basedir:
        raise SaltInvocationError('Could not determine any repo directories')

    return basedir

Response: Normalizes the basedir argument to a list of directories.


Instruction:
Create a Python 3 function for
normalizing the package name to include architectures and architectures.

Response:
def normalize_name(name):
    '''
    Strips the architecture from the specified package name, if necessary.
    Circumstances where this would be done include:

    * If the arch is 32 bit and the package name ends in a 32-bit arch.
    * If the arch matches the OS arch, or is ``noarch``.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.normalize_name zsh.x86_64
    '''
    try:
        arch = name.rsplit('.', 1)[-1]
        if arch not in salt.utils.pkg.rpm.ARCHES + ('noarch',):
            return name
    except ValueError:
        return name
    if arch in (__grains__['osarch'], 'noarch') \
            or salt.utils.pkg.rpm.check_32(arch, osarch=__grains__['osarch']):
        return name[:-(len(arch) + 1)]
    return name


Instruction:
Can you write a function in Python 3 where it
compares two packages and return the version of the first one.

Response:
def version_cmp(pkg1, pkg2, ignore_epoch=False, **kwargs):
    '''
    .. versionadded:: 2015.5.4

    Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
    pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
    making the comparison.

    ignore_epoch : False
        Set to ``True`` to ignore the epoch when comparing versions

        .. versionadded:: 2015.8.10,2016.3.2

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002'
    '''

    return __salt__['lowpkg.version_cmp'](pkg1, pkg2, ignore_epoch=ignore_epoch)


Instruction:
Here you have a function in Python 3, explain what it does
def list_pkgs(versions_as_list=False, **kwargs):
    '''
    List the packages currently installed as a dict. By default, the dict
    contains versions as a comma separated string::

        {'<package_name>': '<version>[,<version>...]'}

    versions_as_list:
        If set to true, the versions are provided as a list

        {'<package_name>': ['<version>', '<version>']}

    attr:
        If a list of package attributes is specified, returned value will
        contain them in addition to version, eg.::

        {'<package_name>': [{'version' : 'version', 'arch' : 'arch'}]}

        Valid attributes are: ``epoch``, ``version``, ``release``, ``arch``,
        ``install_date``, ``install_date_time_t``.

        If ``all`` is specified, all valid attributes will be returned.

            .. versionadded:: 2018.3.0

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_pkgs
        salt '*' pkg.list_pkgs attr=version,arch
        salt '*' pkg.list_pkgs attr='["version", "arch"]'
    '''
    versions_as_list = salt.utils.data.is_true(versions_as_list)
    # not yet implemented or not applicable
    if any([salt.utils.data.is_true(kwargs.get(x))
            for x in ('removed', 'purge_desired')]):
        return {}

    attr = kwargs.get('attr')
    if attr is not None:
        attr = salt.utils.args.split_input(attr)

    contextkey = 'pkg.list_pkgs'

    if contextkey not in __context__:
        ret = {}
        cmd = ['rpm', '-qa', '--queryformat',
               salt.utils.pkg.rpm.QUERYFORMAT.replace('%{REPOID}', '(none)') + '\n']
        output = __salt__['cmd.run'](cmd,
                                     python_shell=False,
                                     output_loglevel='trace')
        for line in output.splitlines():
            pkginfo = salt.utils.pkg.rpm.parse_pkginfo(
                line,
                osarch=__grains__['osarch']
            )
            if pkginfo is not None:
                # see rpm version string rules available at https://goo.gl/UGKPNd
                pkgver = pkginfo.version
                epoch = ''
                release = ''
                if ':' in pkgver:
                    epoch, pkgver = pkgver.split(":", 1)
                if '-' in pkgver:
                    pkgver, release = pkgver.split("-", 1)
                all_attr = {
                    'epoch': epoch,
                    'version': pkgver,
                    'release': release,
                    'arch': pkginfo.arch,
                    'install_date': pkginfo.install_date,
                    'install_date_time_t': pkginfo.install_date_time_t
                }
                __salt__['pkg_resource.add_pkg'](ret, pkginfo.name, all_attr)

        for pkgname in ret:
            ret[pkgname] = sorted(ret[pkgname], key=lambda d: d['version'])

        __context__[contextkey] = ret

    return __salt__['pkg_resource.format_pkg_list'](
        __context__[contextkey],
        versions_as_list,
        attr)

Response: Return a dict containing the currently installed packages.


Instruction:
How would you explain what the following Python 3 function does
def list_repo_pkgs(*args, **kwargs):
    '''
    .. versionadded:: 2014.1.0
    .. versionchanged:: 2014.7.0
        All available versions of each package are now returned. This required
        a slight modification to the structure of the return dict. The return
        data shown below reflects the updated return dict structure. Note that
        packages which are version-locked using :py:mod:`pkg.hold
        <salt.modules.yumpkg.hold>` will only show the currently-installed
        version, as locking a package will make other versions appear
        unavailable to yum/dnf.
    .. versionchanged:: 2017.7.0
        By default, the versions for each package are no longer organized by
        repository. To get results organized by repository, use
        ``byrepo=True``.

    Returns all available packages. Optionally, package names (and name globs)
    can be passed and the results will be filtered to packages matching those
    names. This is recommended as it speeds up the function considerably.

    .. warning::
        Running this function on RHEL/CentOS 6 and earlier will be more
        resource-intensive, as the version of yum that ships with older
        RHEL/CentOS has no yum subcommand for listing packages from a
        repository. Thus, a ``yum list installed`` and ``yum list available``
        are run, which generates a lot of output, which must then be analyzed
        to determine which package information to include in the return data.

    This function can be helpful in discovering the version or repo to specify
    in a :mod:`pkg.installed <salt.states.pkg.installed>` state.

    The return data will be a dictionary mapping package names to a list of
    version numbers, ordered from newest to oldest. If ``byrepo`` is set to
    ``True``, then the return dictionary will contain repository names at the
    top level, and each repository will map packages to lists of version
    numbers. For example:

    .. code-block:: python

        # With byrepo=False (default)
        {
            'bash': ['4.1.2-15.el6_5.2',
                     '4.1.2-15.el6_5.1',
                     '4.1.2-15.el6_4'],
            'kernel': ['2.6.32-431.29.2.el6',
                       '2.6.32-431.23.3.el6',
                       '2.6.32-431.20.5.el6',
                       '2.6.32-431.20.3.el6',
                       '2.6.32-431.17.1.el6',
                       '2.6.32-431.11.2.el6',
                       '2.6.32-431.5.1.el6',
                       '2.6.32-431.3.1.el6',
                       '2.6.32-431.1.2.0.1.el6',
                       '2.6.32-431.el6']
        }
        # With byrepo=True
        {
            'base': {
                'bash': ['4.1.2-15.el6_4'],
                'kernel': ['2.6.32-431.el6']
            },
            'updates': {
                'bash': ['4.1.2-15.el6_5.2', '4.1.2-15.el6_5.1'],
                'kernel': ['2.6.32-431.29.2.el6',
                           '2.6.32-431.23.3.el6',
                           '2.6.32-431.20.5.el6',
                           '2.6.32-431.20.3.el6',
                           '2.6.32-431.17.1.el6',
                           '2.6.32-431.11.2.el6',
                           '2.6.32-431.5.1.el6',
                           '2.6.32-431.3.1.el6',
                           '2.6.32-431.1.2.0.1.el6']
            }
        }

    fromrepo : None
        Only include results from the specified repo(s). Multiple repos can be
        specified, comma-separated.

    enablerepo (ignored if ``fromrepo`` is specified)
        Specify a disabled package repository (or repositories) to enable.
        (e.g., ``yum --enablerepo='somerepo'``)

        .. versionadded:: 2017.7.0

    disablerepo (ignored if ``fromrepo`` is specified)
        Specify an enabled package repository (or repositories) to disable.
        (e.g., ``yum --disablerepo='somerepo'``)

        .. versionadded:: 2017.7.0

    byrepo : False
        When ``True``, the return data for each package will be organized by
        repository.

        .. versionadded:: 2017.7.0

    cacheonly : False
        When ``True``, the repo information will be retrieved from the cached
        repo metadata. This is equivalent to passing the ``-C`` option to
        yum/dnf.

        .. versionadded:: 2017.7.0

    setopt
        A comma-separated or Python list of key=value options. This list will
        be expanded and ``--setopt`` prepended to each in the yum/dnf command
        that is run.

        .. versionadded:: 2019.2.0

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.list_repo_pkgs
        salt '*' pkg.list_repo_pkgs foo bar baz
        salt '*' pkg.list_repo_pkgs 'samba4*' fromrepo=base,updates
        salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True
    '''
    byrepo = kwargs.pop('byrepo', False)
    cacheonly = kwargs.pop('cacheonly', False)
    fromrepo = kwargs.pop('fromrepo', '') or ''
    disablerepo = kwargs.pop('disablerepo', '') or ''
    enablerepo = kwargs.pop('enablerepo', '') or ''

    repo_arg = _get_options(fromrepo=fromrepo, **kwargs)

    if fromrepo and not isinstance(fromrepo, list):
        try:
            fromrepo = [x.strip() for x in fromrepo.split(',')]
        except AttributeError:
            fromrepo = [x.strip() for x in six.text_type(fromrepo).split(',')]

    if disablerepo and not isinstance(disablerepo, list):
        try:
            disablerepo = [x.strip() for x in disablerepo.split(',')
                           if x != '*']
        except AttributeError:
            disablerepo = [x.strip() for x in six.text_type(disablerepo).split(',')
                           if x != '*']

    if enablerepo and not isinstance(enablerepo, list):
        try:
            enablerepo = [x.strip() for x in enablerepo.split(',')
                          if x != '*']
        except AttributeError:
            enablerepo = [x.strip() for x in six.text_type(enablerepo).split(',')
                          if x != '*']

    if fromrepo:
        repos = fromrepo
    else:
        repos = [
            repo_name for repo_name, repo_info in six.iteritems(list_repos())
            if repo_name in enablerepo
            or (repo_name not in disablerepo
                and six.text_type(repo_info.get('enabled', '1')) == '1')
        ]

    ret = {}

    def _check_args(args, name):
        '''
        Do glob matching on args and return True if a match was found.
        Otherwise, return False
        '''
        for arg in args:
            if fnmatch.fnmatch(name, arg):
                return True
        return False

    def _parse_output(output, strict=False):
        for pkg in _yum_pkginfo(output):
            if strict and (pkg.repoid not in repos
                           or not _check_args(args, pkg.name)):
                continue
            repo_dict = ret.setdefault(pkg.repoid, {})
            version_list = repo_dict.setdefault(pkg.name, set())
            version_list.add(pkg.version)

    yum_version = None if _yum() != 'yum' else _LooseVersion(
                __salt__['cmd.run'](
                    ['yum', '--version'],
                    python_shell=False
                ).splitlines()[0].strip()
            )
    # Really old version of yum; does not even have --showduplicates option
    if yum_version and yum_version < _LooseVersion('3.2.13'):
        cmd_prefix = ['--quiet']
        if cacheonly:
            cmd_prefix.append('-C')
        cmd_prefix.append('list')
        for pkg_src in ('installed', 'available'):
            # Check installed packages first
            out = _call_yum(cmd_prefix + [pkg_src], ignore_retcode=True)
            if out['retcode'] == 0:
                _parse_output(out['stdout'], strict=True)
    # The --showduplicates option is added in 3.2.13, but the
    # repository-packages subcommand is only in 3.4.3 and newer
    elif yum_version and yum_version < _LooseVersion('3.4.3'):
        cmd_prefix = ['--quiet', '--showduplicates']
        if cacheonly:
            cmd_prefix.append('-C')
        cmd_prefix.append('list')
        for pkg_src in ('installed', 'available'):
            # Check installed packages first
            out = _call_yum(cmd_prefix + [pkg_src], ignore_retcode=True)
            if out['retcode'] == 0:
                _parse_output(out['stdout'], strict=True)
    else:
        for repo in repos:
            cmd = ['--quiet', '--showduplicates', 'repository-packages', repo, 'list']
            if cacheonly:
                cmd.append('-C')
            # Can't concatenate because args is a tuple, using list.extend()
            cmd.extend(args)
            out = _call_yum(cmd, ignore_retcode=True)
            if out['retcode'] != 0 and 'Error:' in out['stdout']:
                continue
            _parse_output(out['stdout'])

    if byrepo:
        for reponame in ret:
            # Sort versions newest to oldest
            for pkgname in ret[reponame]:
                sorted_versions = sorted(
                    [_LooseVersion(x) for x in ret[reponame][pkgname]],
                    reverse=True
                )
                ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
        return ret
    else:
        byrepo_ret = {}
        for reponame in ret:
            for pkgname in ret[reponame]:
                byrepo_ret.setdefault(pkgname, []).extend(ret[reponame][pkgname])
        for pkgname in byrepo_ret:
            sorted_versions = sorted(
                [_LooseVersion(x) for x in byrepo_ret[pkgname]],
                reverse=True
            )
            byrepo_ret[pkgname] = [x.vstring for x in sorted_versions]
        return byrepo_ret

Response: List the packages in the repository.


Instruction:
Can you tell what is the following Python 3 function doing
def list_upgrades(refresh=True, **kwargs):
    '''
    Check whether or not an upgrade is available for all packages

    The ``fromrepo``, ``enablerepo``, and ``disablerepo`` arguments are
    supported, as used in pkg states, and the ``disableexcludes`` option is
    also supported.

    .. versionadded:: 2014.7.0
        Support for the ``disableexcludes`` option

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_upgrades
    '''
    options = _get_options(**kwargs)

    if salt.utils.data.is_true(refresh):
        refresh_db(check_update=False, **kwargs)

    cmd = ['--quiet']
    cmd.extend(options)
    cmd.extend(['list', 'upgrades' if _yum() == 'dnf' else 'updates'])
    out = _call_yum(cmd, ignore_retcode=True)
    if out['retcode'] != 0 and 'Error:' in out:
        return {}

    return dict([(x.name, x.version) for x in _yum_pkginfo(out['stdout'])])

Response: List the available upgrades for all packages in the current repository.


Instruction:
Explain what the following Python 3 code does
def list_downloaded(**kwargs):
    '''
    .. versionadded:: 2017.7.0

    List prefetched packages downloaded by Yum in the local disk.

    CLI example:

    .. code-block:: bash

        salt '*' pkg.list_downloaded
    '''
    CACHE_DIR = os.path.join('/var/cache/', _yum())

    ret = {}
    for root, dirnames, filenames in salt.utils.path.os_walk(CACHE_DIR):
        for filename in fnmatch.filter(filenames, '*.rpm'):
            package_path = os.path.join(root, filename)
            pkg_info = __salt__['lowpkg.bin_pkg_info'](package_path)
            pkg_timestamp = int(os.path.getctime(package_path))
            ret.setdefault(pkg_info['name'], {})[pkg_info['version']] = {
                'path': package_path,
                'size': os.path.getsize(package_path),
                'creation_date_time_t': pkg_timestamp,
                'creation_date_time': datetime.datetime.fromtimestamp(pkg_timestamp).isoformat(),
            }
    return ret

Response: List prefetched packages downloaded by Yum in the local disk.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def info_installed(*names, **kwargs):
    '''
    .. versionadded:: 2015.8.1

    Return the information of the named package(s), installed on the system.

    :param all_versions:
        Include information for all versions of the packages installed on the minion.

    CLI example:

    .. code-block:: bash

        salt '*' pkg.info_installed <package1>
        salt '*' pkg.info_installed <package1> <package2> <package3> ...
        salt '*' pkg.info_installed <package1> <package2> <package3> all_versions=True
    '''
    all_versions = kwargs.get('all_versions', False)
    ret = dict()
    for pkg_name, pkgs_nfo in __salt__['lowpkg.info'](*names, **kwargs).items():
        pkg_nfo = pkgs_nfo if all_versions else [pkgs_nfo]
        for _nfo in pkg_nfo:
            t_nfo = dict()
            # Translate dpkg-specific keys to a common structure
            for key, value in _nfo.items():
                if key == 'source_rpm':
                    t_nfo['source'] = value
                else:
                    t_nfo[key] = value
            if not all_versions:
                ret[pkg_name] = t_nfo
            else:
                ret.setdefault(pkg_name, []).append(t_nfo)
    return ret

Response: Return the information of the named package on the system.


Instruction:
Write a Python 3 script for
refreshing the yum repos for updated packages

Response:
def refresh_db(**kwargs):
    '''
    Check the yum repos for updated packages

    Returns:

    - ``True``: Updates are available
    - ``False``: An error occurred
    - ``None``: No updates are available

    repo
        Refresh just the specified repo

    disablerepo
        Do not refresh the specified repo

    enablerepo
        Refresh a disabled repo using this option

    branch
        Add the specified branch when refreshing

    disableexcludes
        Disable the excludes defined in your config files. Takes one of three
        options:
        - ``all`` - disable all excludes
        - ``main`` - disable excludes defined in [main] in yum.conf
        - ``repoid`` - disable excludes defined for that repo

    setopt
        A comma-separated or Python list of key=value options. This list will
        be expanded and ``--setopt`` prepended to each in the yum/dnf command
        that is run.

        .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.refresh_db
    '''
    # Remove rtag file to keep multiple refreshes from happening in pkg states
    salt.utils.pkg.clear_rtag(__opts__)
    retcodes = {
        100: True,
        0: None,
        1: False,
    }

    ret = True
    check_update_ = kwargs.pop('check_update', True)
    options = _get_options(**kwargs)

    clean_cmd = ['--quiet', '--assumeyes', 'clean', 'expire-cache']
    clean_cmd.extend(options)
    _call_yum(clean_cmd, ignore_retcode=True)

    if check_update_:
        update_cmd = ['--quiet', '--assumeyes', 'check-update']
        if (__grains__.get('os_family') == 'RedHat'
           and __grains__.get('osmajorrelease') == 7):
            # This feature is disabled because it is not used by Salt and adds a
            # lot of extra time to the command with large repos like EPEL
            update_cmd.append('--setopt=autocheck_running_kernel=false')
        update_cmd.extend(options)
        ret = retcodes.get(_call_yum(update_cmd, ignore_retcode=True)['retcode'], False)

    return ret


Instruction:
Can you generate a brief explanation for the following Python 3 code
def install(name=None,
            refresh=False,
            skip_verify=False,
            pkgs=None,
            sources=None,
            downloadonly=False,
            reinstall=False,
            normalize=True,
            update_holds=False,
            saltenv='base',
            ignore_epoch=False,
            **kwargs):
    '''
    .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
        On minions running systemd>=205, `systemd-run(1)`_ is now used to
        isolate commands which modify installed packages from the
        ``salt-minion`` daemon's control group. This is done to keep systemd
        from killing any yum/dnf commands spawned by Salt when the
        ``salt-minion`` service is restarted. (see ``KillMode`` in the
        `systemd.kill(5)`_ manpage for more information). If desired, usage of
        `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
        <salt.modules.config.get>` called ``systemd.scope``, with a value of
        ``False`` (no quotes).

    .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
    .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html

    Install the passed package(s), add refresh=True to clean the yum database
    before package is installed.

    name
        The name of the package to be installed. Note that this parameter is
        ignored if either "pkgs" or "sources" is passed. Additionally, please
        note that this option can only be used to install packages from a
        software repository. To install a package file manually, use the
        "sources" option.

        32-bit packages can be installed on 64-bit systems by appending the
        architecture designation (``.i686``, ``.i586``, etc.) to the end of the
        package name.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install <package name>

    refresh
        Whether or not to update the yum database before executing.

    reinstall
        Specifying reinstall=True will use ``yum reinstall`` rather than
        ``yum install`` for requested packages that are already installed.

        If a version is specified with the requested package, then
        ``yum reinstall`` will only be used if the installed version
        matches the requested version.

        Works with ``sources`` when the package header of the source can be
        matched to the name and version of an installed package.

        .. versionadded:: 2014.7.0

    skip_verify
        Skip the GPG verification check (e.g., ``--nogpgcheck``)

    downloadonly
        Only download the packages, do not install.

    version
        Install a specific version of the package, e.g. 1.2.3-4.el5. Ignored
        if "pkgs" or "sources" is passed.

        .. versionchanged:: 2018.3.0
            version can now contain comparison operators (e.g. ``>1.2.3``,
            ``<=2.0``, etc.)

    update_holds : False
        If ``True``, and this function would update the package version, any
        packages held using the yum/dnf "versionlock" plugin will be unheld so
        that they can be updated. Otherwise, if this function attempts to
        update a held package, the held package(s) will be skipped and an
        error will be raised.

        .. versionadded:: 2016.11.0

    setopt
        A comma-separated or Python list of key=value options. This list will
        be expanded and ``--setopt`` prepended to each in the yum/dnf command
        that is run.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install foo setopt='obsoletes=0,plugins=0'

        .. versionadded:: 2019.2.0

    Repository Options:

    fromrepo
        Specify a package repository (or repositories) from which to install.
        (e.g., ``yum --disablerepo='*' --enablerepo='somerepo'``)

    enablerepo (ignored if ``fromrepo`` is specified)
        Specify a disabled package repository (or repositories) to enable.
        (e.g., ``yum --enablerepo='somerepo'``)

    disablerepo (ignored if ``fromrepo`` is specified)
        Specify an enabled package repository (or repositories) to disable.
        (e.g., ``yum --disablerepo='somerepo'``)

    disableexcludes
        Disable exclude from main, for a repo or for everything.
        (e.g., ``yum --disableexcludes='main'``)

        .. versionadded:: 2014.7.0

    ignore_epoch : False
        Only used when the version of a package is specified using a comparison
        operator (e.g. ``>4.1``). If set to ``True``, then the epoch will be
        ignored when comparing the currently-installed version to the desired
        version.

        .. versionadded:: 2018.3.0

    Multiple Package Installation Options:

    pkgs
        A list of packages to install from a software repository. Must be
        passed as a python list. A specific version number can be specified
        by using a single-element dict representing the package and its
        version.

        CLI Examples:

        .. code-block:: bash

            salt '*' pkg.install pkgs='["foo", "bar"]'
            salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3-4.el5"}]'

    sources
        A list of RPM packages to install. Must be passed as a list of dicts,
        with the keys being package names, and the values being the source URI
        or local path to the package.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install sources='[{"foo": "salt://foo.rpm"}, {"bar": "salt://bar.rpm"}]'

    normalize : True
        Normalize the package name by removing the architecture. This is useful
        for poorly created packages which might include the architecture as an
        actual part of the name such as kernel modules which match a specific
        kernel version.

        .. code-block:: bash

            salt -G role:nsd pkg.install gpfs.gplbin-2.6.32-279.31.1.el6.x86_64 normalize=False

        .. versionadded:: 2014.7.0

    diff_attr:
        If a list of package attributes is specified, returned value will
        contain them, eg.::

            {'<package>': {
                'old': {
                    'version': '<old-version>',
                    'arch': '<old-arch>'},

                'new': {
                    'version': '<new-version>',
                    'arch': '<new-arch>'}}}

        Valid attributes are: ``epoch``, ``version``, ``release``, ``arch``,
        ``install_date``, ``install_date_time_t``.

        If ``all`` is specified, all valid attributes will be returned.

        .. versionadded:: 2018.3.0

    Returns a dict containing the new package names and versions::

        {'<package>': {'old': '<old-version>',
                       'new': '<new-version>'}}

    If an attribute list in diff_attr is specified, the dict will also contain
    any specified attribute, eg.::

        {'<package>': {
            'old': {
                'version': '<old-version>',
                'arch': '<old-arch>'},

            'new': {
                'version': '<new-version>',
                'arch': '<new-arch>'}}}
    '''
    options = _get_options(**kwargs)

    if salt.utils.data.is_true(refresh):
        refresh_db(**kwargs)
    reinstall = salt.utils.data.is_true(reinstall)

    try:
        pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](
            name, pkgs, sources, saltenv=saltenv, normalize=normalize, **kwargs
        )
    except MinionError as exc:
        raise CommandExecutionError(exc)

    if not pkg_params:
        return {}

    version_num = kwargs.get('version')

    diff_attr = kwargs.get('diff_attr')
    old = list_pkgs(versions_as_list=False, attr=diff_attr) if not downloadonly else list_downloaded()
    # Use of __context__ means no duplicate work here, just accessing
    # information already in __context__ from the previous call to list_pkgs()
    old_as_list = list_pkgs(versions_as_list=True) if not downloadonly else list_downloaded()

    to_install = []
    to_downgrade = []
    to_reinstall = []
    _available = {}
    # The above three lists will be populated with tuples containing the
    # package name and the string being used for this particular package
    # modification. The reason for this method is that the string we use for
    # installation, downgrading, or reinstallation will be different than the
    # package name in a couple cases:
    #
    #   1) A specific version is being targeted. In this case the string being
    #      passed to install/downgrade/reinstall will contain the version
    #      information after the package name.
    #   2) A binary package is being installed via the "sources" param. In this
    #      case the string being passed will be the path to the local copy of
    #      the package in the minion cachedir.
    #
    # The reason that we need both items is to be able to modify the installed
    # version of held packages.
    if pkg_type == 'repository':
        has_wildcards = []
        has_comparison = []
        for pkgname, pkgver in six.iteritems(pkg_params):
            try:
                if '*' in pkgver:
                    has_wildcards.append(pkgname)
                elif pkgver.startswith('<') or pkgver.startswith('>'):
                    has_comparison.append(pkgname)
            except (TypeError, ValueError):
                continue
        _available = AvailablePackages(
            *has_wildcards + has_comparison,
            byrepo=False,
            **kwargs)
        pkg_params_items = six.iteritems(pkg_params)
    elif pkg_type == 'advisory':
        pkg_params_items = []
        cur_patches = list_patches()
        for advisory_id in pkg_params:
            if advisory_id not in cur_patches:
                raise CommandExecutionError(
                    'Advisory id "{0}" not found'.format(advisory_id)
                )
            else:
                pkg_params_items.append(advisory_id)
    else:
        pkg_params_items = []
        for pkg_source in pkg_params:
            if 'lowpkg.bin_pkg_info' in __salt__:
                rpm_info = __salt__['lowpkg.bin_pkg_info'](pkg_source)
            else:
                rpm_info = None
            if rpm_info is None:
                log.error(
                    'pkg.install: Unable to get rpm information for %s. '
                    'Version comparisons will be unavailable, and return '
                    'data may be inaccurate if reinstall=True.', pkg_source
                )
                pkg_params_items.append([pkg_source])
            else:
                pkg_params_items.append(
                    [rpm_info['name'], pkg_source, rpm_info['version']]
                )

    errors = []
    for pkg_item_list in pkg_params_items:
        if pkg_type == 'repository':
            pkgname, version_num = pkg_item_list
        elif pkg_type == 'advisory':
            pkgname = pkg_item_list
            version_num = None
        else:
            try:
                pkgname, pkgpath, version_num = pkg_item_list
            except ValueError:
                pkgname = None
                pkgpath = pkg_item_list[0]
                version_num = None

        if version_num is None:
            if pkg_type == 'repository':
                if reinstall and pkgname in old:
                    to_reinstall.append((pkgname, pkgname))
                else:
                    to_install.append((pkgname, pkgname))
            elif pkg_type == 'advisory':
                to_install.append((pkgname, pkgname))
            else:
                to_install.append((pkgname, pkgpath))
        else:
            # If we are installing a package file and not one from the repo,
            # and version_num is not None, then we can assume that pkgname is
            # not None, since the only way version_num is not None is if RPM
            # metadata parsing was successful.
            if pkg_type == 'repository':
                # yum/dnf does not support comparison operators. If the version
                # starts with an equals sign, ignore it.
                version_num = version_num.lstrip('=')
                if pkgname in has_comparison:
                    candidates = _available.get(pkgname, [])
                    target = salt.utils.pkg.match_version(
                        version_num,
                        candidates,
                        cmp_func=version_cmp,
                        ignore_epoch=ignore_epoch,
                    )
                    if target is None:
                        errors.append(
                            'No version matching \'{0}{1}\' could be found '
                            '(available: {2})'.format(
                                pkgname,
                                version_num,
                                ', '.join(candidates) if candidates else None
                            )
                        )
                        continue
                    else:
                        version_num = target
                if _yum() == 'yum':
                    # yum install does not support epoch without the arch, and
                    # we won't know what the arch will be when it's not
                    # provided. It could either be the OS architecture, or
                    # 'noarch', and we don't make that distinction in the
                    # pkg.list_pkgs return data.
                    if ignore_epoch is True:
                        version_num = version_num.split(':', 1)[-1]
                arch = ''
                try:
                    namepart, archpart = pkgname.rsplit('.', 1)
                except ValueError:
                    pass
                else:
                    if archpart in salt.utils.pkg.rpm.ARCHES:
                        arch = '.' + archpart
                        pkgname = namepart

                if '*' in version_num:
                    # Resolve wildcard matches
                    candidates = _available.get(pkgname, [])
                    match = salt.utils.itertools.fnmatch_multiple(candidates, version_num)
                    if match is not None:
                        version_num = match
                    else:
                        errors.append(
                            'No version matching \'{0}\' found for package '
                            '\'{1}\' (available: {2})'.format(
                                version_num,
                                pkgname,
                                ', '.join(candidates) if candidates else 'none'
                            )
                        )
                        continue

                if ignore_epoch is True:
                    pkgstr = '{0}-{1}{2}'.format(pkgname, version_num, arch)
                else:
                    pkgstr = '{0}-{1}{2}'.format(pkgname, version_num.split(':', 1)[-1], arch)

            else:
                pkgstr = pkgpath

            # Lambda to trim the epoch from the currently-installed version if
            # no epoch is specified in the specified version
            cver = old_as_list.get(pkgname, [])
            if reinstall and cver:
                for ver in cver:
                    if salt.utils.versions.compare(ver1=version_num,
                                                   oper='==',
                                                   ver2=ver,
                                                   cmp_func=version_cmp,
                                                   ignore_epoch=ignore_epoch):
                        # This version is already installed, so we need to
                        # reinstall.
                        to_reinstall.append((pkgname, pkgstr))
                        break
            else:
                if not cver:
                    to_install.append((pkgname, pkgstr))
                else:
                    for ver in cver:
                        if salt.utils.versions.compare(ver1=version_num,
                                                       oper='>=',
                                                       ver2=ver,
                                                   cmp_func=version_cmp,
                                                   ignore_epoch=ignore_epoch):
                            to_install.append((pkgname, pkgstr))
                            break
                    else:
                        if pkgname is not None:
                            if re.match('^kernel(|-devel)$', pkgname):
                                # kernel and kernel-devel support multiple
                                # installs as their paths do not conflict.
                                # Performing a yum/dnf downgrade will be a
                                # no-op so just do an install instead. It will
                                # fail if there are other interdependencies
                                # that have conflicts, and that's OK. We don't
                                # want to force anything, we just want to
                                # properly handle it if someone tries to
                                # install a kernel/kernel-devel of a lower
                                # version than the currently-installed one.
                                # TODO: find a better way to determine if a
                                # package supports multiple installs.
                                to_install.append((pkgname, pkgstr))
                            else:
                                # None of the currently-installed versions are
                                # greater than the specified version, so this
                                # is a downgrade.
                                to_downgrade.append((pkgname, pkgstr))

    def _add_common_args(cmd):
        '''
        DRY function to add args common to all yum/dnf commands
        '''
        cmd.extend(options)
        if skip_verify:
            cmd.append('--nogpgcheck')
        if downloadonly:
            cmd.append('--downloadonly')

    try:
        holds = list_holds(full=False)
    except SaltInvocationError:
        holds = []
        log.debug(
            'Failed to get holds, versionlock plugin is probably not '
            'installed'
        )
    unhold_prevented = []

    @contextlib.contextmanager
    def _temporarily_unhold(pkgs, targets):
        '''
        Temporarily unhold packages that need to be updated. Add any
        successfully-removed ones (and any packages not in the list of current
        holds) to the list of targets.
        '''
        to_unhold = {}
        for pkgname, pkgstr in pkgs:
            if pkgname in holds:
                if update_holds:
                    to_unhold[pkgname] = pkgstr
                else:
                    unhold_prevented.append(pkgname)
            else:
                targets.append(pkgstr)

        if not to_unhold:
            yield
        else:
            log.debug('Unholding packages: %s', ', '.join(to_unhold))
            try:
                # Using list() here for python3 compatibility, dict.keys() no
                # longer returns a list in python3.
                unhold_names = list(to_unhold.keys())
                for unheld_pkg, outcome in \
                        six.iteritems(unhold(pkgs=unhold_names)):
                    if outcome['result']:
                        # Package was successfully unheld, add to targets
                        targets.append(to_unhold[unheld_pkg])
                    else:
                        # Failed to unhold package
                        errors.append(unheld_pkg)
                yield
            except Exception as exc:
                errors.append(
                    'Error encountered unholding packages {0}: {1}'
                    .format(', '.join(to_unhold), exc)
                )
            finally:
                hold(pkgs=unhold_names)

    targets = []
    with _temporarily_unhold(to_install, targets):
        if targets:
            if pkg_type == 'advisory':
                targets = ["--advisory={0}".format(t) for t in targets]
            cmd = ['-y']
            if _yum() == 'dnf':
                cmd.extend(['--best', '--allowerasing'])
            _add_common_args(cmd)
            cmd.append('install' if pkg_type != 'advisory' else 'update')
            cmd.extend(targets)
            out = _call_yum(cmd, ignore_retcode=False, redirect_stderr=True)
            if out['retcode'] != 0:
                errors.append(out['stdout'])

    targets = []
    with _temporarily_unhold(to_downgrade, targets):
        if targets:
            cmd = ['-y']
            _add_common_args(cmd)
            cmd.append('downgrade')
            cmd.extend(targets)
            out = _call_yum(cmd)
            if out['retcode'] != 0:
                errors.append(out['stdout'])

    targets = []
    with _temporarily_unhold(to_reinstall, targets):
        if targets:
            cmd = ['-y']
            _add_common_args(cmd)
            cmd.append('reinstall')
            cmd.extend(targets)
            out = _call_yum(cmd)
            if out['retcode'] != 0:
                errors.append(out['stdout'])

    __context__.pop('pkg.list_pkgs', None)
    new = list_pkgs(versions_as_list=False, attr=diff_attr) if not downloadonly else list_downloaded()

    ret = salt.utils.data.compare_dicts(old, new)

    for pkgname, _ in to_reinstall:
        if pkgname not in ret or pkgname in old:
            ret.update({pkgname: {'old': old.get(pkgname, ''),
                                  'new': new.get(pkgname, '')}})

    if unhold_prevented:
        errors.append(
            'The following package(s) could not be updated because they are '
            'being held: {0}. Set \'update_holds\' to True to temporarily '
            'unhold these packages so that they can be updated.'.format(
                ', '.join(unhold_prevented)
            )
        )

    if errors:
        raise CommandExecutionError(
            'Error occurred installing{0} package(s)'.format(
                '/reinstalling' if to_reinstall else ''
            ),
            info={'errors': errors, 'changes': ret}
        )

    return ret

Response: Install a package from the yum database.


Instruction:
Here you have a function in Python 3, explain what it does
def upgrade(name=None,
            pkgs=None,
            refresh=True,
            skip_verify=False,
            normalize=True,
            minimal=False,
            obsoletes=True,
            **kwargs):
    '''
    Run a full system upgrade (a ``yum upgrade`` or ``dnf upgrade``), or
    upgrade specified packages. If the packages aren't installed, they will
    not be installed.

    .. versionchanged:: 2014.7.0
    .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
        On minions running systemd>=205, `systemd-run(1)`_ is now used to
        isolate commands which modify installed packages from the
        ``salt-minion`` daemon's control group. This is done to keep systemd
        from killing any yum/dnf commands spawned by Salt when the
        ``salt-minion`` service is restarted. (see ``KillMode`` in the
        `systemd.kill(5)`_ manpage for more information). If desired, usage of
        `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
        <salt.modules.config.get>` called ``systemd.scope``, with a value of
        ``False`` (no quotes).

    .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
    .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html

    .. versionchanged:: 2019.2.0
        Added ``obsoletes`` and ``minimal`` arguments

    Returns a dictionary containing the changes:

    .. code-block:: python

        {'<package>':  {'old': '<old-version>',
                        'new': '<new-version>'}}

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.upgrade
        salt '*' pkg.upgrade name=openssl

    Repository Options:

    fromrepo
        Specify a package repository (or repositories) from which to install.
        (e.g., ``yum --disablerepo='*' --enablerepo='somerepo'``)

    enablerepo (ignored if ``fromrepo`` is specified)
        Specify a disabled package repository (or repositories) to enable.
        (e.g., ``yum --enablerepo='somerepo'``)

    disablerepo (ignored if ``fromrepo`` is specified)
        Specify an enabled package repository (or repositories) to disable.
        (e.g., ``yum --disablerepo='somerepo'``)

    disableexcludes
        Disable exclude from main, for a repo or for everything.
        (e.g., ``yum --disableexcludes='main'``)

        .. versionadded:: 2014.7

    name
        The name of the package to be upgraded. Note that this parameter is
        ignored if "pkgs" is passed.

        32-bit packages can be upgraded on 64-bit systems by appending the
        architecture designation (``.i686``, ``.i586``, etc.) to the end of the
        package name.

        Warning: if you forget 'name=' and run pkg.upgrade openssl, ALL packages
        are upgraded. This will be addressed in next releases.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.upgrade name=openssl

        .. versionadded:: 2016.3.0

    pkgs
        A list of packages to upgrade from a software repository. Must be
        passed as a python list. A specific version number can be specified
        by using a single-element dict representing the package and its
        version. If the package was not already installed on the system,
        it will not be installed.

        CLI Examples:

        .. code-block:: bash

            salt '*' pkg.upgrade pkgs='["foo", "bar"]'
            salt '*' pkg.upgrade pkgs='["foo", {"bar": "1.2.3-4.el5"}]'

        .. versionadded:: 2016.3.0

    normalize : True
        Normalize the package name by removing the architecture. This is useful
        for poorly created packages which might include the architecture as an
        actual part of the name such as kernel modules which match a specific
        kernel version.

        .. code-block:: bash

            salt -G role:nsd pkg.upgrade gpfs.gplbin-2.6.32-279.31.1.el6.x86_64 normalize=False

        .. versionadded:: 2016.3.0

    minimal : False
        Use upgrade-minimal instead of upgrade (e.g., ``yum upgrade-minimal``)
        Goes to the 'newest' package match which fixes a problem that affects your system.

        .. code-block:: bash

            salt '*' pkg.upgrade minimal=True

        .. versionadded:: 2019.2.0

    obsoletes : True
        Controls wether yum/dnf should take obsoletes into account and remove them.
        If set to ``False`` yum will use ``update`` instead of ``upgrade``
        and dnf will be run with ``--obsoletes=False``

        .. code-block:: bash

            salt '*' pkg.upgrade obsoletes=False

        .. versionadded:: 2019.2.0

    setopt
        A comma-separated or Python list of key=value options. This list will
        be expanded and ``--setopt`` prepended to each in the yum/dnf command
        that is run.

        .. versionadded:: 2019.2.0

    .. note::
        To add extra arguments to the ``yum upgrade`` command, pass them as key
        word arguments. For arguments without assignments, pass ``True``

    .. code-block:: bash

        salt '*' pkg.upgrade security=True exclude='kernel*'
    '''
    options = _get_options(get_extra_options=True, **kwargs)

    if salt.utils.data.is_true(refresh):
        refresh_db(**kwargs)

    old = list_pkgs()

    targets = []
    if name or pkgs:
        try:
            pkg_params = __salt__['pkg_resource.parse_targets'](
                name=name,
                pkgs=pkgs,
                sources=None,
                normalize=normalize,
                **kwargs)[0]
        except MinionError as exc:
            raise CommandExecutionError(exc)

        if pkg_params:
            # Calling list.extend() on a dict will extend it using the
            # dictionary's keys.
            targets.extend(pkg_params)

    cmd = ['--quiet', '-y']
    cmd.extend(options)
    if skip_verify:
        cmd.append('--nogpgcheck')
    if obsoletes:
        cmd.append('upgrade' if not minimal else 'upgrade-minimal')
    else:
        # do not force the removal of obsolete packages
        if _yum() == 'dnf':
            # for dnf we can just disable obsoletes
            cmd.append('--obsoletes=False')
            cmd.append('upgrade' if not minimal else 'upgrade-minimal')
        else:
            # for yum we have to use update instead of upgrade
            cmd.append('update' if not minimal else 'update-minimal')
    cmd.extend(targets)
    result = _call_yum(cmd)
    __context__.pop('pkg.list_pkgs', None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if result['retcode'] != 0:
        raise CommandExecutionError(
            'Problem encountered upgrading packages',
            info={'changes': ret, 'result': result}
        )

    return ret

Response: A full system upgrade of a set of packages.


Instruction:
How would you explain what the following Python 3 function does
def remove(name=None, pkgs=None, **kwargs):  # pylint: disable=W0613
    '''
    .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
        On minions running systemd>=205, `systemd-run(1)`_ is now used to
        isolate commands which modify installed packages from the
        ``salt-minion`` daemon's control group. This is done to keep systemd
        from killing any yum/dnf commands spawned by Salt when the
        ``salt-minion`` service is restarted. (see ``KillMode`` in the
        `systemd.kill(5)`_ manpage for more information). If desired, usage of
        `systemd-run(1)`_ can be suppressed by setting a :mod:`config option
        <salt.modules.config.get>` called ``systemd.scope``, with a value of
        ``False`` (no quotes).

    .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html
    .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html

    Remove packages

    name
        The name of the package to be removed

    Multiple Package Options:

    pkgs
        A list of packages to delete. Must be passed as a python list. The
        ``name`` parameter will be ignored if this option is passed.

    .. versionadded:: 0.16.0

    Returns a dict containing the changes.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.remove <package name>
        salt '*' pkg.remove <package1>,<package2>,<package3>
        salt '*' pkg.remove pkgs='["foo", "bar"]'
    '''
    try:
        pkg_params = __salt__['pkg_resource.parse_targets'](name, pkgs)[0]
    except MinionError as exc:
        raise CommandExecutionError(exc)

    old = list_pkgs()
    targets = []
    for target in pkg_params:
        # Check if package version set to be removed is actually installed:
        # old[target] contains a comma-separated list of installed versions
        if target in old and not pkg_params[target]:
            targets.append(target)
        elif target in old and pkg_params[target] in old[target].split(','):
            arch = ''
            pkgname = target
            try:
                namepart, archpart = target.rsplit('.', 1)
            except ValueError:
                pass
            else:
                if archpart in salt.utils.pkg.rpm.ARCHES:
                    arch = '.' + archpart
                    pkgname = namepart
            targets.append('{0}-{1}{2}'.format(pkgname, pkg_params[target], arch))
    if not targets:
        return {}

    out = _call_yum(['-y', 'remove'] + targets)
    if out['retcode'] != 0 and out['stderr']:
        errors = [out['stderr']]
    else:
        errors = []

    __context__.pop('pkg.list_pkgs', None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if errors:
        raise CommandExecutionError(
            'Error occurred removing package(s)',
            info={'errors': errors, 'changes': ret}
        )

    return ret

Response: Remove packages from a named package.


Instruction:
How would you explain what the following Python 3 function does
def hold(name=None, pkgs=None, sources=None, normalize=True, **kwargs):  # pylint: disable=W0613
    '''
    .. versionadded:: 2014.7.0

    Version-lock packages

    .. note::
        Requires the appropriate ``versionlock`` plugin package to be installed:

        - On RHEL 5: ``yum-versionlock``
        - On RHEL 6 & 7: ``yum-plugin-versionlock``
        - On Fedora: ``python-dnf-plugins-extras-versionlock``

    name
        The name of the package to be held.

    Multiple Package Options:

    pkgs
        A list of packages to hold. Must be passed as a python list. The
        ``name`` parameter will be ignored if this option is passed.

    Returns a dict containing the changes.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.hold <package name>
        salt '*' pkg.hold pkgs='["foo", "bar"]'
    '''
    _check_versionlock()

    if not name and not pkgs and not sources:
        raise SaltInvocationError(
            'One of name, pkgs, or sources must be specified.'
        )
    if pkgs and sources:
        raise SaltInvocationError(
            'Only one of pkgs or sources can be specified.'
        )

    targets = []
    if pkgs:
        targets.extend(pkgs)
    elif sources:
        for source in sources:
            targets.append(next(six.iterkeys(source)))
    else:
        targets.append(name)

    current_locks = list_holds(full=False)
    ret = {}
    for target in targets:
        if isinstance(target, dict):
            target = next(six.iterkeys(target))

        ret[target] = {'name': target,
                       'changes': {},
                       'result': False,
                       'comment': ''}

        if target not in current_locks:
            if 'test' in __opts__ and __opts__['test']:
                ret[target].update(result=None)
                ret[target]['comment'] = ('Package {0} is set to be held.'
                                          .format(target))
            else:
                out = _call_yum(['versionlock', target])
                if out['retcode'] == 0:
                    ret[target].update(result=True)
                    ret[target]['comment'] = ('Package {0} is now being held.'
                                              .format(target))
                    ret[target]['changes']['new'] = 'hold'
                    ret[target]['changes']['old'] = ''
                else:
                    ret[target]['comment'] = ('Package {0} was unable to be held.'
                                              .format(target))
        else:
            ret[target].update(result=True)
            ret[target]['comment'] = ('Package {0} is already set to be held.'
                                      .format(target))
    return ret

Response: Hold a versionlock package.


Instruction:
Here you have a function in Python 3, explain what it does
def unhold(name=None, pkgs=None, sources=None, **kwargs):  # pylint: disable=W0613
    '''
    .. versionadded:: 2014.7.0

    Remove version locks

    .. note::
        Requires the appropriate ``versionlock`` plugin package to be installed:

        - On RHEL 5: ``yum-versionlock``
        - On RHEL 6 & 7: ``yum-plugin-versionlock``
        - On Fedora: ``python-dnf-plugins-extras-versionlock``

    name
        The name of the package to be unheld

    Multiple Package Options:

    pkgs
        A list of packages to unhold. Must be passed as a python list. The
        ``name`` parameter will be ignored if this option is passed.

    Returns a dict containing the changes.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.unhold <package name>
        salt '*' pkg.unhold pkgs='["foo", "bar"]'
    '''
    _check_versionlock()

    if not name and not pkgs and not sources:
        raise SaltInvocationError(
            'One of name, pkgs, or sources must be specified.'
        )
    if pkgs and sources:
        raise SaltInvocationError(
            'Only one of pkgs or sources can be specified.'
        )

    targets = []
    if pkgs:
        for pkg in salt.utils.data.repack_dictlist(pkgs):
            targets.append(pkg)
    elif sources:
        for source in sources:
            targets.append(next(iter(source)))
    else:
        targets.append(name)

    # Yum's versionlock plugin doesn't support passing just the package name
    # when removing a lock, so we need to get the full list and then use
    # fnmatch below to find the match.
    current_locks = list_holds(full=_yum() == 'yum')

    ret = {}
    for target in targets:
        if isinstance(target, dict):
            target = next(six.iterkeys(target))

        ret[target] = {'name': target,
                       'changes': {},
                       'result': False,
                       'comment': ''}

        if _yum() == 'dnf':
            search_locks = [x for x in current_locks if x == target]
        else:
            # To accommodate yum versionlock's lack of support for removing
            # locks using just the package name, we have to use fnmatch to do
            # glob matching on the target name, and then for each matching
            # expression double-check that the package name (obtained via
            # _get_hold()) matches the targeted package.
            search_locks = [
                x for x in current_locks
                if fnmatch.fnmatch(x, '*{0}*'.format(target))
                and target == _get_hold(x, full=False)
            ]

        if search_locks:
            if __opts__['test']:
                ret[target].update(result=None)
                ret[target]['comment'] = ('Package {0} is set to be unheld.'
                                          .format(target))
            else:
                out = _call_yum(['versionlock', 'delete'] + search_locks)
                if out['retcode'] == 0:
                    ret[target].update(result=True)
                    ret[target]['comment'] = ('Package {0} is no longer held.'
                                              .format(target))
                    ret[target]['changes']['new'] = ''
                    ret[target]['changes']['old'] = 'hold'
                else:
                    ret[target]['comment'] = ('Package {0} was unable to be '
                                              'unheld.'.format(target))
        else:
            ret[target].update(result=True)
            ret[target]['comment'] = ('Package {0} is not being held.'
                                      .format(target))
    return ret

Response: Remove version locks from a package.


Instruction:
Make a summary of the following Python 3 code
def list_holds(pattern=__HOLD_PATTERN, full=True):
    r'''
    .. versionchanged:: 2016.3.0,2015.8.4,2015.5.10
        Function renamed from ``pkg.get_locked_pkgs`` to ``pkg.list_holds``.

    List information on locked packages

    .. note::
        Requires the appropriate ``versionlock`` plugin package to be installed:

        - On RHEL 5: ``yum-versionlock``
        - On RHEL 6 & 7: ``yum-plugin-versionlock``
        - On Fedora: ``python-dnf-plugins-extras-versionlock``

    pattern : \w+(?:[.-][^-]+)*
        Regular expression used to match the package name

    full : True
        Show the full hold definition including version and epoch. Set to
        ``False`` to return just the name of the package(s) being held.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_holds
        salt '*' pkg.list_holds full=False
    '''
    _check_versionlock()

    out = __salt__['cmd.run']([_yum(), 'versionlock', 'list'],
                              python_shell=False)
    ret = []
    for line in salt.utils.itertools.split(out, '\n'):
        match = _get_hold(line, pattern=pattern, full=full)
        if match is not None:
            ret.append(match)
    return ret

Response: r List the packages that are currently held.


Instruction:
Can you create a Python 3 function that
lists all available group names and languages on this system

Response:
def group_list():
    '''
    .. versionadded:: 2014.1.0

    Lists all groups known by yum on this system

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.group_list
    '''
    ret = {'installed': [],
           'available': [],
           'installed environments': [],
           'available environments': [],
           'available languages': {}}

    section_map = {
        'installed groups:': 'installed',
        'available groups:': 'available',
        'installed environment groups:': 'installed environments',
        'available environment groups:': 'available environments',
        'available language groups:': 'available languages',
    }

    out = __salt__['cmd.run_stdout'](
        [_yum(), 'grouplist', 'hidden'],
        output_loglevel='trace',
        python_shell=False
    )
    key = None
    for line in salt.utils.itertools.split(out, '\n'):
        line_lc = line.lower()
        if line_lc == 'done':
            break

        section_lookup = section_map.get(line_lc)
        if section_lookup is not None and section_lookup != key:
            key = section_lookup
            continue

        # Ignore any administrative comments (plugin info, repo info, etc.)
        if key is None:
            continue

        line = line.strip()
        if key != 'available languages':
            ret[key].append(line)
        else:
            match = re.match(r'(.+) \[(.+)\]', line)
            if match:
                name, lang = match.groups()
                ret[key][line] = {'name': name, 'language': lang}
    return ret


Instruction:
Can you tell what is the following Python 3 function doing
def group_info(name, expand=False):
    '''
    .. versionadded:: 2014.1.0
    .. versionchanged:: 2016.3.0,2015.8.4,2015.5.10
        The return data has changed. A new key ``type`` has been added to
        distinguish environment groups from package groups. Also, keys for the
        group name and group ID have been added. The ``mandatory packages``,
        ``optional packages``, and ``default packages`` keys have been renamed
        to ``mandatory``, ``optional``, and ``default`` for accuracy, as
        environment groups include other groups, and not packages. Finally,
        this function now properly identifies conditional packages.

    Lists packages belonging to a certain group

    name
        Name of the group to query

    expand : False
        If the specified group is an environment group, then the group will be
        expanded and the return data will include package names instead of
        group names.

        .. versionadded:: 2016.3.0

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.group_info 'Perl Support'
    '''
    pkgtypes = ('mandatory', 'optional', 'default', 'conditional')
    ret = {}
    for pkgtype in pkgtypes:
        ret[pkgtype] = set()

    cmd = [_yum(), '--quiet', 'groupinfo', name]
    out = __salt__['cmd.run_stdout'](
        cmd,
        output_loglevel='trace',
        python_shell=False
    )

    g_info = {}
    for line in salt.utils.itertools.split(out, '\n'):
        try:
            key, value = [x.strip() for x in line.split(':')]
            g_info[key.lower()] = value
        except ValueError:
            continue

    if 'environment group' in g_info:
        ret['type'] = 'environment group'
    elif 'group' in g_info:
        ret['type'] = 'package group'

    ret['group'] = g_info.get('environment group') or g_info.get('group')
    ret['id'] = g_info.get('environment-id') or g_info.get('group-id')
    if not ret['group'] and not ret['id']:
        raise CommandExecutionError('Group \'{0}\' not found'.format(name))

    ret['description'] = g_info.get('description', '')

    pkgtypes_capturegroup = '(' + '|'.join(pkgtypes) + ')'
    for pkgtype in pkgtypes:
        target_found = False
        for line in salt.utils.itertools.split(out, '\n'):
            line = line.strip().lstrip(string.punctuation)
            match = re.match(
                pkgtypes_capturegroup + r' (?:groups|packages):\s*$',
                line.lower()
            )
            if match:
                if target_found:
                    # We've reached a new section, break from loop
                    break
                else:
                    if match.group(1) == pkgtype:
                        # We've reached the targeted section
                        target_found = True
                    continue
            if target_found:
                if expand and ret['type'] == 'environment group':
                    expanded = group_info(line, expand=True)
                    # Don't shadow the pkgtype variable from the outer loop
                    for p_type in pkgtypes:
                        ret[p_type].update(set(expanded[p_type]))
                else:
                    ret[pkgtype].add(line)

    for pkgtype in pkgtypes:
        ret[pkgtype] = sorted(ret[pkgtype])

    return ret

Response: Return information about a specific group.


Instruction:
Can you generate the documentation for the following Python 3 function
def group_diff(name):
    '''
    .. versionadded:: 2014.1.0
    .. versionchanged:: 2016.3.0,2015.8.4,2015.5.10
        Environment groups are now supported. The key names have been renamed,
        similar to the changes made in :py:func:`pkg.group_info
        <salt.modules.yumpkg.group_info>`.

    Lists which of a group's packages are installed and which are not
    installed

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.group_diff 'Perl Support'
    '''
    pkgtypes = ('mandatory', 'optional', 'default', 'conditional')
    ret = {}
    for pkgtype in pkgtypes:
        ret[pkgtype] = {'installed': [], 'not installed': []}

    pkgs = list_pkgs()
    group_pkgs = group_info(name, expand=True)
    for pkgtype in pkgtypes:
        for member in group_pkgs.get(pkgtype, []):
            if member in pkgs:
                ret[pkgtype]['installed'].append(member)
            else:
                ret[pkgtype]['not installed'].append(member)
    return ret

Response: Return a dictionary of the keys that are not installed and which are not installed.


Instruction:
Create a Python 3 function to
list all repos in a basedir.

Response:
def list_repos(basedir=None, **kwargs):
    '''
    Lists all repos in <basedir> (default: all dirs in `reposdir` yum option).

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_repos
        salt '*' pkg.list_repos basedir=/path/to/dir
        salt '*' pkg.list_repos basedir=/path/to/dir,/path/to/another/dir
    '''

    basedirs = _normalize_basedir(basedir)
    repos = {}
    log.debug('Searching for repos in %s', basedirs)
    for bdir in basedirs:
        if not os.path.exists(bdir):
            continue
        for repofile in os.listdir(bdir):
            repopath = '{0}/{1}'.format(bdir, repofile)
            if not repofile.endswith('.repo'):
                continue
            filerepos = _parse_repo_file(repopath)[1]
            for reponame in filerepos:
                repo = filerepos[reponame]
                repo['file'] = repopath
                repos[reponame] = repo
    return repos


Instruction:
Can you generate a brief explanation for the following Python 3 code
def get_repo(name, basedir=None, **kwargs):  # pylint: disable=W0613
    '''
    Display a repo from <basedir> (default basedir: all dirs in ``reposdir``
    yum option).

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.get_repo myrepo
        salt '*' pkg.get_repo myrepo basedir=/path/to/dir
        salt '*' pkg.get_repo myrepo basedir=/path/to/dir,/path/to/another/dir
    '''
    repos = list_repos(basedir)

    # Find out what file the repo lives in
    repofile = ''
    for repo in repos:
        if repo == name:
            repofile = repos[repo]['file']

    if repofile:
        # Return just one repo
        filerepos = _parse_repo_file(repofile)[1]
        return filerepos[name]
    return {}

Response: Get a single repo from a given basedir


Instruction:
How would you code a function in Python 3 to
delete a repo from the specified base directory.

Response:
def del_repo(repo, basedir=None, **kwargs):  # pylint: disable=W0613
    '''
    Delete a repo from <basedir> (default basedir: all dirs in `reposdir` yum
    option).

    If the .repo file in which the repo exists does not contain any other repo
    configuration, the file itself will be deleted.

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.del_repo myrepo
        salt '*' pkg.del_repo myrepo basedir=/path/to/dir
        salt '*' pkg.del_repo myrepo basedir=/path/to/dir,/path/to/another/dir
    '''
    # this is so we know which dirs are searched for our error messages below
    basedirs = _normalize_basedir(basedir)
    repos = list_repos(basedirs)

    if repo not in repos:
        return 'Error: the {0} repo does not exist in {1}'.format(
            repo, basedirs)

    # Find out what file the repo lives in
    repofile = ''
    for arepo in repos:
        if arepo == repo:
            repofile = repos[arepo]['file']

    # See if the repo is the only one in the file
    onlyrepo = True
    for arepo in six.iterkeys(repos):
        if arepo == repo:
            continue
        if repos[arepo]['file'] == repofile:
            onlyrepo = False

    # If this is the only repo in the file, delete the file itself
    if onlyrepo:
        os.remove(repofile)
        return 'File {0} containing repo {1} has been removed'.format(
            repofile, repo)

    # There must be other repos in this file, write the file with them
    header, filerepos = _parse_repo_file(repofile)
    content = header
    for stanza in six.iterkeys(filerepos):
        if stanza == repo:
            continue
        comments = ''
        if 'comments' in six.iterkeys(filerepos[stanza]):
            comments = salt.utils.pkg.rpm.combine_comments(
                    filerepos[stanza]['comments'])
            del filerepos[stanza]['comments']
        content += '\n[{0}]'.format(stanza)
        for line in filerepos[stanza]:
            content += '\n{0}={1}'.format(line, filerepos[stanza][line])
        content += '\n{0}\n'.format(comments)

    with salt.utils.files.fopen(repofile, 'w') as fileout:
        fileout.write(salt.utils.stringutils.to_str(content))

    return 'Repo {0} has been removed from {1}'.format(repo, repofile)


Instruction:
Can you generate the documentation for the following Python 3 function
def mod_repo(repo, basedir=None, **kwargs):
    '''
    Modify one or more values for a repo. If the repo does not exist, it will
    be created, so long as the following values are specified:

    repo
        name by which the yum refers to the repo
    name
        a human-readable name for the repo
    baseurl
        the URL for yum to reference
    mirrorlist
        the URL for yum to reference
    key_url
        the URL to gather the repo key from (salt:// or any other scheme supported by cp.cache_file)

    Key/Value pairs may also be removed from a repo's configuration by setting
    a key to a blank value. Bear in mind that a name cannot be deleted, and a
    baseurl can only be deleted if a mirrorlist is specified (or vice versa).

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.mod_repo reponame enabled=1 gpgcheck=1
        salt '*' pkg.mod_repo reponame basedir=/path/to/dir enabled=1
        salt '*' pkg.mod_repo reponame baseurl= mirrorlist=http://host.com/
    '''
    # Filter out '__pub' arguments, as well as saltenv
    repo_opts = dict(
        (x, kwargs[x]) for x in kwargs
        if not x.startswith('__') and x not in ('saltenv',)
    )

    if all(x in repo_opts for x in ('mirrorlist', 'baseurl')):
        raise SaltInvocationError(
            'Only one of \'mirrorlist\' and \'baseurl\' can be specified'
        )

    # Build a list of keys to be deleted
    todelete = []
    # list() of keys because the dict could be shrinking in the for loop.
    for key in list(repo_opts):
        if repo_opts[key] != 0 and not repo_opts[key]:
            del repo_opts[key]
            todelete.append(key)

    # Add baseurl or mirrorlist to the 'todelete' list if the other was
    # specified in the repo_opts
    if 'mirrorlist' in repo_opts:
        todelete.append('baseurl')
    elif 'baseurl' in repo_opts:
        todelete.append('mirrorlist')

    # Fail if the user tried to delete the name
    if 'name' in todelete:
        raise SaltInvocationError('The repo name cannot be deleted')

    # Give the user the ability to change the basedir
    repos = {}
    basedirs = _normalize_basedir(basedir)
    repos = list_repos(basedirs)

    repofile = ''
    header = ''
    filerepos = {}
    if repo not in repos:
        # If the repo doesn't exist, create it in a new file in the first
        # repo directory that exists
        newdir = None
        for d in basedirs:
            if os.path.exists(d):
                newdir = d
                break
        if not newdir:
            raise SaltInvocationError(
                'The repo does not exist and needs to be created, but none '
                'of the following basedir directories exist: {0}'.format(basedirs)
            )

        repofile = '{0}/{1}.repo'.format(newdir, repo)

        if 'name' not in repo_opts:
            raise SaltInvocationError(
                'The repo does not exist and needs to be created, but a name '
                'was not given'
            )

        if 'baseurl' not in repo_opts and 'mirrorlist' not in repo_opts:
            raise SaltInvocationError(
                'The repo does not exist and needs to be created, but either '
                'a baseurl or a mirrorlist needs to be given'
            )
        filerepos[repo] = {}
    else:
        # The repo does exist, open its file
        repofile = repos[repo]['file']
        header, filerepos = _parse_repo_file(repofile)

    # Error out if they tried to delete baseurl or mirrorlist improperly
    if 'baseurl' in todelete:
        if 'mirrorlist' not in repo_opts and 'mirrorlist' \
                not in filerepos[repo]:
            raise SaltInvocationError(
                'Cannot delete baseurl without specifying mirrorlist'
            )
    if 'mirrorlist' in todelete:
        if 'baseurl' not in repo_opts and 'baseurl' \
                not in filerepos[repo]:
            raise SaltInvocationError(
                'Cannot delete mirrorlist without specifying baseurl'
            )

    # Import repository gpg key
    if 'key_url' in repo_opts:
        key_url = kwargs['key_url']
        fn_ = __salt__['cp.cache_file'](key_url, saltenv=(kwargs['saltenv'] if 'saltenv' in kwargs else 'base'))
        if not fn_:
            raise CommandExecutionError(
                'Error: Unable to copy key from URL {0} for repository {1}'.format(key_url, repo_opts['name'])
            )
        cmd = ['rpm', '--import', fn_]
        out = __salt__['cmd.retcode'](cmd, python_shell=False, **kwargs)
        if out != salt.defaults.exitcodes.EX_OK:
            raise CommandExecutionError(
                'Error: Unable to import key from URL {0} for repository {1}'.format(key_url, repo_opts['name'])
            )
        del repo_opts['key_url']

    # Delete anything in the todelete list
    for key in todelete:
        if key in six.iterkeys(filerepos[repo].copy()):
            del filerepos[repo][key]

    _bool_to_str = lambda x: '1' if x else '0'
    # Old file or new, write out the repos(s)
    filerepos[repo].update(repo_opts)
    content = header
    for stanza in six.iterkeys(filerepos):
        comments = salt.utils.pkg.rpm.combine_comments(
            filerepos[stanza].pop('comments', [])
        )
        content += '[{0}]\n'.format(stanza)
        for line in six.iterkeys(filerepos[stanza]):
            content += '{0}={1}\n'.format(
                line,
                filerepos[stanza][line]
                    if not isinstance(filerepos[stanza][line], bool)
                    else _bool_to_str(filerepos[stanza][line])
            )
        content += comments + '\n'

    with salt.utils.files.fopen(repofile, 'w') as fileout:
        fileout.write(salt.utils.stringutils.to_str(content))

    return {repofile: filerepos}

Response: Modify one or more values for a repo.


Instruction:
Explain what the following Python 3 code does
def _parse_repo_file(filename):
    '''
    Turn a single repo file into a dict
    '''
    parsed = configparser.ConfigParser()
    config = {}

    try:
        parsed.read(filename)
    except configparser.MissingSectionHeaderError as err:
        log.error(
            'Failed to parse file %s, error: %s',
            filename, err.message
        )
        return ('', {})

    for section in parsed._sections:
        section_dict = dict(parsed._sections[section])
        section_dict.pop('__name__', None)
        config[section] = section_dict

    # Try to extract header comments, as well as comments for each repo. Read
    # from the beginning of the file and assume any leading comments are
    # header comments. Continue to read each section header and then find the
    # comments for each repo.
    headers = ''
    section = None
    with salt.utils.files.fopen(filename, 'r') as repofile:
        for line in repofile:
            line = salt.utils.stringutils.to_unicode(line)
            line = line.strip()
            if line.startswith('#'):
                if section is None:
                    headers += line + '\n'
                else:
                    try:
                        comments = config[section].setdefault('comments', [])
                        comments.append(line[1:].lstrip())
                    except KeyError:
                        log.debug(
                            'Found comment in %s which does not appear to '
                            'belong to any repo section: %s', filename, line
                        )
            elif line.startswith('[') and line.endswith(']'):
                section = line[1:-1]

    return (headers, salt.utils.data.decode(config))

Response: Parse a single repo file into a dict containing the headers and comments for each repo section.


Instruction:
Can you tell what is the following Python 3 function doing
def owner(*paths, **kwargs):
    '''
    .. versionadded:: 2014.7.0

    Return the name of the package that owns the file. Multiple file paths can
    be passed. Like :mod:`pkg.version <salt.modules.yumpkg.version>`, if a
    single path is passed, a string will be returned, and if multiple paths are
    passed, a dictionary of file/package name pairs will be returned.

    If the file is not owned by a package, or is not present on the minion,
    then an empty string will be returned for that path.

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.owner /usr/bin/apachectl
        salt '*' pkg.owner /usr/bin/apachectl /etc/httpd/conf/httpd.conf
    '''
    if not paths:
        return ''
    ret = {}
    cmd_prefix = ['rpm', '-qf', '--queryformat', '%{name}']
    for path in paths:
        ret[path] = __salt__['cmd.run_stdout'](
            cmd_prefix + [path],
            output_loglevel='trace',
            python_shell=False
        )
        if 'not owned' in ret[path].lower():
            ret[path] = ''
    if len(ret) == 1:
        return next(six.itervalues(ret))
    return ret

Response: Return the name of the package that owns the file.


Instruction:
Can you generate the documentation for the following Python 3 function
def download(*packages, **kwargs):
    '''
    .. versionadded:: 2015.5.0

    Download packages to the local disk. Requires ``yumdownloader`` from
    ``yum-utils`` package.

    .. note::

        ``yum-utils`` will already be installed on the minion if the package
        was installed from the Fedora / EPEL repositories.

    CLI example:

    .. code-block:: bash

        salt '*' pkg.download httpd
        salt '*' pkg.download httpd postfix
    '''
    if not packages:
        raise SaltInvocationError('No packages were specified')

    CACHE_DIR = '/var/cache/yum/packages'
    if not os.path.exists(CACHE_DIR):
        os.makedirs(CACHE_DIR)
    cached_pkgs = os.listdir(CACHE_DIR)
    to_purge = []
    for pkg in packages:
        to_purge.extend([os.path.join(CACHE_DIR, x)
                         for x in cached_pkgs
                         if x.startswith('{0}-'.format(pkg))])
    for purge_target in set(to_purge):
        log.debug('Removing cached package %s', purge_target)
        try:
            os.unlink(purge_target)
        except OSError as exc:
            log.error('Unable to remove %s: %s', purge_target, exc)

    cmd = ['yumdownloader', '-q', '--destdir={0}'.format(CACHE_DIR)]
    cmd.extend(packages)
    __salt__['cmd.run'](
        cmd,
        output_loglevel='trace',
        python_shell=False
    )
    ret = {}
    for dld_result in os.listdir(CACHE_DIR):
        if not dld_result.endswith('.rpm'):
            continue
        pkg_name = None
        pkg_file = None
        for query_pkg in packages:
            if dld_result.startswith('{0}-'.format(query_pkg)):
                pkg_name = query_pkg
                pkg_file = dld_result
                break
        if pkg_file is not None:
            ret[pkg_name] = os.path.join(CACHE_DIR, pkg_file)

    if not ret:
        raise CommandExecutionError(
            'Unable to download any of the following packages: {0}'
            .format(', '.join(packages))
        )

    failed = [x for x in packages if x not in ret]
    if failed:
        ret['_error'] = ('The following package(s) failed to download: {0}'
                         .format(', '.join(failed)))
    return ret

Response: Download a list of packages to the local disk.


Instruction:
Make a summary of the following Python 3 code
def diff(*paths, **kwargs):
    '''
    Return a formatted diff between current files and original in a package.
    NOTE: this function includes all files (configuration and not), but does
    not work on binary content.

    :param path: Full path to the installed file
    :return: Difference string or raises and exception if examined file is binary.

    CLI example:

    .. code-block:: bash

        salt '*' pkg.diff /etc/apache2/httpd.conf /etc/sudoers
    '''
    ret = {}

    pkg_to_paths = {}
    for pth in paths:
        pth_pkg = __salt__['lowpkg.owner'](pth)
        if not pth_pkg:
            ret[pth] = os.path.exists(pth) and 'Not managed' or 'N/A'
        else:
            if pkg_to_paths.get(pth_pkg) is None:
                pkg_to_paths[pth_pkg] = []
            pkg_to_paths[pth_pkg].append(pth)

    if pkg_to_paths:
        local_pkgs = __salt__['pkg.download'](*pkg_to_paths.keys())
        for pkg, files in pkg_to_paths.items():
            for path in files:
                ret[path] = __salt__['lowpkg.diff'](
                    local_pkgs[pkg]['path'], path) or 'Unchanged'

    return ret

Response: Return a formatted diff between current files and original files in a package.


Instruction:
Given the following Python 3 function, write the documentation
def _get_patches(installed_only=False):
    '''
    List all known patches in repos.
    '''
    patches = {}

    cmd = [_yum(), '--quiet', 'updateinfo', 'list', 'all']
    ret = __salt__['cmd.run_stdout'](
        cmd,
        python_shell=False
    )
    for line in salt.utils.itertools.split(ret, os.linesep):
        inst, advisory_id, sev, pkg = re.match(r'([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)',
                                               line).groups()
        if inst != 'i' and installed_only:
            continue
        patches[advisory_id] = {
            'installed': True if inst == 'i' else False,
            'summary': pkg
        }
    return patches

Response: Return a dict of all known patches in repos.


Instruction:
Can you generate the documentation for the following Python 3 function
def complete_transaction(cleanup_only=False, recursive=False, max_attempts=3):
    '''
    .. versionadded:: Fluorine

    Execute ``yum-complete-transaction``, which is provided by the ``yum-utils`` package.

    cleanup_only
        Specify if the ``--cleanup-only`` option should be supplied.

    recursive
        Specify if ``yum-complete-transaction`` should be called recursively
        (it only completes one transaction at a time).

    max_attempts
        If ``recursive`` is ``True``, the maximum times ``yum-complete-transaction`` should be called.

    .. note::

        Recursive calls will stop once ``No unfinished transactions left.`` is in the returned output.

    .. note::

        ``yum-utils`` will already be installed on the minion if the package
        was installed from the Fedora / EPEL repositories.

    CLI example:

    .. code-block:: bash

        salt '*' pkg.complete_transaction
        salt '*' pkg.complete_transaction cleanup_only=True
        salt '*' pkg.complete_transaction recursive=True max_attempts=5
    '''

    return _complete_transaction(cleanup_only, recursive, max_attempts, 1, [])

Response: Returns a new version of the base base


Instruction:
Given the following Python 3 function, write the documentation
def _complete_transaction(cleanup_only, recursive, max_attempts, run_count, cmd_ret_list):
    '''
    .. versionadded:: Fluorine

    Called from ``complete_transaction`` to protect the arguments
    used for tail recursion, ``run_count`` and ``cmd_ret_list``.
    '''

    cmd = ['yum-complete-transaction']
    if cleanup_only:
        cmd.append('--cleanup-only')

    cmd_ret_list.append(__salt__['cmd.run_all'](
        cmd,
        output_loglevel='trace',
        python_shell=False
    ))

    if (cmd_ret_list[-1]['retcode'] == salt.defaults.exitcodes.EX_OK and
            recursive and
            'No unfinished transactions left.' not in cmd_ret_list[-1]['stdout']):
        if run_count >= max_attempts:
            cmd_ret_list[-1]['retcode'] = salt.defaults.exitcodes.EX_GENERIC
            log.error('Attempt %s/%s exceeded `max_attempts` for command: `%s`',
                      run_count, max_attempts, ' '.join(cmd))
            raise CommandExecutionError('The `max_attempts` limit was reached and unfinished transactions remain.'
                                        ' You may wish to increase `max_attempts` or re-execute this module.',
                                        info={'results': cmd_ret_list})
        else:
            return _complete_transaction(cleanup_only, recursive, max_attempts, run_count + 1, cmd_ret_list)

    return cmd_ret_list

Response: Helper function to run the yum - complete - transaction command and return the list of output


Instruction:
Here you have a function in Python 3, explain what it does
def present(name, value, zone, record_type, ttl=None, identifier=None, region=None, key=None,
            keyid=None, profile=None, wait_for_sync=True, split_dns=False, private_zone=False):
    '''
    Ensure the Route53 record is present.

    name
        Name of the record.

    value
        Value of the record.  As a special case, you can pass in:
            `private:<Name tag>` to have the function autodetermine the private IP
            `public:<Name tag>` to have the function autodetermine the public IP

    zone
        The zone to create the record in.

    record_type
        The record type (A, NS, MX, TXT, etc.)

    ttl
        The time to live for the record.

    identifier
        The unique identifier to use for this record.

    region
        The region to connect to.

    key
        Secret key to be used.

    keyid
        Access key to be used.

    profile
        A dict with region, key and keyid, or a pillar key (string) that contains a dict
        with region, key and keyid.

    wait_for_sync
        Wait for an INSYNC change status from Route53 before returning success.

    split_dns
        Route53 supports parallel public and private DNS zones with the same name.

    private_zone
        If using split_dns, specify if this is the private zone.
    '''
    ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}

    # If a list is passed in for value, change it to a comma-separated string
    # So it will work with subsequent boto module calls and string functions
    if isinstance(value, list):
        value = ','.join(value)
    elif value.startswith('private:') or value.startswith('public:'):
        name_tag = value.split(':', 1)[1]
        in_states = ('pending', 'rebooting', 'running', 'stopping', 'stopped')
        r = __salt__['boto_ec2.find_instances'](name=name_tag,
                                                return_objs=True,
                                                in_states=in_states,
                                                profile=profile)
        if not r:
            ret['comment'] = 'Error: instance with Name tag {0} not found'.format(name_tag)
            ret['result'] = False
            return ret
        if len(r) > 1:
            ret['comment'] = 'Error: Name tag {0} matched more than one instance'.format(name_tag)
            ret['result'] = False
            return ret
        instance = r[0]
        private_ip = getattr(instance, 'private_ip_address', None)
        public_ip = getattr(instance, 'ip_address', None)
        if value.startswith('private:'):
            value = private_ip
            log.info('Found private IP %s for instance %s', private_ip, name_tag)
        else:
            if public_ip is None:
                ret['comment'] = 'Error: No Public IP assigned to instance with Name {0}'.format(name_tag)
                ret['result'] = False
                return ret
            value = public_ip
            log.info('Found public IP %s for instance %s', public_ip, name_tag)

    try:
        record = __salt__['boto_route53.get_record'](name, zone, record_type,
                                                     False, region, key, keyid,
                                                     profile, split_dns,
                                                     private_zone, identifier)
    except SaltInvocationError as err:
        ret['comment'] = 'Error: {0}'.format(err)
        ret['result'] = False
        return ret

    if isinstance(record, dict) and not record:
        if __opts__['test']:
            ret['comment'] = 'Route53 record {0} set to be added.'.format(name)
            ret['result'] = None
            return ret
        added = __salt__['boto_route53.add_record'](name, value, zone,
                                                    record_type, identifier,
                                                    ttl, region, key, keyid,
                                                    profile, wait_for_sync,
                                                    split_dns, private_zone)
        if added:
            ret['changes']['old'] = None
            ret['changes']['new'] = {'name': name,
                                     'value': value,
                                     'record_type': record_type,
                                     'ttl': ttl,
                                     'identifier': identifier}
            ret['comment'] = 'Added {0} Route53 record.'.format(name)
        else:
            ret['result'] = False
            ret['comment'] = 'Failed to add {0} Route53 record.'.format(name)
            return ret
    elif record:
        need_to_update = False
        # Values can be a comma separated list and some values will end with a
        # period (even if we set it without one). To easily check this we need
        # to split and check with the period stripped from the input and what's
        # in route53.
        # TODO: figure out if this will cause us problems with some records.
        _values = [x.rstrip('.') for x in value.split(',')]
        _r_values = [x.rstrip('.') for x in record['value'].split(',')]
        _values.sort()
        _r_values.sort()
        if _values != _r_values:
            need_to_update = True
        if identifier and identifier != record['identifier']:
            need_to_update = True
        if ttl and six.text_type(ttl) != six.text_type(record['ttl']):
            need_to_update = True
        if need_to_update:
            if __opts__['test']:
                ret['comment'] = 'Route53 record {0} set to be updated.'.format(name)
                ret['result'] = None
                return ret
            updated = __salt__['boto_route53.update_record'](name, value, zone,
                                                             record_type,
                                                             identifier, ttl,
                                                             region, key,
                                                             keyid, profile,
                                                             wait_for_sync,
                                                             split_dns,
                                                             private_zone)
            if updated:
                ret['changes']['old'] = record
                ret['changes']['new'] = {'name': name,
                                         'value': value,
                                         'record_type': record_type,
                                         'ttl': ttl,
                                         'identifier': identifier}
                ret['comment'] = 'Updated {0} Route53 record.'.format(name)
            else:
                ret['result'] = False
                ret['comment'] = 'Failed to update {0} Route53 record.'.format(name)
        else:
            ret['comment'] = '{0} exists.'.format(name)
    return ret

Response: Ensure a Route53 record is present.