Fix pip idempotence in check mode

PIP package names must be case insensitive, and must consider hyphens
and underscores to be equivalent
(https://www.python.org/dev/peps/pep-0426/#name), because of this the
module didn't work correctly in check mode. For example if the passed
package name had a different case or an underscore instead of a hyphen
(or the other way around) compared to the installed package, check mode
reported as changed, even though packages were installed. Now the module
ignores case and hyphens/underscores in package names, so check mode
works correctly.
This commit is contained in:
Strahinja Kustudic 2018-08-20 15:26:12 +02:00 committed by Toshio Kuratomi
parent 96c2375692
commit b89b688d52
3 changed files with 33 additions and 2 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- "pip - idempotence in check mode now works correctly."

View file

@ -350,10 +350,11 @@ def _is_present(module, req, installed_pkgs, pkg_command):
for pkg in installed_pkgs: for pkg in installed_pkgs:
if '==' in pkg: if '==' in pkg:
pkg_name, pkg_version = pkg.split('==') pkg_name, pkg_version = pkg.split('==')
pkg_name = Package.canonicalize_name(pkg_name)
else: else:
continue continue
if pkg_name.lower() == req.package_name and req.is_satisfied_by(pkg_version): if pkg_name == req.package_name and req.is_satisfied_by(pkg_version):
return True return True
return False return False
@ -499,6 +500,8 @@ class Package:
test whether a package is already satisfied. test whether a package is already satisfied.
""" """
_CANONICALIZE_RE = re.compile(r'[-_.]+')
def __init__(self, name_string, version_string=None): def __init__(self, name_string, version_string=None):
self._plain_package = False self._plain_package = False
self.package_name = name_string self.package_name = name_string
@ -515,7 +518,7 @@ class Package:
self.package_name = "setuptools" self.package_name = "setuptools"
self._requirement.project_name = "setuptools" self._requirement.project_name = "setuptools"
else: else:
self.package_name = self._requirement.project_name self.package_name = Package.canonicalize_name(self._requirement.project_name)
self._plain_package = True self._plain_package = True
except ValueError as e: except ValueError as e:
pass pass
@ -539,6 +542,11 @@ class Package:
for op, ver in self._requirement.specs for op, ver in self._requirement.specs
) )
@staticmethod
def canonicalize_name(name):
# This is taken from PEP 503.
return Package._CANONICALIZE_RE.sub("-", name).lower()
def __str__(self): def __str__(self):
if self._plain_package: if self._plain_package:
return to_native(self._requirement) return to_native(self._requirement)

View file

@ -213,6 +213,27 @@
that: that:
- "not (q_check_mode is changed)" - "not (q_check_mode is changed)"
# Case with package name that has a different package name case and an
# underscore instead of a hyphen
- name: check for Junit-XML package
pip:
name: Junit-XML
virtualenv: "{{ output_dir }}/pipenv"
state: present
- name: check for Junit-XML package in check_mode
pip:
name: Junit-XML
virtualenv: "{{ output_dir }}/pipenv"
state: present
check_mode: True
register: diff_case_check_mode
- name: make sure Junit-XML in check_mode doesn't report changed
assert:
that:
- "diff_case_check_mode is not changed"
# ansible#23204 # ansible#23204
- name: ensure is a fresh virtualenv - name: ensure is a fresh virtualenv
file: file: