Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport #50963 to 2017.7 #51452

Merged
merged 5 commits into from Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 8 additions & 3 deletions salt/output/nested.py
Expand Up @@ -34,6 +34,11 @@
from salt.utils import get_colors
import salt.utils.locales

try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping


class NestDisplay(object):
'''
Expand Down Expand Up @@ -109,19 +114,19 @@ def display(self, ret, indent, prefix, out):
first_line = False
elif isinstance(ret, (list, tuple)):
for ind in ret:
if isinstance(ind, (list, tuple, dict)):
if isinstance(ind, (list, tuple, Mapping)):
out.append(
self.ustring(
indent,
self.GREEN,
'|_'
)
)
prefix = '' if isinstance(ind, dict) else '- '
prefix = '' if isinstance(ind, Mapping) else '- '
self.display(ind, indent + 2, prefix, out)
else:
self.display(ind, indent, '- ', out)
elif isinstance(ret, dict):
elif isinstance(ret, Mapping):
if indent:
out.append(
self.ustring(
Expand Down
58 changes: 21 additions & 37 deletions salt/states/pip_state.py
Expand Up @@ -27,6 +27,7 @@

# Import salt libs
import salt.utils
import salt.utils.data
from salt.version import SaltStackVersion as _SaltStackVersion
from salt.exceptions import CommandExecutionError, CommandNotFoundError

Expand Down Expand Up @@ -81,20 +82,6 @@ def __virtual__():
return False


def _find_key(prefix, pip_list):
'''
Does a case-insensitive match in the pip_list for the desired package.
'''
try:
match = next(
iter(x for x in pip_list if x.lower() == prefix.lower())
)
except StopIteration:
return None
else:
return match


def _fulfills_version_spec(version, version_spec):
'''
Check version number against version specification info and return a
Expand Down Expand Up @@ -210,23 +197,20 @@ def _check_if_installed(prefix, state_pkg_name, version_spec, ignore_installed,
ret = {'result': False, 'comment': None}

# If we are not passed a pip list, get one:
if not pip_list:
pip_list = __salt__['pip.list'](prefix, bin_env=bin_env,
user=user, cwd=cwd,
env_vars=env_vars, **kwargs)

# Check if the requested package is already installed.
prefix_realname = _find_key(prefix, pip_list)
pip_list = salt.utils.data.CaseInsensitiveDict(
pip_list or __salt__['pip.list'](prefix, bin_env=bin_env,
user=user, cwd=cwd,
env_vars=env_vars, **kwargs)
)

# If the package was already installed, check
# the ignore_installed and force_reinstall flags
if ignore_installed is False and prefix_realname is not None:
if ignore_installed is False and prefix in pip_list:
if force_reinstall is False and not upgrade:
# Check desired version (if any) against currently-installed
if (
any(version_spec) and
_fulfills_version_spec(pip_list[prefix_realname],
version_spec)
_fulfills_version_spec(pip_list[prefix], version_spec)
) or (not any(version_spec)):
ret['result'] = True
ret['comment'] = ('Python package {0} was already '
Expand All @@ -246,7 +230,7 @@ def _check_if_installed(prefix, state_pkg_name, version_spec, ignore_installed,
if 'rc' in spec[1]:
include_rc = True
available_versions = __salt__['pip.list_all_versions'](
prefix_realname, bin_env=bin_env, include_alpha=include_alpha,
prefix, bin_env=bin_env, include_alpha=include_alpha,
include_beta=include_beta, include_rc=include_rc, user=user,
cwd=cwd)
desired_version = ''
Expand All @@ -262,9 +246,9 @@ def _check_if_installed(prefix, state_pkg_name, version_spec, ignore_installed,
ret['comment'] = ('Python package {0} was already '
'installed and\nthe available upgrade '
'doesn\'t fulfills the version '
'requirements'.format(prefix_realname))
'requirements'.format(prefix))
return ret
if _pep440_version_cmp(pip_list[prefix_realname], desired_version) == 0:
if _pep440_version_cmp(pip_list[prefix], desired_version) == 0:
ret['result'] = True
ret['comment'] = ('Python package {0} was already '
'installed'.format(state_pkg_name))
Expand Down Expand Up @@ -898,10 +882,12 @@ def installed(name,

# Case for packages that are not an URL
if prefix:
pipsearch = __salt__['pip.list'](prefix, bin_env,
user=user, cwd=cwd,
env_vars=env_vars,
**kwargs)
pipsearch = salt.utils.data.CaseInsensitiveDict(
__salt__['pip.list'](prefix, bin_env,
user=user, cwd=cwd,
env_vars=env_vars,
**kwargs)
)

# If we didn't find the package in the system after
# installing it report it
Expand All @@ -912,12 +898,10 @@ def installed(name,
'\'pip.freeze\'.'.format(pkg)
)
else:
pkg_name = _find_key(prefix, pipsearch)
if pkg_name.lower() in already_installed_packages:
continue
ver = pipsearch[pkg_name]
ret['changes']['{0}=={1}'.format(pkg_name,
ver)] = 'Installed'
if prefix in pipsearch \
and prefix.lower() not in already_installed_packages:
ver = pipsearch[prefix]
ret['changes']['{0}=={1}'.format(prefix, ver)] = 'Installed'
# Case for packages that are an URL
else:
ret['changes']['{0}==???'.format(state_name)] = 'Installed'
Expand Down
99 changes: 99 additions & 0 deletions salt/utils/data.py
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
'''
Functions for manipulating, inspecting, or otherwise working with data types
and data structures.
'''

from __future__ import absolute_import, print_function, unicode_literals

try:
from collections.abc import Mapping, MutableMapping, Sequence
except ImportError:
from collections import Mapping, MutableMapping, Sequence

# Import Salt libs
from salt.utils.odict import OrderedDict

# Import 3rd-party libs
from salt.ext import six


class CaseInsensitiveDict(MutableMapping):
'''
Inspired by requests' case-insensitive dict implementation, but works with
non-string keys as well.
'''
def __init__(self, init=None, **kwargs):
'''
Force internal dict to be ordered to ensure a consistent iteration
order, irrespective of case.
'''
self._data = OrderedDict()
self.update(init or {}, **kwargs)

def __len__(self):
return len(self._data)

def __setitem__(self, key, value):
# Store the case-sensitive key so it is available for dict iteration
self._data[to_lowercase(key)] = (key, value)

def __delitem__(self, key):
del self._data[to_lowercase(key)]

def __getitem__(self, key):
return self._data[to_lowercase(key)][1]

def __iter__(self):
return (item[0] for item in six.itervalues(self._data))

def __eq__(self, rval):
if not isinstance(rval, Mapping):
# Comparing to non-mapping type (e.g. int) is always False
return False
return dict(self.items_lower()) == dict(CaseInsensitiveDict(rval).items_lower())

def __repr__(self):
return repr(dict(six.iteritems(self)))

def items_lower(self):
'''
Returns a generator iterating over keys and values, with the keys all
being lowercase.
'''
return ((key, val[1]) for key, val in six.iteritems(self._data))

def copy(self):
'''
Returns a copy of the object
'''
return CaseInsensitiveDict(six.iteritems(self._data))


def __change_case(data, attr, preserve_dict_class=False):
try:
return getattr(data, attr)()
except AttributeError:
pass

data_type = data.__class__

if isinstance(data, Mapping):
return (data_type if preserve_dict_class else dict)(
(__change_case(key, attr, preserve_dict_class),
__change_case(val, attr, preserve_dict_class))
for key, val in six.iteritems(data)
)
elif isinstance(data, Sequence):
return data_type(
__change_case(item, attr, preserve_dict_class) for item in data)
else:
return data


def to_lowercase(data, preserve_dict_class=False):
return __change_case(data, 'lower', preserve_dict_class)


def to_uppercase(data, preserve_dict_class=False):
return __change_case(data, 'upper', preserve_dict_class)