diff --git a/salt/loader.py b/salt/loader.py index c1ece247ba57..1c0945134671 100644 --- a/salt/loader.py +++ b/salt/loader.py @@ -1463,7 +1463,10 @@ def _load_module(self, name): # It default's of course to the found callable attribute name # if no alias is defined. funcname = getattr(mod, '__func_alias__', {}).get(attr, attr) - full_funcname = '{0}.{1}'.format(module_name, funcname) + try: + full_funcname = '.'.join((module_name, funcname)) + except TypeError: + full_funcname = '{0}.{1}'.format(module_name, funcname) # Save many references for lookups # Careful not to overwrite existing (higher priority) functions if full_funcname not in self._dict: diff --git a/salt/modules/jenkins.py b/salt/modules/jenkins.py index 8b7897e8ab9d..2b68b8ec1a8e 100644 --- a/salt/modules/jenkins.py +++ b/salt/modules/jenkins.py @@ -4,6 +4,11 @@ .. versionadded:: 2016.3.0 +:depends: python-jenkins_ Python module (not to be confused with jenkins_) + +.. _python-jenkins: https://pypi.python.org/pypi/python-jenkins +.. _jenkins: https://pypi.python.org/pypi/jenkins + :configuration: This module can be used by either passing an api key and version directly or by specifying both in a configuration profile in the salt master/minion config. @@ -45,9 +50,15 @@ def __virtual__(): :return: The virtual name of the module. ''' if HAS_JENKINS: - return __virtualname__ + if hasattr(jenkins, 'Jenkins'): + return __virtualname__ + else: + return (False, + 'The wrong Python module appears to be installed. Please ' + 'make sure that \'python-jenkins\' is installed, not ' + '\'jenkins\'.') return (False, 'The jenkins execution module cannot be loaded: ' - 'python jenkins library is not installed.') + 'python-jenkins is not installed.') def _connect(): diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index 7be0b9ace086..4601689d5183 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -2375,18 +2375,12 @@ def mod_repo(repo, basedir=None, **kwargs): ) # Build a list of keys to be deleted - todelete = ['disabled'] + todelete = [] for key in repo_opts: if repo_opts[key] != 0 and not repo_opts[key]: del repo_opts[key] todelete.append(key) - # convert disabled to enabled respectively from pkgrepo state - if 'enabled' not in repo_opts: - repo_opts['enabled'] = int(str(repo_opts.pop('disabled', False)).lower() != 'true') - else: - repo_opts.pop('disabled', False) - # Add baseurl or mirrorlist to the 'todelete' list if the other was # specified in the repo_opts if 'mirrorlist' in repo_opts: @@ -2458,6 +2452,7 @@ def mod_repo(repo, basedir=None, **kwargs): 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 @@ -2468,7 +2463,12 @@ def mod_repo(repo, basedir=None, **kwargs): del filerepos[stanza]['comments'] content += '\n[{0}]'.format(stanza) for line in six.iterkeys(filerepos[stanza]): - content += '\n{0}={1}'.format(line, filerepos[stanza][line]) + content += '\n{0}={1}'.format( + line, + filerepos[stanza][line] + if not isinstance(filerepos[stanza][line], bool) + else _bool_to_str(filerepos[stanza][line]) + ) content += '\n{0}\n'.format(comments) with salt.utils.fopen(repofile, 'w') as fileout: @@ -2514,9 +2514,6 @@ def _parse_repo_file(filename): 'Failed to parse line in %s, offending line was ' '\'%s\'', filename, line.rstrip() ) - # YUM uses enabled field - create the disabled field so that comparisons works correctly in state - if comps[0].strip() == 'enabled': - repos[repo]['disabled'] = comps[1] != "1" return (header, repos) diff --git a/salt/pillar/s3.py b/salt/pillar/s3.py index 6c5b9c724206..d821aa02a9aa 100644 --- a/salt/pillar/s3.py +++ b/salt/pillar/s3.py @@ -172,7 +172,7 @@ def ext_pillar(minion_id, pil = Pillar(opts, __grains__, minion_id, environment) - compiled_pillar = pil.compile_pillar() + compiled_pillar = pil.compile_pillar(ext=False) return compiled_pillar diff --git a/salt/pillar/svn_pillar.py b/salt/pillar/svn_pillar.py index 7be9ad9407e4..01f1fb0c1e9a 100644 --- a/salt/pillar/svn_pillar.py +++ b/salt/pillar/svn_pillar.py @@ -193,4 +193,4 @@ def ext_pillar(minion_id, opts = deepcopy(__opts__) opts['pillar_roots'][branch] = [pillar_dir] pil = Pillar(opts, __grains__, minion_id, branch) - return pil.compile_pillar() + return pil.compile_pillar(ext=False) diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py index 37ce64cef2dd..5f8a85ad4c16 100644 --- a/salt/states/pkgrepo.py +++ b/salt/states/pkgrepo.py @@ -110,7 +110,7 @@ def managed(name, ppa=None, **kwargs): `, :mod:`apt `, and :mod:`zypper ` repositories are supported. - **YUM OR ZYPPER-BASED SYSTEMS** + **YUM/DNF/ZYPPER-BASED SYSTEMS** .. note:: One of ``baseurl`` or ``mirrorlist`` below is required. Additionally, @@ -124,6 +124,16 @@ def managed(name, ppa=None, **kwargs): repo. Secondly, it will be the name of the file as stored in /etc/yum.repos.d (e.g. ``/etc/yum.repos.d/foo.conf``). + enabled : True + Whether or not the repo is enabled. Can be specified as True/False or + 1/0. + + disabled : False + Included to reduce confusion due to APT's use of the ``disabled`` + argument. If this is passed for a yum/dnf/zypper-based distro, then the + reverse will be passed as ``enabled``. For example passing + ``disabled=True`` will assume ``enabled=False``. + humanname This is used as the "name" value in the repo file in ``/etc/yum.repos.d/`` (or ``/etc/zypp/repos.d`` for SUSE distros). @@ -201,10 +211,16 @@ def managed(name, ppa=None, **kwargs): 'deb http://us.archive.ubuntu.com/ubuntu precise main': pkgrepo.managed - disabled + disabled : False Toggles whether or not the repo is used for resolving dependencies and/or installing packages. + enabled : True + Included to reduce confusion due to yum/dnf/zypper's use of the + ``enabled`` argument. If this is passed for an APT-based distro, then + the reverse will be passed as ``disabled``. For example, passing + ``enabled=False`` will assume ``disabled=False``. + comps On apt-based systems, comps dictate the types of packages to be installed from the repository (e.g. main, nonfree, ...). For @@ -279,14 +295,19 @@ def managed(name, ppa=None, **kwargs): 'intended.') return ret - if 'enabled' in kwargs: - salt.utils.warn_until( - 'Nitrogen', - 'The `enabled` argument has been deprecated in favor of ' - '`disabled`.' - ) + enabled = kwargs.pop('enabled', None) + disabled = kwargs.pop('disabled', None) + + if enabled is not None and disabled is not None: + ret['result'] = False + ret['comment'] = 'Only one of enabled/disabled is allowed' + return ret + elif enabled is None and disabled is None: + # If neither argument was passed we assume the repo will be enabled + enabled = True repo = name + os_family = __grains__['os_family'].lower() if __grains__['os'] in ('Ubuntu', 'Mint'): if ppa is not None: # overload the name/repo value for PPAs cleanly @@ -296,26 +317,26 @@ def managed(name, ppa=None, **kwargs): except TypeError: repo = ':'.join(('ppa', str(ppa))) - elif __grains__['os_family'].lower() in ('redhat', 'suse'): + kwargs['disabled'] = not salt.utils.is_true(enabled) \ + if enabled is not None \ + else salt.utils.is_true(disabled) + + elif os_family in ('redhat', 'suse'): if 'humanname' in kwargs: kwargs['name'] = kwargs.pop('humanname') - _val = lambda x: '1' if salt.utils.is_true(x) else '0' - if 'disabled' in kwargs: - if 'enabled' in kwargs: - ret['result'] = False - ret['comment'] = 'Only one of enabled/disabled is permitted' - return ret - _reverse = lambda x: '1' if x == '0' else '0' - kwargs['enabled'] = _reverse(_val(kwargs.pop('disabled'))) - elif 'enabled' in kwargs: - kwargs['enabled'] = _val(kwargs['enabled']) if 'name' not in kwargs: # Fall back to the repo name if humanname not provided kwargs['name'] = repo - # Replace 'enabled' from kwargs with 'disabled' - enabled = kwargs.pop('enabled', True) - kwargs['disabled'] = not salt.utils.is_true(enabled) + kwargs['enabled'] = not salt.utils.is_true(disabled) \ + if disabled is not None \ + else salt.utils.is_true(enabled) + + elif os_family == 'nilinuxrt': + # opkg is the pkg virtual + kwargs['enabled'] = not salt.utils.is_true(disabled) \ + if disabled is not None \ + else salt.utils.is_true(enabled) for kwarg in _STATE_INTERNAL_KEYWORDS: kwargs.pop(kwarg, None) @@ -340,11 +361,10 @@ def managed(name, ppa=None, **kwargs): else: sanitizedkwargs = kwargs - if __grains__['os_family'] == 'Debian': + if os_family == 'debian': repo = _strip_uri(repo) if pre: - needs_update = False for kwarg in sanitizedkwargs: if kwarg not in pre: if kwarg == 'enabled': @@ -352,33 +372,40 @@ def managed(name, ppa=None, **kwargs): # not explicitly set, so we don't need to update the repo # if it's desired to be enabled and the 'enabled' key is # missing from the repo definition - if __grains__['os_family'] == 'RedHat': + if os_family == 'redhat': if not salt.utils.is_true(sanitizedkwargs[kwarg]): - needs_update = True + break else: - needs_update = True + break else: - needs_update = True + break elif kwarg == 'comps': if sorted(sanitizedkwargs[kwarg]) != sorted(pre[kwarg]): - needs_update = True - elif kwarg == 'line' and __grains__['os_family'] == 'Debian': + break + elif kwarg == 'line' and os_family == 'debian': # split the line and sort everything after the URL sanitizedsplit = sanitizedkwargs[kwarg].split() sanitizedsplit[3:] = sorted(sanitizedsplit[3:]) reposplit = pre[kwarg].split() reposplit[3:] = sorted(reposplit[3:]) if sanitizedsplit != reposplit: - needs_update = True + break if 'comments' in kwargs: _line = pre[kwarg].split('#') if str(kwargs['comments']) not in _line: - needs_update = True + break else: - if str(sanitizedkwargs[kwarg]) != str(pre[kwarg]): - needs_update = True - - if not needs_update: + if os_family in ('redhat', 'suse') \ + and any(isinstance(x, bool) for x in + (sanitizedkwargs[kwarg], pre[kwarg])): + # This check disambiguates 1/0 from True/False + if salt.utils.is_true(sanitizedkwargs[kwarg]) != \ + salt.utils.is_true(pre[kwarg]): + break + else: + if str(sanitizedkwargs[kwarg]) != str(pre[kwarg]): + break + else: ret['result'] = True ret['comment'] = ('Package repo \'{0}\' already configured' .format(name)) @@ -399,7 +426,7 @@ def managed(name, ppa=None, **kwargs): pass try: - if __grains__['os_family'] == 'Debian': + if os_family == 'debian': __salt__['pkg.mod_repo'](repo, saltenv=__env__, **kwargs) else: __salt__['pkg.mod_repo'](repo, **kwargs) diff --git a/salt/utils/aws.py b/salt/utils/aws.py index a416eaaa02da..152e68f2020c 100644 --- a/salt/utils/aws.py +++ b/salt/utils/aws.py @@ -87,7 +87,7 @@ def creds(provider): proxies={'http': ''}, timeout=AWS_METADATA_TIMEOUT, ) result.raise_for_status() - role = result.text.encode(result.encoding) + role = result.text.encode(result.encoding or 'utf-8') except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError): return provider['id'], provider['key'], '' @@ -460,7 +460,7 @@ def query(params=None, setname=None, requesturl=None, location=None, ) LOG.trace( 'AWS Response Text: {0}'.format( - result.text.encode(result.encoding) + result.text.encode(result.encoding or 'utf-8') ) ) result.raise_for_status() @@ -501,7 +501,7 @@ def query(params=None, setname=None, requesturl=None, location=None, return {'error': data}, requesturl return {'error': data} - response = result.text.encode(result.encoding) + response = result.text.encode(result.encoding or 'utf-8') root = ET.fromstring(response) items = root[1] diff --git a/tests/unit/config/schemas/ssh_test.py b/tests/unit/config/schemas/ssh_test.py index 02b47ef7cc73..14f9bed3f617 100644 --- a/tests/unit/config/schemas/ssh_test.py +++ b/tests/unit/config/schemas/ssh_test.py @@ -7,6 +7,7 @@ ''' # Import python libs from __future__ import absolute_import, print_function +from distutils.version import LooseVersion as _LooseVersion # Import Salt Testing Libs from salttesting import TestCase, skipIf @@ -23,8 +24,10 @@ import jsonschema import jsonschema.exceptions HAS_JSONSCHEMA = True + JSONSCHEMA_VERSION = _LooseVersion(jsonschema.__version__) except ImportError: HAS_JSONSCHEMA = False + JSONSCHEMA_VERSION = _LooseVersion('0') class RoosterEntryConfigTest(TestCase): @@ -296,7 +299,13 @@ def test_roster_config_validate(self): ssh_schemas.RosterItem.serialize(), format_checker=jsonschema.FormatChecker() ) - self.assertIn( - 'Additional properties are not allowed (\'target-1:1\' was unexpected)', - excinfo.exception.message - ) + if JSONSCHEMA_VERSION < _LooseVersion('2.6.0'): + self.assertIn( + 'Additional properties are not allowed (\'target-1:1\' was unexpected)', + excinfo.exception.message + ) + else: + self.assertIn( + '\'target-1:1\' does not match any of the regexes', + excinfo.exception.message + ) diff --git a/tests/unit/utils/schema_test.py b/tests/unit/utils/schema_test.py index e94126e29464..3bc42edb1412 100644 --- a/tests/unit/utils/schema_test.py +++ b/tests/unit/utils/schema_test.py @@ -9,9 +9,7 @@ import json import yaml -from distutils.version import LooseVersion - -from distutils.version import LooseVersion +from distutils.version import LooseVersion as _LooseVersion # Import Salt Testing Libs from salttesting import TestCase, skipIf @@ -27,10 +25,10 @@ import jsonschema import jsonschema.exceptions HAS_JSONSCHEMA = True - JSONSCHEMA_VERSION = jsonschema.__version__ + JSONSCHEMA_VERSION = _LooseVersion(jsonschema.__version__) except ImportError: - JSONSCHEMA_VERSION = '' HAS_JSONSCHEMA = False + JSONSCHEMA_VERSION = _LooseVersion('0') # pylint: disable=unused-import @@ -754,8 +752,7 @@ def test_ipv4_config(self): } ) - @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing') - @skipIf(HAS_JSONSCHEMA and LooseVersion(jsonschema.__version__) <= LooseVersion('2.5.0'), 'Requires jsonschema 2.5.0 or greater') + @skipIf(JSONSCHEMA_VERSION <= _LooseVersion('2.5.0'), 'Requires jsonschema 2.5.0 or greater') def test_ipv4_config_validation(self): class TestConf(schema.Schema): item = schema.IPv4Item(title='Item', description='Item description') @@ -1707,7 +1704,14 @@ class TestConf(schema.Schema): with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo: jsonschema.validate({'item': {'color': 'green', 'sides': 4, 'surfaces': 4}}, TestConf.serialize()) - self.assertIn('Additional properties are not allowed', excinfo.exception.message) + if JSONSCHEMA_VERSION < _LooseVersion('2.6.0'): + self.assertIn( + 'Additional properties are not allowed', + excinfo.exception.message) + else: + self.assertIn( + '\'surfaces\' does not match any of the regexes', + excinfo.exception.message) class TestConf(schema.Schema): item = schema.DictItem(