From 214f2d6ad3457f2e4d0aa9b9a5f3ca8c1029f407 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Wed, 14 Mar 2018 23:33:51 -0500 Subject: [PATCH 1/4] Add pkg.list_repo_pkgs to zypper.py --- salt/modules/zypper.py | 117 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index fd36f260c7..e1b30c6828 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -706,6 +706,123 @@ def list_pkgs(versions_as_list=False, **kwargs): return ret +def list_repo_pkgs(*args, **kwargs): + ''' + .. versionadded:: 2017.7.5 + + 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. + + This function can be helpful in discovering the version or repo to specify + in a :mod:`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.3-83.3.1', + '4.3-82.6'], + 'vim': ['7.4.326-12.1'] + } + { + 'OSS': { + 'bash': ['4.3-82.6'], + 'vim': ['7.4.326-12.1'] + }, + 'OSS Update': { + 'bash': ['4.3-83.3.1'] + } + } + + fromrepo : None + Only include results from the specified repo(s). Multiple repos can be + specified, comma-separated. + + byrepo : False + When ``True``, the return data for each package will be organized by + repository. + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.list_repo_pkgs + salt '*' pkg.list_repo_pkgs foo bar baz + salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True + salt '*' pkg.list_repo_pkgs 'python2-*' fromrepo='OSS Updates' + ''' + byrepo = kwargs.pop('byrepo', False) + fromrepo = kwargs.pop('fromrepo', '') or '' + ret = {} + + targets = [ + arg if isinstance(arg, six.string_types) else six.text_type(arg) + for arg in args + ] + + def _is_match(pkgname): + ''' + When package names are passed to a zypper search, they will be matched + anywhere in the package name. This makes sure that only exact or + fnmatch matches are identified. + ''' + if not args: + # No package names passed, everyone's a winner! + return True + for target in targets: + if fnmatch.fnmatch(pkgname, target): + return True + return False + + for node in __zypper__.xml.call('se', '-s', *targets).getElementsByTagName('solvable'): + pkginfo = dict(node.attributes.items()) + try: + if pkginfo['kind'] != 'package': + continue + reponame = pkginfo['repository'] + if fromrepo and reponame != fromrepo: + continue + pkgname = pkginfo['name'] + pkgversion = pkginfo['edition'] + except KeyError: + continue + else: + if _is_match(pkgname): + repo_dict = ret.setdefault(reponame, {}) + version_list = repo_dict.setdefault(pkgname, set()) + version_list.add(pkgversion) + + 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 + + def _get_configured_repos(): ''' Get all the info about repositories from the configurations. From f3f5dec2392d6cd888f795ad795629488091b678 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Wed, 14 Mar 2018 23:34:36 -0500 Subject: [PATCH 2/4] zypper.py: fix version argument being ignored --- salt/modules/zypper.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index e1b30c6828..1a475fd934 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -1205,6 +1205,15 @@ def install(name=None, return {} version_num = Wildcard(__zypper__)(name, version) + + if version_num: + if pkgs is None and sources is None: + # Allow "version" to work for single package target + pkg_params = {name: version_num} + else: + log.warning('"version" parameter will be ignored for multiple ' + 'package targets') + if pkg_type == 'repository': targets = [] problems = [] From 010d260d061212548677aa3e5c300b824ff490d4 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Wed, 14 Mar 2018 23:35:25 -0500 Subject: [PATCH 3/4] Rewrite failing Suse pkg integration test This makes the test less brittle by hard-coding which package to use --- tests/integration/modules/test_pkg.py | 63 +++++++++++++++++---------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/tests/integration/modules/test_pkg.py b/tests/integration/modules/test_pkg.py index 742db38b3a..ad8a19ed41 100644 --- a/tests/integration/modules/test_pkg.py +++ b/tests/integration/modules/test_pkg.py @@ -272,38 +272,53 @@ class PkgModuleTest(ModuleCase, SaltReturnAssertsMixin): self.run_function('pkg.refresh_db') if os_family == 'Suse': - # pkg.latest version returns empty if the latest version is already installed - vim_version_dict = self.run_function('pkg.latest_version', ['vim']) - vim_info = self.run_function('pkg.info_available', ['vim'])['vim'] - if vim_version_dict == {}: - # Latest version is installed, get its version and construct - # a version selector so the immediately previous version is selected - vim_version = 'version=<'+vim_info['version'] - else: - # Vim was not installed, so pkg.latest_version returns the latest one. - # Construct a version selector so immediately previous version is selected - vim_version = 'version=<'+vim_version_dict + # This test assumes that there are multiple possible versions of a + # package available. That makes it brittle if you pick just one + # target, as changes in the available packages will break the test. + # Therefore, we'll choose from several packages to make sure we get + # one that is suitable for this test. + packages = ('hwinfo', 'avrdude', 'diffoscope', 'vim') - # Only install a new version of vim if vim is up-to-date, otherwise we don't - # need this check. (And the test will fail when we check for the empty dict - # since vim gets upgraded in the install step.) - if 'out-of-date' not in vim_info['status']: - # Install a version of vim that should need upgrading - ret = self.run_function('pkg.install', ['vim', vim_version]) - if not isinstance(ret, dict): - if ret.startswith('ERROR'): - self.skipTest('Could not install earlier vim to complete test.') + available = self.run_function('pkg.list_repo_pkgs', packages) + versions = self.run_function('pkg.version', packages) + + for package in packages: + try: + new, old = available[package][:2] + except (KeyError, ValueError): + # Package not available, or less than 2 versions + # available. This is not a suitable target. + continue else: - self.assertNotEqual(ret, {}) + target = package + current = versions[target] + break + else: + # None of the packages have more than one version available, so + # we need to find new package(s). pkg.list_repo_pkgs can be + # used to get an overview of the available packages. We should + # try to find packages with few dependencies and small download + # sizes, to keep this test from taking longer than necessary. + self.fail('No suitable package found for this test') - # Run a system upgrade, which should catch the fact that Vim needs upgrading, and upgrade it. + # Make sure we have the 2nd-oldest available version installed + ret = self.run_function('pkg.install', [target], version=old) + if not isinstance(ret, dict): + if ret.startswith('ERROR'): + self.skipTest( + 'Could not install older {0} to complete ' + 'test.'.format(target) + ) + + # Run a system upgrade, which should catch the fact that the + # targeted package needs upgrading, and upgrade it. ret = self.run_function(func) # The changes dictionary should not be empty. if 'changes' in ret: - self.assertIn('vim', ret['changes']) + self.assertIn(target, ret['changes']) else: - self.assertIn('vim', ret) + self.assertIn(target, ret) else: ret = self.run_function('pkg.list_upgrades') if ret == '' or ret == {}: From 703b5e7e65f9959cd5c9ddde53bebf33b4fafebb Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Thu, 15 Mar 2018 00:01:42 -0500 Subject: [PATCH 4/4] Change versionadded to show that 2018.3.0 will not have this function --- salt/modules/zypper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index 1a475fd934..7a308f0a25 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -708,7 +708,7 @@ def list_pkgs(versions_as_list=False, **kwargs): def list_repo_pkgs(*args, **kwargs): ''' - .. versionadded:: 2017.7.5 + .. versionadded:: 2017.7.5,2018.3.1 Returns all available packages. Optionally, package names (and name globs) can be passed and the results will be filtered to packages matching those