167167 HAS_PYTHON_APT = False
168168
169169def package_split (pkgspec ):
170- parts = pkgspec .split ('=' )
170+ parts = pkgspec .split ('=' , 1 )
171171 if len (parts ) > 1 :
172172 return parts [0 ], parts [1 ]
173173 else :
@@ -205,19 +205,34 @@ def package_status(m, pkgname, version, cache, state):
205205 # assume older version of python-apt is installed
206206 package_is_installed = pkg .isInstalled
207207
208- if version and package_is_installed :
208+ if version :
209209 try :
210210 installed_version = pkg .installed .version
211211 except AttributeError :
212212 installed_version = pkg .installedVersion
213- return package_is_installed and fnmatch .fnmatch (installed_version , version ), False , has_files
213+
214+ avail_upgrades = fnmatch .filter ((p .version for p in pkg .versions ), version )
215+
216+ if package_is_installed :
217+ # Only claim the package is installed if the version is matched as well
218+ package_is_installed = fnmatch .fnmatch (installed_version , version )
219+
220+ # Only claim the package is upgradable if a candidate matches the version
221+ package_is_upgradable = False
222+ for candidate in avail_upgrades :
223+ if pkg .versions [candidate ] > p .installed :
224+ package_is_upgradable = True
225+ break
226+ else :
227+ package_is_upgradable = bool (avail_upgrades )
214228 else :
215229 try :
216230 package_is_upgradable = pkg .is_upgradable
217231 except AttributeError :
218232 # assume older version of python-apt is installed
219233 package_is_upgradable = pkg .isUpgradable
220- return package_is_installed , package_is_upgradable , has_files
234+
235+ return package_is_installed , package_is_upgradable , has_files
221236
222237def expand_dpkg_options (dpkg_options_compressed ):
223238 options_list = dpkg_options_compressed .split (',' )
@@ -229,39 +244,54 @@ def expand_dpkg_options(dpkg_options_compressed):
229244
230245def expand_pkgspec_from_fnmatches (m , pkgspec , cache ):
231246 new_pkgspec = []
232- for pkgname_or_fnmatch_pattern in pkgspec :
233- # note that any of these chars is not allowed in a (debian) pkgname
234- if [ c for c in pkgname_or_fnmatch_pattern if c in "*?[]!" ]:
235- if "=" in pkgname_or_fnmatch_pattern :
236- m . fail_json ( msg = "pkgname wildcard and version can not be mixed" )
247+ for pkgspec_pattern in pkgspec :
248+ pkgname_pattern , version = package_split ( pkgspec_pattern )
249+
250+ # note that none of these chars is allowed in a (debian) pkgname
251+ if frozenset ( '*?[]!' ). intersection ( pkgname_pattern ):
237252 # handle multiarch pkgnames, the idea is that "apt*" should
238253 # only select native packages. But "apt*:i386" should still work
239- if not ":" in pkgname_or_fnmatch_pattern :
240- matches = fnmatch .filter (
241- [pkg .name for pkg in cache
242- if not ":" in pkg .name ], pkgname_or_fnmatch_pattern )
254+ if not ":" in pkgname_pattern :
255+ try :
256+ pkg_name_cache = _non_multiarch
257+ except NameError :
258+ pkg_name_cache = _non_multiarch = [pkg .name for pkg in cache if not ':' in pkg .name ]
243259 else :
244- matches = fnmatch .filter (
245- [pkg .name for pkg in cache ], pkgname_or_fnmatch_pattern )
260+ try :
261+ pkg_name_cache = _all_pkg_names
262+ except NameError :
263+ pkg_name_cache = _all_pkg_names = [pkg .name for pkg in cache ]
264+ matches = fnmatch .filter (pkg_name_cache , pkgname_pattern )
246265
247266 if len (matches ) == 0 :
248- m .fail_json (msg = "No package(s) matching '%s' available" % str (pkgname_or_fnmatch_pattern ))
267+ m .fail_json (msg = "No package(s) matching '%s' available" % str (pkgname_pattern ))
249268 else :
250269 new_pkgspec .extend (matches )
251270 else :
252- new_pkgspec .append (pkgname_or_fnmatch_pattern )
271+ # No wildcards in name
272+ new_pkgspec .append (pkgspec_pattern )
253273 return new_pkgspec
254274
255275def install (m , pkgspec , cache , upgrade = False , default_release = None ,
256276 install_recommends = True , force = False ,
257277 dpkg_options = expand_dpkg_options (DPKG_OPTIONS )):
278+ pkg_list = []
258279 packages = ""
259280 pkgspec = expand_pkgspec_from_fnmatches (m , pkgspec , cache )
260281 for package in pkgspec :
261282 name , version = package_split (package )
262283 installed , upgradable , has_files = package_status (m , name , version , cache , state = 'install' )
263284 if not installed or (upgrade and upgradable ):
264- packages += "'%s' " % package
285+ pkg_list .append ("'%s'" % package )
286+ if installed and upgradable and version :
287+ # This happens when the package is installed, a newer version is
288+ # available, and the version is a wildcard that matches both
289+ #
290+ # We do not apply the upgrade flag because we cannot specify both
291+ # a version and state=latest. (This behaviour mirrors how apt
292+ # treats a version with wildcard in the package)
293+ pkg_list .append ("'%s'" % package )
294+ packages = ' ' .join (pkg_list )
265295
266296 if len (packages ) != 0 :
267297 if force :
@@ -350,13 +380,14 @@ def install_deb(m, debs, cache, force, install_recommends, dpkg_options):
350380
351381def remove (m , pkgspec , cache , purge = False ,
352382 dpkg_options = expand_dpkg_options (DPKG_OPTIONS )):
353- packages = ""
383+ pkg_list = []
354384 pkgspec = expand_pkgspec_from_fnmatches (m , pkgspec , cache )
355385 for package in pkgspec :
356386 name , version = package_split (package )
357387 installed , upgradable , has_files = package_status (m , name , version , cache , state = 'remove' )
358388 if installed or (has_files and purge ):
359- packages += "'%s' " % package
389+ pkg_list .append ("'%s'" % package )
390+ packages = ' ' .join (pkg_list )
360391
361392 if len (packages ) == 0 :
362393 m .exit_json (changed = False )
@@ -567,4 +598,5 @@ def main():
567598# import module snippets
568599from ansible .module_utils .basic import *
569600
570- main ()
601+ if __name__ == "__main__" :
602+ main ()
0 commit comments