SHA256
1
0
forked from pool/salt

Accepting request 602524 from systemsmanagement:saltstack

OBS-URL: https://build.opensuse.org/request/show/602524
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/salt?expand=0&rev=75
This commit is contained in:
Dominique Leuenberger 2018-04-30 20:58:17 +00:00 committed by Git OBS Bridge
commit 68d26c0254
10 changed files with 848 additions and 73 deletions

View File

@ -1 +1 @@
f43b8fb2425e3371decf3cde040c70ed15de375d 057b96d7f072dc69b61ca9e3b8d04a890003e58f

View File

@ -3,7 +3,7 @@
<param name="url">https://github.com/openSUSE/salt-packaging.git</param> <param name="url">https://github.com/openSUSE/salt-packaging.git</param>
<param name="subdir">salt</param> <param name="subdir">salt</param>
<param name="filename">package</param> <param name="filename">package</param>
<param name="revision">oxygen-rc1</param> <param name="revision">2018.3.0</param>
<param name="scm">git</param> <param name="scm">git</param>
</service> </service>
<service name="extract_file" mode="disabled"> <service name="extract_file" mode="disabled">

View File

@ -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 <bo@suse.de> From: Bo Maryniuk <bo@suse.de>
Date: Mon, 12 Mar 2018 12:01:39 +0100 Date: Mon, 12 Mar 2018 12:01:39 +0100
Subject: [PATCH] Add SaltSSH multi-version support across Python Subject: [PATCH] Add SaltSSH multi-version support across Python
@ -227,16 +227,45 @@ Disable wiping if state is executed
Properly mock a tempfile object Properly mock a tempfile object
Support Python 2.6 versions 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 +++++++++++ doc/topics/releases/fluorine.rst | 178 +++++++++++
salt/client/ssh/__init__.py | 20 +- salt/client/ssh/__init__.py | 66 ++--
salt/client/ssh/ssh_py_shim.py | 88 ++++-- salt/client/ssh/ssh_py_shim.py | 95 ++++--
salt/client/ssh/wrapper/__init__.py | 2 +- salt/client/ssh/wrapper/__init__.py | 2 +-
salt/config/__init__.py | 1 +
salt/modules/zfs.py | 4 +- salt/modules/zfs.py | 4 +-
salt/modules/zpool.py | 4 +- salt/modules/zpool.py | 4 +-
salt/utils/thin.py | 434 +++++++++++++++++++------- salt/state.py | 2 +-
tests/unit/utils/test_thin.py | 607 ++++++++++++++++++++++++++++++++++++ salt/utils/hashutils.py | 37 +++
8 files changed, 1182 insertions(+), 155 deletions(-) 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 doc/topics/releases/fluorine.rst
create mode 100644 tests/unit/utils/test_thin.py 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 +Salt version is also available on the Master machine, although does not need to be directly
+installed together with the older Python interpreter. +installed together with the older Python interpreter.
diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py 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 --- a/salt/client/ssh/__init__.py
+++ b/salt/client/ssh/__init__.py +++ b/salt/client/ssh/__init__.py
@@ -150,14 +150,10 @@ EX_PYTHON_INVALID={EX_THIN_PYTHON_INVALID} @@ -150,14 +150,10 @@ EX_PYTHON_INVALID={EX_THIN_PYTHON_INVALID}
@ -470,16 +499,71 @@ index f1c1ad9a22..ea5c700830 100644
if kwargs.get('thin_dir'): if kwargs.get('thin_dir'):
self.thin_dir = kwargs['thin_dir'] self.thin_dir = kwargs['thin_dir']
elif self.winrm: 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')]: if salt.log.LOG_LEVELS['debug'] >= salt.log.LOG_LEVELS[self.opts.get('log_level', 'info')]:
debug = '1' debug = '1'
arg_str = ''' arg_str = '''
-OPTIONS = OBJ() -OPTIONS = OBJ()
OPTIONS.config = \ 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 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 --- a/salt/client/ssh/ssh_py_shim.py
+++ b/salt/client/ssh/ssh_py_shim.py +++ b/salt/client/ssh/ssh_py_shim.py
@@ -16,11 +16,13 @@ import sys @@ -16,11 +16,13 @@ import sys
@ -587,7 +671,7 @@ index e46220fc80..661a671b81 100644
def main(argv): # pylint: disable=W0613 def main(argv): # pylint: disable=W0613
''' '''
Main program body 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: if scpstat != 0:
sys.exit(EX_SCP_NOT_FOUND) 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')) - 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(version_path) or not os.path.isfile(version_path):
+ if not os.path.exists(OPTIONS.saltdir): - sys.stderr.write(
+ need_deployment() - 'WARNING: Unable to locate current thin '
+
+ 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 '
- ' version: {0}.\n'.format(version_path) - ' version: {0}.\n'.format(version_path)
+ ' checksum: {0}.\n'.format(checksum_path) - )
) + if not os.path.exists(OPTIONS.saltdir):
need_deployment() need_deployment()
- with open(version_path, 'r') as vpo: - with open(version_path, 'r') as vpo:
- cur_version = vpo.readline().strip() - cur_version = vpo.readline().strip()
- if cur_version != OPTIONS.version: - if cur_version != OPTIONS.version:
+ with open(checksum_path, 'r') as vpo: - sys.stderr.write(
+ cur_checksum = vpo.readline().strip()
+ if cur_checksum != OPTIONS.checksum:
sys.stderr.write(
- 'WARNING: current thin version {0}' - '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_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() 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 argv_prepared = ARGS
salt_argv = [ salt_argv = [
@ -639,7 +725,7 @@ index e46220fc80..661a671b81 100644
salt_call_path, salt_call_path,
'--retcode-passthrough', '--retcode-passthrough',
'--local', '--local',
@@ -303,7 +349,10 @@ def main(argv): # pylint: disable=W0613 @@ -303,7 +342,10 @@ def main(argv): # pylint: disable=W0613
if OPTIONS.tty: if OPTIONS.tty:
# Returns bytes instead of string on python 3 # Returns bytes instead of string on python 3
stdout, _ = subprocess.Popen(salt_argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() stdout, _ = subprocess.Popen(salt_argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
@ -651,7 +737,7 @@ index e46220fc80..661a671b81 100644
sys.stdout.flush() sys.stdout.flush()
if OPTIONS.wipe: if OPTIONS.wipe:
shutil.rmtree(OPTIONS.saltdir) 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: if OPTIONS.cmd_umask is not None:
os.umask(old_umask) os.umask(old_umask)
@ -671,6 +757,18 @@ index 04d751b51a..09f9344642 100644
fsclient=self.fsclient, fsclient=self.fsclient,
minion_opts=self.minion_opts, minion_opts=self.minion_opts,
**self.kwargs **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 diff --git a/salt/modules/zfs.py b/salt/modules/zfs.py
index bc54044b5c..d8fbfc76be 100644 index bc54044b5c..d8fbfc76be 100644
--- a/salt/modules/zfs.py --- a/salt/modules/zfs.py
@ -705,8 +803,73 @@ index f955175664..5e03418919 100644
@salt.utils.decorators.memoize @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 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 --- a/salt/utils/thin.py
+++ b/salt/utils/thin.py +++ b/salt/utils/thin.py
@@ -8,11 +8,14 @@ from __future__ import absolute_import, print_function, unicode_literals @@ -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): 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') return os.path.join(cachedir, 'thin', 'thin.tgz')
@ -878,7 +1041,9 @@ index 4c0969ea96..a6990d00b1 100644
+def _add_dependency(container, obj): +def _add_dependency(container, obj):
+ ''' + '''
+ Add a dependency to the top list. + 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 obj:
+ :param is_file: + :param is_file:
+ :return: + :return:
@ -893,23 +1058,32 @@ index 4c0969ea96..a6990d00b1 100644
+ ''' + '''
+ This function is called externally from the alternative + This function is called externally from the alternative
+ Python interpreter from within _get_tops function. + Python interpreter from within _get_tops function.
+
- if HAS_CERTIFI:
- tops.append(os.path.dirname(certifi.__file__))
+ :param extra_mods: + :param extra_mods:
+ :param so_mods: + :param so_mods:
+ :return: + :return:
+ ''' + '''
+ extra = salt.utils.json.loads(sys.argv[1]) + extra = salt.utils.json.loads(sys.argv[1])
+ tops = get_tops(**extra) + tops = get_tops(**extra)
+
- if HAS_SINGLEDISPATCH:
- tops.append(singledispatch.__file__.replace('.pyc', '.py'))
+ return salt.utils.json.dumps(tops, ensure_ascii=False) + 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): +def get_ext_tops(config):
+ ''' + '''
+ Get top directories for the dependencies, based on external configuration. + Get top directories for the dependencies, based on external configuration.
+ +
+ :return: + :return:
+ ''' + '''
+ config = copy.deepcopy(config)
+ alternatives = {} + alternatives = {}
+ required = ['jinja2', 'yaml', 'tornado', 'msgpack'] + required = ['jinja2', 'yaml', 'tornado', 'msgpack']
+ tops = [] + tops = []
@ -958,9 +1132,7 @@ index 4c0969ea96..a6990d00b1 100644
+def _get_ext_namespaces(config): +def _get_ext_namespaces(config):
+ ''' + '''
+ Get namespaces from the existing configuration. + Get namespaces from the existing configuration.
+
- tops.append(_six.__file__.replace('.pyc', '.py'))
- tops.append(backports_abc.__file__.replace('.pyc', '.py'))
+ :param config: + :param config:
+ :return: + :return:
+ ''' + '''
@ -975,22 +1147,14 @@ index 4c0969ea96..a6990d00b1 100644
+ "to what Python's major/minor version it should be constrained.") + "to what Python's major/minor version it should be constrained.")
+ else: + else:
+ namespaces[ns] = constraint_version + namespaces[ns] = constraint_version
+
- if HAS_CERTIFI:
- tops.append(os.path.dirname(certifi.__file__))
+ return namespaces + 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=''): +def get_tops(extra_mods='', so_mods=''):
+ ''' + '''
+ Get top directories for the dependencies, based on Python interpreter. + 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 extra_mods:
+ :param so_mods: + :param so_mods:
+ :return: + :return:
@ -1004,7 +1168,7 @@ index 4c0969ea96..a6990d00b1 100644
for mod in [m for m in extra_mods.split(',') if m]: for mod in [m for m in extra_mods.split(',') if m]:
if mod not in locals() and mod not in globals(): 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) tops.append(moddir)
else: else:
tops.append(os.path.join(moddir, base + '.py')) 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 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 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 mako,wempy 1
salt-run thin.generate overwrite=1 salt-run thin.generate overwrite=1
''' '''
@ -1089,6 +1253,8 @@ index 4c0969ea96..a6990d00b1 100644
pythinver = os.path.join(thindir, '.thin-gen-py-version') pythinver = os.path.join(thindir, '.thin-gen-py-version')
salt_call = os.path.join(thindir, 'salt-call') salt_call = os.path.join(thindir, 'salt-call')
+ pymap_cfg = os.path.join(thindir, 'supported-versions') + 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_: with salt.utils.files.fopen(salt_call, 'wb') as fp_:
- fp_.write(SALTCALL) - fp_.write(SALTCALL)
@ -1097,7 +1263,7 @@ index 4c0969ea96..a6990d00b1 100644
if os.path.isfile(thintar): if os.path.isfile(thintar):
if not overwrite: if not overwrite:
if os.path.isfile(thinver): 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: if overwrite:
try: try:
@ -1225,7 +1391,7 @@ index 4c0969ea96..a6990d00b1 100644
for py_ver, tops in _six.iteritems(tops_py_version_mapping): for py_ver, tops in _six.iteritems(tops_py_version_mapping):
for top in tops: for top in tops:
if absonly and not os.path.isabs(top): 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) egg.extractall(tempdir)
top = os.path.join(tempdir, base) top = os.path.join(tempdir, base)
os.chdir(tempdir) os.chdir(tempdir)
@ -1249,6 +1415,7 @@ index 4c0969ea96..a6990d00b1 100644
- tfp.add(os.path.join(root, name), - tfp.add(os.path.join(root, name),
- arcname=os.path.join('py{0}'.format(py_ver), root, name)) - arcname=os.path.join('py{0}'.format(py_ver), root, name))
- elif compress == 'zip': - elif compress == 'zip':
+ digest_collector.add(os.path.join(root, name))
+ arcname = os.path.join(site_pkg_dir, root, name) + arcname = os.path.join(site_pkg_dir, root, name)
+ if hasattr(tfp, 'getinfo'): + if hasattr(tfp, 'getinfo'):
try: try:
@ -1285,6 +1452,7 @@ index 4c0969ea96..a6990d00b1 100644
+ for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True): + for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True):
+ for name in files: + for name in files:
+ if not name.endswith(('.pyc', '.pyo')): + if not name.endswith(('.pyc', '.pyo')):
+ digest_collector.add(os.path.join(root, name))
+ arcname = os.path.join(ns, site_pkg_dir, root, name) + arcname = os.path.join(ns, site_pkg_dir, root, name)
+ if hasattr(tfp, 'getinfo'): + if hasattr(tfp, 'getinfo'):
+ try: + try:
@ -1305,6 +1473,8 @@ index 4c0969ea96..a6990d00b1 100644
with salt.utils.files.fopen(pythinver, 'w+') as fp_: 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[0])) # future lint: disable=blacklisted-function
+ fp_.write(str(sys.version_info.major)) # 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)) os.chdir(os.path.dirname(thinver))
- if compress == 'gzip': - if compress == 'gzip':
- tfp.add('version') - tfp.add('version')
@ -1313,7 +1483,7 @@ index 4c0969ea96..a6990d00b1 100644
- tfp.write('version') - tfp.write('version')
- tfp.write('.thin-gen-py-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) + tfp.add(fname)
+ +
if start_dir: if start_dir:
@ -1323,7 +1493,23 @@ index 4c0969ea96..a6990d00b1 100644
return thintar 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') pyminver = os.path.join(mindir, '.min-gen-py-version')
salt_call = os.path.join(mindir, 'salt-call') salt_call = os.path.join(mindir, 'salt-call')
with salt.utils.files.fopen(salt_call, 'wb') as fp_: with salt.utils.files.fopen(salt_call, 'wb') as fp_:
@ -1334,10 +1520,10 @@ index 4c0969ea96..a6990d00b1 100644
if os.path.isfile(minver): if os.path.isfile(minver):
diff --git a/tests/unit/utils/test_thin.py b/tests/unit/utils/test_thin.py diff --git a/tests/unit/utils/test_thin.py b/tests/unit/utils/test_thin.py
new file mode 100644 new file mode 100644
index 0000000000..8157eefed8 index 0000000000..549d48a703
--- /dev/null --- /dev/null
+++ b/tests/unit/utils/test_thin.py +++ b/tests/unit/utils/test_thin.py
@@ -0,0 +1,607 @@ @@ -0,0 +1,612 @@
+# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*-
+''' +'''
+ :codeauthor: :email:`Bo Maryniuk <bo@suse.de>` + :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
@ -1417,7 +1603,6 @@ index 0000000000..8157eefed8
+ +
+ return tf + return tf
+ +
+
+ @patch('salt.exceptions.SaltSystemExit', Exception) + @patch('salt.exceptions.SaltSystemExit', Exception)
+ @patch('salt.utils.thin.log', MagicMock()) + @patch('salt.utils.thin.log', MagicMock())
+ @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False)) + @patch('salt.utils.thin.os.path.isfile', MagicMock(return_value=False))
@ -1532,7 +1717,11 @@ index 0000000000..8157eefed8
+ 'yaml': '/yaml/', + 'yaml': '/yaml/',
+ 'tornado': '/tornado/tornado.py', + 'tornado': '/tornado/tornado.py',
+ 'msgpack': 'msgpack.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.sys.argv', [None, '{"foo": "bar"}'])
+ @patch('salt.utils.thin.get_tops', lambda **kw: kw) + @patch('salt.utils.thin.get_tops', lambda **kw: kw)
@ -1698,7 +1887,7 @@ index 0000000000..8157eefed8
+ +
+ :return: + :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() + thin.salt.utils.hashutils.get_hash.assert_called()
+ assert thin.salt.utils.hashutils.get_hash.call_count == 1 + 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.PY3', True)
+ @patch('salt.utils.thin._six.PY2', False) + @patch('salt.utils.thin._six.PY2', False)
+ @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6)) + @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): + def test_gen_thin_main_content_files_written_py3(self):
+ ''' + '''
+ Test thin.gen_thin function if main content files are written. + 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', + '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' + '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') + arcname = cl[2].get('arcname')
+ assert arcname in files + assert arcname in files
+ files.pop(files.index(arcname)) + files.pop(files.index(arcname))
@ -1890,6 +2080,7 @@ index 0000000000..8157eefed8
+ @patch('salt.utils.thin._six.PY3', True) + @patch('salt.utils.thin._six.PY3', True)
+ @patch('salt.utils.thin._six.PY2', False) + @patch('salt.utils.thin._six.PY2', False)
+ @patch('salt.utils.thin.sys.version_info', _version_info(None, 3, 6)) + @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): + def test_gen_thin_ext_alternative_content_files_written_py3(self):
+ ''' + '''
+ Test thin.gen_thin function if external alternative content files are written. + 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/root/r1', 'namespace/py2/root/r2', 'namespace/py2/root/r3',
+ 'namespace/py2/root2/r4', 'namespace/py2/root2/r5', 'namespace/py2/root2/r6' + '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') + arcname = cl[2].get('arcname')
+ assert arcname in files + assert arcname in files
+ files.pop(files.index(arcname)) + 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']: + for t_line in ['second-system-effect:2:7', 'solar-interference:2:6']:
+ assert t_line in out + assert t_line in out
-- --
2.16.2 2.15.1

