From 0aa0223de45541fdccb178675a5c6b57665137336be93423495f23d5bfe4d461 Mon Sep 17 00:00:00 2001 From: Jochen Breuer Date: Mon, 30 Apr 2018 12:06:44 +0000 Subject: [PATCH] Accepting request 601028 from systemsmanagement:saltstack:testing - Fix minion scheduler to return a 'retcode' attribute (bsc#1089112) - Fix for logging during network interface querying (bsc#1087581) - Fix rhel packages requires both net-tools and iproute (bsc#1087055) - Added: * initialize-__context__-retcode-for-functions-handled.patch - Modified: * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch - Fix patchinstall on yum module. Bad comparison (bsc#1087278) - Added: * provide-kwargs-to-pkg_resource.parse_targets-require.patch - Strip trailing commas on Linux user's GECOS fields (bsc#1089362) - Fallback to PyMySQL (bsc#1087891) - Improved test for fqdns - Update SaltSSH patch - Fix for [Errno 0] Resolver Error 0 (no error) (bsc#1087581) * Lintfix: PEP8 ident * Use proper levels of the error handling, use proper log formatting. * Fix unit test for reversed fqdns return data - Added: * strip-trailing-commas-on-linux-user-gecos-fields.patch * fall-back-to-pymysql.patch * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch - Modified: * add-saltssh-multi-version-support-across-python-inte.patch OBS-URL: https://build.opensuse.org/request/show/601028 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=119 --- _lastrevision | 2 +- _service | 2 +- ...i-version-support-across-python-inte.patch | 327 ++++++++++++++---- fall-back-to-pymysql.patch | 317 +++++++++++++++++ ...resolver-error-0-no-error-bsc-108758.patch | 91 +++++ ...text__-retcode-for-functions-handled.patch | 27 ++ ...o-pkg_resource.parse_targets-require.patch | 37 ++ salt.changes | 41 +++ salt.spec | 22 +- ...ng-commas-on-linux-user-gecos-fields.patch | 55 +++ 10 files changed, 848 insertions(+), 73 deletions(-) create mode 100644 fall-back-to-pymysql.patch create mode 100644 fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch create mode 100644 initialize-__context__-retcode-for-functions-handled.patch create mode 100644 provide-kwargs-to-pkg_resource.parse_targets-require.patch create mode 100644 strip-trailing-commas-on-linux-user-gecos-fields.patch diff --git a/_lastrevision b/_lastrevision index 98cd9fe..cc6fc44 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -f43b8fb2425e3371decf3cde040c70ed15de375d \ No newline at end of file +057b96d7f072dc69b61ca9e3b8d04a890003e58f \ No newline at end of file diff --git a/_service b/_service index c995eeb..998165c 100644 --- a/_service +++ b/_service @@ -3,7 +3,7 @@ https://github.com/openSUSE/salt-packaging.git salt package - oxygen-rc1 + 2018.3.0 git diff --git a/add-saltssh-multi-version-support-across-python-inte.patch b/add-saltssh-multi-version-support-across-python-inte.patch index b656153..43652c9 100644 --- a/add-saltssh-multi-version-support-across-python-inte.patch +++ b/add-saltssh-multi-version-support-across-python-inte.patch @@ -1,4 +1,4 @@ -From 7d3c1fee891a34f1e521228458ab113c3f6dabe1 Mon Sep 17 00:00:00 2001 +From 36bc22560e050b7afe3d872aed99c0cdb9fde282 Mon Sep 17 00:00:00 2001 From: Bo Maryniuk Date: Mon, 12 Mar 2018 12:01:39 +0100 Subject: [PATCH] Add SaltSSH multi-version support across Python @@ -227,16 +227,45 @@ Disable wiping if state is executed Properly mock a tempfile object Support Python 2.6 versions + +Add digest collector for file trees etc + +Bufix: recurse calls damages the configuration (reference problem) + +Collect digest of the code + +Get code checksum into the shim options + +Get all the code content, not just Python sources + +Bugfix: Python3 compat - string required instead of bytes + +Lintfix: too many empty lines + +Lintfix: blocked function used + +Bugfix: key error master_tops_first + +Fix unit tests for the checksum generator + +Use code checksum to update thin archive on client's cache + +Lintfix + +Set master_top_first to False by default --- doc/topics/releases/fluorine.rst | 178 +++++++++++ - salt/client/ssh/__init__.py | 20 +- - salt/client/ssh/ssh_py_shim.py | 88 ++++-- + salt/client/ssh/__init__.py | 66 ++-- + salt/client/ssh/ssh_py_shim.py | 95 ++++-- salt/client/ssh/wrapper/__init__.py | 2 +- + salt/config/__init__.py | 1 + salt/modules/zfs.py | 4 +- salt/modules/zpool.py | 4 +- - salt/utils/thin.py | 434 +++++++++++++++++++------- - tests/unit/utils/test_thin.py | 607 ++++++++++++++++++++++++++++++++++++ - 8 files changed, 1182 insertions(+), 155 deletions(-) + salt/state.py | 2 +- + salt/utils/hashutils.py | 37 +++ + salt/utils/thin.py | 450 +++++++++++++++++++------- + tests/unit/utils/test_thin.py | 612 ++++++++++++++++++++++++++++++++++++ + 11 files changed, 1265 insertions(+), 186 deletions(-) create mode 100644 doc/topics/releases/fluorine.rst create mode 100644 tests/unit/utils/test_thin.py @@ -425,7 +454,7 @@ index 0000000000..40c69e25cc +Salt version is also available on the Master machine, although does not need to be directly +installed together with the older Python interpreter. diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py -index f1c1ad9a22..ea5c700830 100644 +index f1c1ad9a22..399facf5c8 100644 --- a/salt/client/ssh/__init__.py +++ b/salt/client/ssh/__init__.py @@ -150,14 +150,10 @@ EX_PYTHON_INVALID={EX_THIN_PYTHON_INVALID} @@ -470,16 +499,71 @@ index f1c1ad9a22..ea5c700830 100644 if kwargs.get('thin_dir'): self.thin_dir = kwargs['thin_dir'] elif self.winrm: -@@ -1168,7 +1165,6 @@ class Single(object): +@@ -1161,38 +1158,39 @@ class Single(object): + cachedir = self.opts['_caller_cachedir'] + else: + cachedir = self.opts['cachedir'] +- thin_sum = salt.utils.thin.thin_sum(cachedir, 'sha1') ++ thin_code_digest, thin_sum = salt.utils.thin.thin_sum(cachedir, 'sha1') + debug = '' + if not self.opts.get('log_level'): + self.opts['log_level'] = 'info' if salt.log.LOG_LEVELS['debug'] >= salt.log.LOG_LEVELS[self.opts.get('log_level', 'info')]: debug = '1' arg_str = ''' -OPTIONS = OBJ() OPTIONS.config = \ """ - {0} +-{0} ++{config} + """ +-OPTIONS.delimiter = '{1}' +-OPTIONS.saltdir = '{2}' +-OPTIONS.checksum = '{3}' +-OPTIONS.hashfunc = '{4}' +-OPTIONS.version = '{5}' +-OPTIONS.ext_mods = '{6}' +-OPTIONS.wipe = {7} +-OPTIONS.tty = {8} +-OPTIONS.cmd_umask = {9} +-ARGS = {10}\n'''.format(self.minion_config, +- RSTR, +- self.thin_dir, +- thin_sum, +- 'sha1', +- salt.version.__version__, +- self.mods.get('version', ''), +- self.wipe, +- self.tty, +- self.cmd_umask, +- self.argv) ++OPTIONS.delimiter = '{delimeter}' ++OPTIONS.saltdir = '{saltdir}' ++OPTIONS.checksum = '{checksum}' ++OPTIONS.hashfunc = '{hashfunc}' ++OPTIONS.version = '{version}' ++OPTIONS.ext_mods = '{ext_mods}' ++OPTIONS.wipe = {wipe} ++OPTIONS.tty = {tty} ++OPTIONS.cmd_umask = {cmd_umask} ++OPTIONS.code_checksum = {code_checksum} ++ARGS = {arguments}\n'''.format(config=self.minion_config, ++ delimeter=RSTR, ++ saltdir=self.thin_dir, ++ checksum=thin_sum, ++ hashfunc='sha1', ++ version=salt.version.__version__, ++ ext_mods=self.mods.get('version', ''), ++ wipe=self.wipe, ++ tty=self.tty, ++ cmd_umask=self.cmd_umask, ++ code_checksum=thin_code_digest, ++ arguments=self.argv) + py_code = SSH_PY_SHIM.replace('#%%OPTS', arg_str) + if six.PY2: + py_code_enc = py_code.encode('base64') diff --git a/salt/client/ssh/ssh_py_shim.py b/salt/client/ssh/ssh_py_shim.py -index e46220fc80..661a671b81 100644 +index e46220fc80..21d03343b9 100644 --- a/salt/client/ssh/ssh_py_shim.py +++ b/salt/client/ssh/ssh_py_shim.py @@ -16,11 +16,13 @@ import sys @@ -587,7 +671,7 @@ index e46220fc80..661a671b81 100644 def main(argv): # pylint: disable=W0613 ''' Main program body -@@ -215,30 +261,30 @@ def main(argv): # pylint: disable=W0613 +@@ -215,32 +261,25 @@ def main(argv): # pylint: disable=W0613 if scpstat != 0: sys.exit(EX_SCP_NOT_FOUND) @@ -604,33 +688,35 @@ index e46220fc80..661a671b81 100644 - version_path = os.path.normpath(os.path.join(OPTIONS.saltdir, 'version')) - if not os.path.exists(version_path) or not os.path.isfile(version_path): -+ if not os.path.exists(OPTIONS.saltdir): -+ need_deployment() -+ -+ checksum_path = os.path.normpath(os.path.join(OPTIONS.saltdir, 'thin_checksum')) -+ if not os.path.exists(checksum_path) or not os.path.isfile(checksum_path): - sys.stderr.write( - 'WARNING: Unable to locate current thin ' +- sys.stderr.write( +- 'WARNING: Unable to locate current thin ' - ' version: {0}.\n'.format(version_path) -+ ' checksum: {0}.\n'.format(checksum_path) - ) +- ) ++ if not os.path.exists(OPTIONS.saltdir): need_deployment() - with open(version_path, 'r') as vpo: - cur_version = vpo.readline().strip() - if cur_version != OPTIONS.version: -+ with open(checksum_path, 'r') as vpo: -+ cur_checksum = vpo.readline().strip() -+ if cur_checksum != OPTIONS.checksum: - sys.stderr.write( +- sys.stderr.write( - 'WARNING: current thin version {0}' -+ 'WARNING: current thin checksum {0}' - ' is not up-to-date with {1}.\n'.format( +- ' is not up-to-date with {1}.\n'.format( - cur_version, OPTIONS.version -+ cur_checksum, OPTIONS.checksum - ) - ) +- ) +- ) ++ ++ code_checksum_path = os.path.normpath(os.path.join(OPTIONS.saltdir, 'code-checksum')) ++ if not os.path.exists(code_checksum_path) or not os.path.isfile(code_checksum_path): ++ sys.stderr.write('WARNING: Unable to locate current code checksum: {0}.\n'.format(code_checksum_path)) ++ need_deployment() ++ with open(code_checksum_path, 'r') as vpo: ++ cur_code_cs = vpo.readline().strip() ++ if cur_code_cs != OPTIONS.code_checksum: ++ sys.stderr.write('WARNING: current code checksum {0} is different to {1}.\n'.format(cur_code_cs, ++ OPTIONS.code_checksum)) need_deployment() -@@ -270,7 +316,7 @@ def main(argv): # pylint: disable=W0613 + # Salt thin exists and is up-to-date - fall through and use it + +@@ -270,7 +309,7 @@ def main(argv): # pylint: disable=W0613 argv_prepared = ARGS salt_argv = [ @@ -639,7 +725,7 @@ index e46220fc80..661a671b81 100644 salt_call_path, '--retcode-passthrough', '--local', -@@ -303,7 +349,10 @@ def main(argv): # pylint: disable=W0613 +@@ -303,7 +342,10 @@ def main(argv): # pylint: disable=W0613 if OPTIONS.tty: # Returns bytes instead of string on python 3 stdout, _ = subprocess.Popen(salt_argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() @@ -651,7 +737,7 @@ index e46220fc80..661a671b81 100644 sys.stdout.flush() if OPTIONS.wipe: shutil.rmtree(OPTIONS.saltdir) -@@ -315,5 +364,6 @@ def main(argv): # pylint: disable=W0613 +@@ -315,5 +357,6 @@ def main(argv): # pylint: disable=W0613 if OPTIONS.cmd_umask is not None: os.umask(old_umask) @@ -671,6 +757,18 @@ index 04d751b51a..09f9344642 100644 fsclient=self.fsclient, minion_opts=self.minion_opts, **self.kwargs +diff --git a/salt/config/__init__.py b/salt/config/__init__.py +index df0e1388b7..b3de3820b0 100644 +--- a/salt/config/__init__.py ++++ b/salt/config/__init__.py +@@ -1652,6 +1652,7 @@ DEFAULT_MASTER_OPTS = { + 'state_top': 'top.sls', + 'state_top_saltenv': None, + 'master_tops': {}, ++ 'master_tops_first': False, + 'order_masters': False, + 'job_cache': True, + 'ext_job_cache': '', diff --git a/salt/modules/zfs.py b/salt/modules/zfs.py index bc54044b5c..d8fbfc76be 100644 --- a/salt/modules/zfs.py @@ -705,8 +803,73 @@ index f955175664..5e03418919 100644 @salt.utils.decorators.memoize +diff --git a/salt/state.py b/salt/state.py +index 49d68d2edf..8c0b90545c 100644 +--- a/salt/state.py ++++ b/salt/state.py +@@ -3332,7 +3332,7 @@ class BaseHighState(object): + ext_matches = self._master_tops() + for saltenv in ext_matches: + top_file_matches = matches.get(saltenv, []) +- if self.opts['master_tops_first']: ++ if self.opts.get('master_tops_first'): + first = ext_matches[saltenv] + second = top_file_matches + else: +diff --git a/salt/utils/hashutils.py b/salt/utils/hashutils.py +index 4c9cb4a50c..18f7459d3c 100644 +--- a/salt/utils/hashutils.py ++++ b/salt/utils/hashutils.py +@@ -9,6 +9,7 @@ import base64 + import hashlib + import hmac + import random ++import os + + # Import Salt libs + from salt.ext import six +@@ -163,3 +164,39 @@ def get_hash(path, form='sha256', chunk_size=65536): + for chunk in iter(lambda: ifile.read(chunk_size), b''): + hash_obj.update(chunk) + return hash_obj.hexdigest() ++ ++ ++class DigestCollector(object): ++ ''' ++ Class to collect digest of the file tree. ++ ''' ++ ++ def __init__(self, form='sha256', buff=0x10000): ++ ''' ++ Constructor of the class. ++ :param form: ++ ''' ++ self.__digest = hasattr(hashlib, form) and getattr(hashlib, form)() or None ++ if self.__digest is None: ++ raise ValueError('Invalid hash type: {0}'.format(form)) ++ self.__buff = buff ++ ++ def add(self, path): ++ ''' ++ Update digest with the file content by path. ++ ++ :param path: ++ :return: ++ ''' ++ with salt.utils.files.fopen(path, 'rb') as ifile: ++ for chunk in iter(lambda: ifile.read(self.__buff), b''): ++ self.__digest.update(chunk) ++ ++ def digest(self): ++ ''' ++ Get digest. ++ ++ :return: ++ ''' ++ ++ return salt.utils.stringutils.to_str(self.__digest.hexdigest() + os.linesep) diff --git a/salt/utils/thin.py b/salt/utils/thin.py -index 4c0969ea96..a6990d00b1 100644 +index 4c0969ea96..e4b878eb19 100644 --- a/salt/utils/thin.py +++ b/salt/utils/thin.py @@ -8,11 +8,14 @@ from __future__ import absolute_import, print_function, unicode_literals @@ -849,7 +1012,7 @@ index 4c0969ea96..a6990d00b1 100644 def thin_path(cachedir): -@@ -101,29 +133,136 @@ def thin_path(cachedir): +@@ -101,29 +133,137 @@ def thin_path(cachedir): return os.path.join(cachedir, 'thin', 'thin.tgz') @@ -878,7 +1041,9 @@ index 4c0969ea96..a6990d00b1 100644 +def _add_dependency(container, obj): + ''' + Add a dependency to the top list. -+ + +- tops.append(_six.__file__.replace('.pyc', '.py')) +- tops.append(backports_abc.__file__.replace('.pyc', '.py')) + :param obj: + :param is_file: + :return: @@ -893,23 +1058,32 @@ index 4c0969ea96..a6990d00b1 100644 + ''' + This function is called externally from the alternative + Python interpreter from within _get_tops function. -+ + +- if HAS_CERTIFI: +- tops.append(os.path.dirname(certifi.__file__)) + :param extra_mods: + :param so_mods: + :return: + ''' + extra = salt.utils.json.loads(sys.argv[1]) + tops = get_tops(**extra) -+ + +- if HAS_SINGLEDISPATCH: +- tops.append(singledispatch.__file__.replace('.pyc', '.py')) + return salt.utils.json.dumps(tops, ensure_ascii=False) -+ -+ + +- if HAS_SINGLEDISPATCH_HELPERS: +- tops.append(singledispatch_helpers.__file__.replace('.pyc', '.py')) + +- if HAS_SSL_MATCH_HOSTNAME: +- tops.append(os.path.dirname(os.path.dirname(ssl_match_hostname.__file__))) +def get_ext_tops(config): + ''' + Get top directories for the dependencies, based on external configuration. + + :return: + ''' ++ config = copy.deepcopy(config) + alternatives = {} + required = ['jinja2', 'yaml', 'tornado', 'msgpack'] + tops = [] @@ -958,9 +1132,7 @@ index 4c0969ea96..a6990d00b1 100644 +def _get_ext_namespaces(config): + ''' + Get namespaces from the existing configuration. - -- tops.append(_six.__file__.replace('.pyc', '.py')) -- tops.append(backports_abc.__file__.replace('.pyc', '.py')) ++ + :param config: + :return: + ''' @@ -975,22 +1147,14 @@ index 4c0969ea96..a6990d00b1 100644 + "to what Python's major/minor version it should be constrained.") + else: + namespaces[ns] = constraint_version - -- if HAS_CERTIFI: -- tops.append(os.path.dirname(certifi.__file__)) ++ + return namespaces - -- if HAS_SINGLEDISPATCH: -- tops.append(singledispatch.__file__.replace('.pyc', '.py')) - -- if HAS_SINGLEDISPATCH_HELPERS: -- tops.append(singledispatch_helpers.__file__.replace('.pyc', '.py')) ++ ++ +def get_tops(extra_mods='', so_mods=''): + ''' + Get top directories for the dependencies, based on Python interpreter. - -- if HAS_SSL_MATCH_HOSTNAME: -- tops.append(os.path.dirname(os.path.dirname(ssl_match_hostname.__file__))) ++ + :param extra_mods: + :param so_mods: + :return: @@ -1004,7 +1168,7 @@ index 4c0969ea96..a6990d00b1 100644 for mod in [m for m in extra_mods.split(',') if m]: if mod not in locals() and mod not in globals(): -@@ -135,28 +274,49 @@ def get_tops(extra_mods='', so_mods=''): +@@ -135,28 +275,49 @@ def get_tops(extra_mods='', so_mods=''): tops.append(moddir) else: tops.append(os.path.join(moddir, base + '.py')) @@ -1066,7 +1230,7 @@ index 4c0969ea96..a6990d00b1 100644 ''' Generate the salt-thin tarball and print the location of the tarball Optional additional mods to include (e.g. mako) can be supplied as a comma -@@ -171,19 +331,24 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods='', +@@ -171,19 +332,26 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods='', salt-run thin.generate mako,wempy 1 salt-run thin.generate overwrite=1 ''' @@ -1089,6 +1253,8 @@ index 4c0969ea96..a6990d00b1 100644 pythinver = os.path.join(thindir, '.thin-gen-py-version') salt_call = os.path.join(thindir, 'salt-call') + pymap_cfg = os.path.join(thindir, 'supported-versions') ++ code_checksum = os.path.join(thindir, 'code-checksum') ++ digest_collector = salt.utils.hashutils.DigestCollector() + with salt.utils.files.fopen(salt_call, 'wb') as fp_: - fp_.write(SALTCALL) @@ -1097,7 +1263,7 @@ index 4c0969ea96..a6990d00b1 100644 if os.path.isfile(thintar): if not overwrite: if os.path.isfile(thinver): -@@ -197,85 +362,88 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods='', +@@ -197,85 +365,88 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods='', if overwrite: try: @@ -1225,7 +1391,7 @@ index 4c0969ea96..a6990d00b1 100644 for py_ver, tops in _six.iteritems(tops_py_version_mapping): for top in tops: if absonly and not os.path.isabs(top): -@@ -291,48 +459,76 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods='', +@@ -291,48 +462,80 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods='', egg.extractall(tempdir) top = os.path.join(tempdir, base) os.chdir(tempdir) @@ -1249,6 +1415,7 @@ index 4c0969ea96..a6990d00b1 100644 - tfp.add(os.path.join(root, name), - arcname=os.path.join('py{0}'.format(py_ver), root, name)) - elif compress == 'zip': ++ digest_collector.add(os.path.join(root, name)) + arcname = os.path.join(site_pkg_dir, root, name) + if hasattr(tfp, 'getinfo'): try: @@ -1285,6 +1452,7 @@ index 4c0969ea96..a6990d00b1 100644 + for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True): + for name in files: + if not name.endswith(('.pyc', '.pyo')): ++ digest_collector.add(os.path.join(root, name)) + arcname = os.path.join(ns, site_pkg_dir, root, name) + if hasattr(tfp, 'getinfo'): + try: @@ -1305,6 +1473,8 @@ index 4c0969ea96..a6990d00b1 100644 with salt.utils.files.fopen(pythinver, 'w+') as fp_: - fp_.write(str(sys.version_info[0])) # future lint: disable=blacklisted-function + fp_.write(str(sys.version_info.major)) # future lint: disable=blacklisted-function ++ with salt.utils.files.fopen(code_checksum, 'w+') as fp_: ++ fp_.write(digest_collector.digest()) os.chdir(os.path.dirname(thinver)) - if compress == 'gzip': - tfp.add('version') @@ -1313,7 +1483,7 @@ index 4c0969ea96..a6990d00b1 100644 - tfp.write('version') - tfp.write('.thin-gen-py-version') + -+ for fname in ['version', '.thin-gen-py-version', 'salt-call', 'supported-versions']: ++ for fname in ['version', '.thin-gen-py-version', 'salt-call', 'supported-versions', 'code-checksum']: + tfp.add(fname) + if start_dir: @@ -1323,7 +1493,23 @@ index 4c0969ea96..a6990d00b1 100644 return thintar -@@ -368,7 +564,7 @@ def gen_min(cachedir, extra_mods='', overwrite=False, so_mods='', +@@ -341,7 +544,14 @@ def thin_sum(cachedir, form='sha1'): + Return the checksum of the current thin tarball + ''' + thintar = gen_thin(cachedir) +- return salt.utils.hashutils.get_hash(thintar, form) ++ code_checksum_path = os.path.join(cachedir, 'thin', 'code-checksum') ++ if os.path.isfile(code_checksum_path): ++ with salt.utils.fopen(code_checksum_path, 'r') as fh: ++ code_checksum = "'{0}'".format(fh.read().strip()) ++ else: ++ code_checksum = "'0'" ++ ++ return code_checksum, salt.utils.hashutils.get_hash(thintar, form) + + + def gen_min(cachedir, extra_mods='', overwrite=False, so_mods='', +@@ -368,7 +578,7 @@ def gen_min(cachedir, extra_mods='', overwrite=False, so_mods='', pyminver = os.path.join(mindir, '.min-gen-py-version') salt_call = os.path.join(mindir, 'salt-call') with salt.utils.files.fopen(salt_call, 'wb') as fp_: @@ -1334,10 +1520,10 @@ index 4c0969ea96..a6990d00b1 100644 if os.path.isfile(minver): diff --git a/tests/unit/utils/test_thin.py b/tests/unit/utils/test_thin.py new file mode 100644 -index 0000000000..8157eefed8 +index 0000000000..549d48a703 --- /dev/null +++ b/tests/unit/utils/test_thin.py -@@ -0,0 +1,607 @@ +@@ -0,0 +1,612 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Bo Maryniuk ` @@ -1417,7 +1603,6 @@ index 0000000000..8157eefed8 + + return tf + -+ + @patch('salt.exceptions.SaltSystemExit', Exception) + @patch('salt.utils.thin.log', MagicMock()) + @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False)) @@ -1532,7 +1717,11 @@ index 0000000000..8157eefed8 + 'yaml': '/yaml/', + 'tornado': '/tornado/tornado.py', + 'msgpack': 'msgpack.py'}}} -+ assert cfg == thin.get_ext_tops(cfg) ++ out = thin.get_ext_tops(cfg) ++ assert out['namespace']['py-version'] == cfg['namespace']['py-version'] ++ assert out['namespace']['path'] == cfg['namespace']['path'] ++ assert sorted(out['namespace']['dependencies']) == sorted(['/tornado/tornado.py', ++ '/jinja/foo.py', '/yaml/', 'msgpack.py']) + + @patch('salt.utils.thin.sys.argv', [None, '{"foo": "bar"}']) + @patch('salt.utils.thin.get_tops', lambda **kw: kw) @@ -1698,7 +1887,7 @@ index 0000000000..8157eefed8 + + :return: + ''' -+ assert thin.thin_sum('/cachedir', form='sha256') == 12345 ++ assert thin.thin_sum('/cachedir', form='sha256')[1] == 12345 + thin.salt.utils.hashutils.get_hash.assert_called() + assert thin.salt.utils.hashutils.get_hash.call_count == 1 + @@ -1841,6 +2030,7 @@ index 0000000000..8157eefed8 + @patch('salt.utils.thin._six.PY3', True) + @patch('salt.utils.thin._six.PY2', False) + @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6)) ++ @patch('salt.utils.hashutils.DigestCollector', MagicMock()) + def test_gen_thin_main_content_files_written_py3(self): + ''' + Test thin.gen_thin function if main content files are written. @@ -1855,7 +2045,7 @@ index 0000000000..8157eefed8 + 'py3/root/r1', 'py3/root/r2', 'py3/root/r3', 'py3/root2/r4', 'py3/root2/r5', 'py3/root2/r6', + 'pyall/root/r1', 'pyall/root/r2', 'pyall/root/r3', 'pyall/root2/r4', 'pyall/root2/r5', 'pyall/root2/r6' + ] -+ for cl in thin.tarfile.open().method_calls[:-5]: ++ for cl in thin.tarfile.open().method_calls[:-6]: + arcname = cl[2].get('arcname') + assert arcname in files + files.pop(files.index(arcname)) @@ -1890,6 +2080,7 @@ index 0000000000..8157eefed8 + @patch('salt.utils.thin._six.PY3', True) + @patch('salt.utils.thin._six.PY2', False) + @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6)) ++ @patch('salt.utils.hashutils.DigestCollector', MagicMock()) + def test_gen_thin_ext_alternative_content_files_written_py3(self): + ''' + Test thin.gen_thin function if external alternative content files are written. @@ -1905,7 +2096,7 @@ index 0000000000..8157eefed8 + 'namespace/py2/root/r1', 'namespace/py2/root/r2', 'namespace/py2/root/r3', + 'namespace/py2/root2/r4', 'namespace/py2/root2/r5', 'namespace/py2/root2/r6' + ] -+ for idx, cl in enumerate(thin.tarfile.open().method_calls[12:-5]): ++ for idx, cl in enumerate(thin.tarfile.open().method_calls[12:-6]): + arcname = cl[2].get('arcname') + assert arcname in files + files.pop(files.index(arcname)) @@ -1946,6 +2137,6 @@ index 0000000000..8157eefed8 + for t_line in ['second-system-effect:2:7', 'solar-interference:2:6']: + assert t_line in out -- -2.16.2 +2.15.1 diff --git a/fall-back-to-pymysql.patch b/fall-back-to-pymysql.patch new file mode 100644 index 0000000..067ac2e --- /dev/null +++ b/fall-back-to-pymysql.patch @@ -0,0 +1,317 @@ +From f7ba683153e11be401a5971ba029d0a3964b1ecb Mon Sep 17 00:00:00 2001 +From: Maximilian Meister +Date: Thu, 5 Apr 2018 13:23:23 +0200 +Subject: [PATCH] fall back to PyMySQL + +same is already done in modules (see #26803) + +Signed-off-by: Maximilian Meister +--- + salt/auth/mysql.py | 25 ++++++++++++++++++++++--- + salt/cache/mysql_cache.py | 28 +++++++++++++++++++--------- + salt/modules/mysql.py | 22 ++++++++++------------ + salt/pillar/mysql.py | 21 ++++++++++++++++----- + salt/returners/mysql.py | 29 +++++++++++++++++++++-------- + tests/unit/pillar/test_mysql.py | 2 +- + 6 files changed, 89 insertions(+), 38 deletions(-) + +diff --git a/salt/auth/mysql.py b/salt/auth/mysql.py +index 8bc18a4101..86d00a4373 100644 +--- a/salt/auth/mysql.py ++++ b/salt/auth/mysql.py +@@ -55,10 +55,29 @@ import logging + log = logging.getLogger(__name__) + + try: ++ # Trying to import MySQLdb + import MySQLdb +- HAS_MYSQL = True ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ from MySQLdb.connections import OperationalError + except ImportError: +- HAS_MYSQL = False ++ try: ++ # MySQLdb import failed, try to import PyMySQL ++ import pymysql ++ pymysql.install_as_MySQLdb() ++ import MySQLdb ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ from MySQLdb.err import OperationalError ++ except ImportError: ++ MySQLdb = None ++ ++ ++def __virtual__(): ++ ''' ++ Confirm that a python mysql client is installed. ++ ''' ++ return bool(MySQLdb), 'No python mysql client installed.' if MySQLdb is None else '' + + + def __get_connection_info(): +@@ -95,7 +114,7 @@ def auth(username, password): + _info['username'], + _info['password'], + _info['database']) +- except MySQLdb.OperationalError as e: ++ except OperationalError as e: + log.error(e) + return False + +diff --git a/salt/cache/mysql_cache.py b/salt/cache/mysql_cache.py +index 9d6aa17987..8b0a942310 100644 +--- a/salt/cache/mysql_cache.py ++++ b/salt/cache/mysql_cache.py +@@ -46,11 +46,24 @@ value to ``mysql``: + from __future__ import absolute_import, print_function, unicode_literals + from time import sleep + import logging ++ + try: ++ # Trying to import MySQLdb + import MySQLdb +- HAS_MYSQL = True ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ from MySQLdb.connections import OperationalError + except ImportError: +- HAS_MYSQL = False ++ try: ++ # MySQLdb import failed, try to import PyMySQL ++ import pymysql ++ pymysql.install_as_MySQLdb() ++ import MySQLdb ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ from MySQLdb.err import OperationalError ++ except ImportError: ++ MySQLdb = None + + from salt.exceptions import SaltCacheError + +@@ -71,12 +84,9 @@ __func_alias__ = {'ls': 'list'} + + def __virtual__(): + ''' +- Confirm that python-mysql package is installed. ++ Confirm that a python mysql client is installed. + ''' +- if not HAS_MYSQL: +- return (False, "Please install python-mysql package to use mysql data " +- "cache driver") +- return __virtualname__ ++ return bool(MySQLdb), 'No python mysql client installed.' if MySQLdb is None else '' + + + def run_query(conn, query, retries=3): +@@ -84,13 +94,13 @@ def run_query(conn, query, retries=3): + Get a cursor and run a query. Reconnect up to `retries` times if + needed. + Returns: cursor, affected rows counter +- Raises: SaltCacheError, AttributeError, MySQLdb.OperationalError ++ Raises: SaltCacheError, AttributeError, OperationalError + ''' + try: + cur = conn.cursor() + out = cur.execute(query) + return cur, out +- except (AttributeError, MySQLdb.OperationalError) as e: ++ except (AttributeError, OperationalError) as e: + if retries == 0: + raise + # reconnect creating new client +diff --git a/salt/modules/mysql.py b/salt/modules/mysql.py +index 0625b02a96..8b17e461ea 100644 +--- a/salt/modules/mysql.py ++++ b/salt/modules/mysql.py +@@ -51,13 +51,14 @@ import salt.utils.stringutils + from salt.ext import six + # pylint: disable=import-error + from salt.ext.six.moves import range, zip # pylint: disable=no-name-in-module,redefined-builtin ++ + try: +- # Try to import MySQLdb ++ # Trying to import MySQLdb + import MySQLdb + import MySQLdb.cursors + import MySQLdb.converters + from MySQLdb.constants import FIELD_TYPE, FLAG +- HAS_MYSQLDB = True ++ from MySQLdb.connections import OperationalError + except ImportError: + try: + # MySQLdb import failed, try to import PyMySQL +@@ -67,10 +68,9 @@ except ImportError: + import MySQLdb.cursors + import MySQLdb.converters + from MySQLdb.constants import FIELD_TYPE, FLAG +- HAS_MYSQLDB = True ++ from MySQLdb.err import OperationalError + except ImportError: +- # No MySQL Connector installed, return False +- HAS_MYSQLDB = False ++ MySQLdb = None + + log = logging.getLogger(__name__) + +@@ -195,11 +195,9 @@ And theses could be mixed, in a like query value with args: 'f\_o\%%o`b\'a"r' + + def __virtual__(): + ''' +- Only load this module if the mysql libraries exist ++ Confirm that a python mysql client is installed. + ''' +- if HAS_MYSQLDB: +- return True +- return (False, 'The mysql execution module cannot be loaded: neither MySQLdb nor PyMySQL is available.') ++ return bool(MySQLdb), 'No python mysql client installed.' if MySQLdb is None else '' + + + def __check_table(name, table, **connection_args): +@@ -331,7 +329,7 @@ def _connect(**kwargs): + connargs.pop('passwd') + try: + dbc = MySQLdb.connect(**connargs) +- except MySQLdb.OperationalError as exc: ++ except OperationalError as exc: + err = 'MySQL Error {0}: {1}'.format(*exc) + __context__['mysql.error'] = err + log.error(err) +@@ -647,7 +645,7 @@ def query(database, query, **connection_args): + log.debug('Using db: %s to run query %s', database, query) + try: + affected = _execute(cur, query) +- except MySQLdb.OperationalError as exc: ++ except OperationalError as exc: + err = 'MySQL Error {0}: {1}'.format(*exc) + __context__['mysql.error'] = err + log.error(err) +@@ -772,7 +770,7 @@ def status(**connection_args): + qry = 'SHOW STATUS' + try: + _execute(cur, qry) +- except MySQLdb.OperationalError as exc: ++ except OperationalError as exc: + err = 'MySQL Error {0}: {1}'.format(*exc) + __context__['mysql.error'] = err + log.error(err) +diff --git a/salt/pillar/mysql.py b/salt/pillar/mysql.py +index 8029e5c197..d3f9619ad5 100644 +--- a/salt/pillar/mysql.py ++++ b/salt/pillar/mysql.py +@@ -59,16 +59,27 @@ log = logging.getLogger(__name__) + + # Import third party libs + try: ++ # Trying to import MySQLdb + import MySQLdb +- HAS_MYSQL = True ++ import MySQLdb.cursors ++ import MySQLdb.converters + except ImportError: +- HAS_MYSQL = False ++ try: ++ # MySQLdb import failed, try to import PyMySQL ++ import pymysql ++ pymysql.install_as_MySQLdb() ++ import MySQLdb ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ except ImportError: ++ MySQLdb = None + + + def __virtual__(): +- if not HAS_MYSQL: +- return False +- return True ++ ''' ++ Confirm that a python mysql client is installed. ++ ''' ++ return bool(MySQLdb), 'No python mysql client installed.' if MySQLdb is None else '' + + + class MySQLExtPillar(SqlBaseExtPillar): +diff --git a/salt/returners/mysql.py b/salt/returners/mysql.py +index af6698142b..85892cb06c 100644 +--- a/salt/returners/mysql.py ++++ b/salt/returners/mysql.py +@@ -155,11 +155,24 @@ import salt.exceptions + + # Import 3rd-party libs + from salt.ext import six ++ + try: ++ # Trying to import MySQLdb + import MySQLdb +- HAS_MYSQL = True ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ from MySQLdb.connections import OperationalError + except ImportError: +- HAS_MYSQL = False ++ try: ++ # MySQLdb import failed, try to import PyMySQL ++ import pymysql ++ pymysql.install_as_MySQLdb() ++ import MySQLdb ++ import MySQLdb.cursors ++ import MySQLdb.converters ++ from MySQLdb.err import OperationalError ++ except ImportError: ++ MySQLdb = None + + log = logging.getLogger(__name__) + +@@ -168,10 +181,10 @@ __virtualname__ = 'mysql' + + + def __virtual__(): +- if not HAS_MYSQL: +- return False, 'Could not import mysql returner; ' \ +- 'mysql python client is not installed.' +- return True ++ ''' ++ Confirm that a python mysql client is installed. ++ ''' ++ return bool(MySQLdb), 'No python mysql client installed.' if MySQLdb is None else '' + + + def _get_options(ret=None): +@@ -228,7 +241,7 @@ def _get_serv(ret=None, commit=False): + conn = __context__['mysql_returner_conn'] + conn.ping() + connect = False +- except MySQLdb.connections.OperationalError as exc: ++ except OperationalError as exc: + log.debug('OperationalError on ping: %s', exc) + + if connect: +@@ -254,7 +267,7 @@ def _get_serv(ret=None, commit=False): + __context__['mysql_returner_conn'] = conn + except TypeError: + pass +- except MySQLdb.connections.OperationalError as exc: ++ except OperationalError as exc: + raise salt.exceptions.SaltMasterError('MySQL returner could not connect to database: {exc}'.format(exc=exc)) + + cursor = conn.cursor() +diff --git a/tests/unit/pillar/test_mysql.py b/tests/unit/pillar/test_mysql.py +index 8d49ac24e2..b72988673d 100644 +--- a/tests/unit/pillar/test_mysql.py ++++ b/tests/unit/pillar/test_mysql.py +@@ -12,7 +12,7 @@ import salt.pillar.mysql as mysql + + + @skipIf(NO_MOCK, NO_MOCK_REASON) +-@skipIf(not mysql.HAS_MYSQL, 'MySQL-python module not installed') ++@skipIf(mysql.MySQLdb is None, 'MySQL-python module not installed') + class MysqlPillarTestCase(TestCase): + maxDiff = None + +-- +2.13.6 + + diff --git a/fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch b/fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch new file mode 100644 index 0000000..87b161e --- /dev/null +++ b/fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch @@ -0,0 +1,91 @@ +From b9cc71639d4e918ef14635124f6991917150de46 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Wed, 21 Mar 2018 11:10:23 +0100 +Subject: [PATCH] Fix for [Errno 0] Resolver Error 0 (no error) + (bsc#1087581) + + * Lintfix: PEP8 ident + * Use proper levels of the error handling, use proper log formatting. + * Fix unit test for reversed fqdns return data +--- + salt/grains/core.py | 19 ++++++++++++------- + tests/unit/grains/test_core.py | 32 ++++++++++++++++++++++++++++++++ + 2 files changed, 44 insertions(+), 7 deletions(-) + +diff --git a/salt/grains/core.py b/salt/grains/core.py +index 17a7d9819a..cd9ba1f29c 100644 +--- a/salt/grains/core.py ++++ b/salt/grains/core.py +@@ -1900,16 +1900,21 @@ def fqdns(): + fqdns = set() + + addresses = salt.utils.network.ip_addrs(include_loopback=False, +- interface_data=_INTERFACES) ++ interface_data=_INTERFACES) + addresses.extend(salt.utils.network.ip_addrs6(include_loopback=False, +- interface_data=_INTERFACES)) +- ++ interface_data=_INTERFACES)) ++ err_message = 'Exception during resolving address: %s' + for ip in addresses: + try: +- fqdns.add(socket.gethostbyaddr(ip)[0]) +- except (socket.error, socket.herror, +- socket.gaierror, socket.timeout) as e: +- log.info("Exception during resolving address: " + str(e)) ++ fqdns.add(socket.getfqdn(socket.gethostbyaddr(ip)[0])) ++ except socket.herror as err: ++ if err.errno == 0: ++ # No FQDN for this IP address, so we don't need to know this all the time. ++ log.debug("Unable to resolve address %s: %s", ip, err) ++ else: ++ log.error(err_message, err) ++ except (socket.error, socket.gaierror, socket.timeout) as err: ++ log.error(err_message, err) + + grains['fqdns'] = list(fqdns) + return grains +diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py +index 47c9cdd35b..c604df6c57 100644 +--- a/tests/unit/grains/test_core.py ++++ b/tests/unit/grains/test_core.py +@@ -784,3 +784,35 @@ SwapTotal: 4789244 kB''' + []}} + with patch.object(salt.utils.dns, 'parse_resolv', MagicMock(return_value=resolv_mock)): + assert core.dns() == ret ++ ++ def _run_dns_test(self, resolv_mock, ret): ++ with patch.object(salt.utils, 'is_windows', ++ MagicMock(return_value=False)): ++ with patch.dict(core.__opts__, {'ipv6': False}): ++ with patch.object(salt.utils.dns, 'parse_resolv', ++ MagicMock(return_value=resolv_mock)): ++ get_dns = core.dns() ++ self.assertEqual(get_dns, ret) ++ ++ @skipIf(not salt.utils.platform.is_linux(), 'System is not Linux') ++ @patch.object(salt.utils, 'is_windows', MagicMock(return_value=False)) ++ @patch('salt.utils.network.ip_addrs', MagicMock(return_value=['1.2.3.4', '5.6.7.8'])) ++ @patch('salt.utils.network.ip_addrs6', ++ MagicMock(return_value=['fe80::a8b2:93ff:fe00:0', 'fe80::a8b2:93ff:dead:beef'])) ++ @patch('salt.utils.network.socket.getfqdn', MagicMock(side_effect=lambda v: v)) # Just pass-through ++ def test_fqdns_return(self): ++ ''' ++ test the return for a dns grain. test for issue: ++ https://github.com/saltstack/salt/issues/41230 ++ ''' ++ reverse_resolv_mock = [('foo.bar.baz', [], ['1.2.3.4']), ++ ('rinzler.evil-corp.com', [], ['5.6.7.8']), ++ ('foo.bar.baz', [], ['fe80::a8b2:93ff:fe00:0']), ++ ('bluesniff.foo.bar', [], ['fe80::a8b2:93ff:dead:beef'])] ++ ret = {'fqdns': ['bluesniff.foo.bar', 'foo.bar.baz', 'rinzler.evil-corp.com']} ++ with patch.object(socket, 'gethostbyaddr', side_effect=reverse_resolv_mock): ++ fqdns = core.fqdns() ++ self.assertIn('fqdns', fqdns) ++ self.assertEqual(len(fqdns['fqdns']), len(ret['fqdns'])) ++ self.assertEqual(set(fqdns['fqdns']), set(ret['fqdns'])) ++ +-- +2.13.6 + + diff --git a/initialize-__context__-retcode-for-functions-handled.patch b/initialize-__context__-retcode-for-functions-handled.patch new file mode 100644 index 0000000..b326f74 --- /dev/null +++ b/initialize-__context__-retcode-for-functions-handled.patch @@ -0,0 +1,27 @@ +From c374feb62af75dfe18e8c81fb9cb556d678487ce Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 24 Apr 2018 13:50:49 +0100 +Subject: [PATCH] Initialize __context__ retcode for functions handled + via schedule util module + +--- + salt/utils/schedule.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/salt/utils/schedule.py b/salt/utils/schedule.py +index de057477a3..6cb3ce0ef8 100644 +--- a/salt/utils/schedule.py ++++ b/salt/utils/schedule.py +@@ -701,6 +701,7 @@ class Schedule(object): + for global_key, value in six.iteritems(func_globals): + self.functions[mod_name].__globals__[global_key] = value + ++ self.functions.pack['__context__']['retcode'] = 0 + ret['return'] = self.functions[func](*args, **kwargs) + + if not self.standalone: +-- +2.15.1 + + diff --git a/provide-kwargs-to-pkg_resource.parse_targets-require.patch b/provide-kwargs-to-pkg_resource.parse_targets-require.patch new file mode 100644 index 0000000..139352d --- /dev/null +++ b/provide-kwargs-to-pkg_resource.parse_targets-require.patch @@ -0,0 +1,37 @@ +From f7af1739a5795de6f98cfe2856372c755711e6dc Mon Sep 17 00:00:00 2001 +From: Michael Calmer +Date: Wed, 18 Apr 2018 17:19:18 +0200 +Subject: [PATCH] provide kwargs to pkg_resource.parse_targets required + to detect advisory type + +fix invalid string compare +--- + salt/modules/yumpkg.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py +index 39abb77fbc..9eb27e7701 100644 +--- a/salt/modules/yumpkg.py ++++ b/salt/modules/yumpkg.py +@@ -1322,7 +1322,7 @@ def install(name=None, + + try: + pkg_params, pkg_type = __salt__['pkg_resource.parse_targets']( +- name, pkgs, sources, saltenv=saltenv, normalize=normalize ++ name, pkgs, sources, saltenv=saltenv, normalize=normalize, **kwargs + ) + except MinionError as exc: + raise CommandExecutionError(exc) +@@ -1620,7 +1620,7 @@ def install(name=None, + if _yum() == 'dnf': + cmd.extend(['--best', '--allowerasing']) + _add_common_args(cmd) +- cmd.append('install' if pkg_type is not 'advisory' else 'update') ++ cmd.append('install' if pkg_type != 'advisory' else 'update') + cmd.extend(targets) + out = __salt__['cmd.run_all']( + cmd, +-- +2.15.1 + + diff --git a/salt.changes b/salt.changes index 8928018..088cd92 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,44 @@ +------------------------------------------------------------------- +Wed Apr 25 14:50:36 UTC 2018 - Pablo Suárez Hernández + +- Fix minion scheduler to return a 'retcode' attribute (bsc#1089112) +- Fix for logging during network interface querying (bsc#1087581) +- Fix rhel packages requires both net-tools and iproute (bsc#1087055) + +- Added: + * initialize-__context__-retcode-for-functions-handled.patch + +- Modified: + * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch + +------------------------------------------------------------------- +Wed Apr 18 17:09:41 UTC 2018 - Pablo Suárez Hernández + +- Fix patchinstall on yum module. Bad comparison (bsc#1087278) + +- Added: + * provide-kwargs-to-pkg_resource.parse_targets-require.patch + +------------------------------------------------------------------- +Wed Apr 18 16:55:28 UTC 2018 - Pablo Suárez Hernández + +- Strip trailing commas on Linux user's GECOS fields (bsc#1089362) +- Fallback to PyMySQL (bsc#1087891) +- Improved test for fqdns +- Update SaltSSH patch +- Fix for [Errno 0] Resolver Error 0 (no error) (bsc#1087581) + * Lintfix: PEP8 ident + * Use proper levels of the error handling, use proper log formatting. + * Fix unit test for reversed fqdns return data + +- Added: + * strip-trailing-commas-on-linux-user-gecos-fields.patch + * fall-back-to-pymysql.patch + * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch + +- Modified: + * add-saltssh-multi-version-support-across-python-inte.patch + ------------------------------------------------------------------- Fri Apr 6 16:58:59 UTC 2018 - Mihai Dinca diff --git a/salt.spec b/salt.spec index 2d67b34..48178f9 100644 --- a/salt.spec +++ b/salt.spec @@ -86,6 +86,17 @@ Patch12: make-it-possible-to-use-login-pull-and-push-from-mod.patch Patch13: explore-module.run-response-to-catch-the-result-in-d.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/46684 Patch14: add-saltssh-multi-version-support-across-python-inte.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/46635 +Patch15: fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/46890 +Patch16: fall-back-to-pymysql.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/47149 +Patch17: strip-trailing-commas-on-linux-user-gecos-fields.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/47155 +Patch18: provide-kwargs-to-pkg_resource.parse_targets-require.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/47270 +Patch19: initialize-__context__-retcode-for-functions-handled.patch + # BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -116,12 +127,12 @@ Requires: procps %if 0%{?suse_version} >= 1500 Requires: iproute2 %else +%if 0%{?suse_version} Requires: net-tools -%endif - -%if 0%{?rhel} +%else Requires: iproute %endif +%endif %if %{with systemd} BuildRequires: systemd @@ -558,6 +569,11 @@ cp %{S:5} ./.travis.yml %patch12 -p1 %patch13 -p1 %patch14 -p1 +%patch15 -p1 +%patch16 -p1 +%patch17 -p1 +%patch18 -p1 +%patch19 -p1 %build %if 0%{?build_py2} diff --git a/strip-trailing-commas-on-linux-user-gecos-fields.patch b/strip-trailing-commas-on-linux-user-gecos-fields.patch new file mode 100644 index 0000000..e82879b --- /dev/null +++ b/strip-trailing-commas-on-linux-user-gecos-fields.patch @@ -0,0 +1,55 @@ +From f9fb3639bb3c44babd92d9499bdde83a0a81d6ab Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 18 Apr 2018 12:05:35 +0100 +Subject: [PATCH] Strip trailing commas on Linux user GECOS fields + +Add unit tests for GECOS fields +--- + salt/modules/useradd.py | 2 +- + tests/unit/modules/test_useradd.py | 18 ++++++++++++++++++ + 2 files changed, 19 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/useradd.py b/salt/modules/useradd.py +index 545fe2a6f1..a61ba0e960 100644 +--- a/salt/modules/useradd.py ++++ b/salt/modules/useradd.py +@@ -81,7 +81,7 @@ def _build_gecos(gecos_dict): + return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname', ''), + gecos_dict.get('roomnumber', ''), + gecos_dict.get('workphone', ''), +- gecos_dict.get('homephone', '')) ++ gecos_dict.get('homephone', '')).rstrip(',') + + + def _update_gecos(name, key, value, root=None): +diff --git a/tests/unit/modules/test_useradd.py b/tests/unit/modules/test_useradd.py +index eb983685bb..fa30a0df71 100644 +--- a/tests/unit/modules/test_useradd.py ++++ b/tests/unit/modules/test_useradd.py +@@ -393,3 +393,21 @@ class UserAddTestCase(TestCase, LoaderModuleMockMixin): + mock = MagicMock(side_effect=[{'name': ''}, False, {'name': ''}]) + with patch.object(useradd, 'info', mock): + self.assertFalse(useradd.rename('salt', 'salt')) ++ ++ def test_build_gecos_field(self): ++ ''' ++ Test if gecos fields are built correctly (removing trailing commas) ++ ''' ++ test_gecos = {'fullname': 'Testing', ++ 'roomnumber': 1234, ++ 'workphone': 22222, ++ 'homephone': 99999} ++ expected_gecos_fields = 'Testing,1234,22222,99999' ++ self.assertEqual(useradd._build_gecos(test_gecos), expected_gecos_fields) ++ test_gecos.pop('roomnumber') ++ test_gecos.pop('workphone') ++ expected_gecos_fields = 'Testing,,,99999' ++ self.assertEqual(useradd._build_gecos(test_gecos), expected_gecos_fields) ++ test_gecos.pop('homephone') ++ expected_gecos_fields = 'Testing' ++ self.assertEqual(useradd._build_gecos(test_gecos), expected_gecos_fields) +-- +2.15.1 + +