317
fall-back-to-pymysql.patch Normal file
View File

@ -0,0 +1,317 @@
From f7ba683153e11be401a5971ba029d0a3964b1ecb Mon Sep 17 00:00:00 2001
From: Maximilian Meister <mmeister@suse.de>
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 <mmeister@suse.de>
---
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

View File

@ -0,0 +1,91 @@
From b9cc71639d4e918ef14635124f6991917150de46 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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

View File

@ -0,0 +1,27 @@
From c374feb62af75dfe18e8c81fb9cb556d678487ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
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

View File

@ -0,0 +1,37 @@
From f7af1739a5795de6f98cfe2856372c755711e6dc Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
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

View File

@ -1,3 +1,44 @@
-------------------------------------------------------------------
Wed Apr 25 14:50:36 UTC 2018 - Pablo Suárez Hernández <psuarezhernandez@suse.com>
- 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 <psuarezhernandez@suse.com>
- 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 <psuarezhernandez@suse.com>
- 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 <mdinca@suse.de> Fri Apr 6 16:58:59 UTC 2018 - Mihai Dinca <mdinca@suse.de>

View File

@ -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 Patch13: explore-module.run-response-to-catch-the-result-in-d.patch
# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/46684 # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/46684
Patch14: add-saltssh-multi-version-support-across-python-inte.patch 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
BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRoot: %{_tmppath}/%{name}-%{version}-build
@ -116,12 +127,12 @@ Requires: procps
%if 0%{?suse_version} >= 1500 %if 0%{?suse_version} >= 1500
Requires: iproute2 Requires: iproute2
%else %else
%if 0%{?suse_version}
Requires: net-tools Requires: net-tools
%endif %else
%if 0%{?rhel}
Requires: iproute Requires: iproute
%endif %endif
%endif
%if %{with systemd} %if %{with systemd}
BuildRequires: systemd BuildRequires: systemd
@ -558,6 +569,11 @@ cp %{S:5} ./.travis.yml
%patch12 -p1 %patch12 -p1
%patch13 -p1 %patch13 -p1
%patch14 -p1 %patch14 -p1
%patch15 -p1
%patch16 -p1
%patch17 -p1
%patch18 -p1
%patch19 -p1
%build %build
%if 0%{?build_py2} %if 0%{?build_py2}

View File

@ -0,0 +1,55 @@
From f9fb3639bb3c44babd92d9499bdde83a0a81d6ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
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