Hello community,

here is the log from the commit of package salt for openSUSE:Leap:15.2 checked 
in at 2020-04-08 12:48:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/salt (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.salt.new.3248 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "salt"

Wed Apr  8 12:48:17 2020 rev:69 rq:791027 version:2019.2.3

Changes:
--------
--- /work/SRC/openSUSE:Leap:15.2/salt/salt.changes      2020-03-23 
09:33:43.408304977 +0100
+++ /work/SRC/openSUSE:Leap:15.2/.salt.new.3248/salt.changes    2020-04-08 
12:48:52.994357864 +0200
@@ -1,0 +2,31 @@
+Thu Apr  2 13:47:34 UTC 2020 - Pablo Suárez Hernández 
<pablo.suarezhernan...@suse.com>
+
+- Enable building and installation for Fedora
+- Disable python2 build on Tumbleweed
+  We are removing the python2 interpreter from openSUSE (SLE16).
+  As such disable salt building for python2 there.
+
+-------------------------------------------------------------------
+Thu Apr  2 10:54:53 UTC 2020 - Pablo Suárez Hernández 
<pablo.suarezhernan...@suse.com>
+
+- Sanitize grains loaded from roster_grains.json cache during "state.pkg"
+
+- Added:
+  * fix-load-cached-grain-osrelease_info.patch
+
+-------------------------------------------------------------------
+Fri Mar 27 15:37:10 UTC 2020 - Jochen Breuer <jbre...@suse.de>
+
+- Build: Buildequire pkgconfig(systemd) instead of systemd
+
+-------------------------------------------------------------------
+Thu Mar 26 13:18:50 UTC 2020 - Pablo Suárez Hernández 
<pablo.suarezhernan...@suse.com>
+
+- Backport saltutil state module to 2019.2 codebase (bsc#1167556)
+- Add new custom SUSE capability for saltutil state module
+
+- Added:
+  * backport-saltutil-state-module-to-2019.2-codebase.patch
+  * add-new-custom-suse-capability-for-saltutil-state-mo.patch
+
+-------------------------------------------------------------------

New:
----
  add-new-custom-suse-capability-for-saltutil-state-mo.patch
  backport-saltutil-state-module-to-2019.2-codebase.patch
  fix-load-cached-grain-osrelease_info.patch

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ salt.spec ++++++
--- /var/tmp/diff_new_pack.EPCQX3/_old  2020-04-08 12:48:57.818360378 +0200
+++ /var/tmp/diff_new_pack.EPCQX3/_new  2020-04-08 12:48:57.822360381 +0200
@@ -15,8 +15,12 @@
 # Please submit bugfixes or comments via http://bugs.opensuse.org/
 #
 %global debug_package %{nil}
-
-%if 0%{?suse_version} >= 1320
+%if 0%{?suse_version} > 1500
+%global build_py3   1
+%global build_py2   0
+%global default_py3 1
+%else
+%if 0%{?suse_version} >= 1500
 # SLE15
 %global build_py3   1
 %global build_py2   1
@@ -36,9 +40,10 @@
 %endif
 %endif
 %endif
+%endif
 %define pythonX %{?default_py3: python3}%{!?default_py3: python2}
 
-%if 0%{?suse_version} > 1210 || 0%{?rhel} >= 7 || 0%{?fedora}
+%if 0%{?suse_version} > 1210 || 0%{?rhel} >= 7 || 0%{?fedora} >=28
 %bcond_without systemd
 %else
 %bcond_with    systemd
@@ -310,6 +315,13 @@
 Patch114:      open-suse-2019.2.3-virt-defined-states-219.patch
 # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/56392
 Patch115:      virt._get_domain-don-t-raise-an-exception-if-there-i.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/50197
+Patch116:      backport-saltutil-state-module-to-2019.2-codebase.patch
+# PATCH_FIX_OPENSUSE: 
https://github.com/openSUSE/salt/commit/b713d0b3031faadc17cd9cf09977ccc19e50bef7
+Patch117:      add-new-custom-suse-capability-for-saltutil-state-mo.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/55796
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/56491
+Patch118:      fix-load-cached-grain-osrelease_info.patch
 
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
@@ -348,8 +360,8 @@
 %endif
 
 %if %{with systemd}
-BuildRequires:  systemd
-%{?systemd_requires}
+BuildRequires:  pkgconfig(systemd)
+%{?systemd_ordering}
 %else
 %if 0%{?suse_version}
 Requires(pre): %insserv_prereq
@@ -373,7 +385,7 @@
 BuildRequires:  zsh
 %endif
 
-%if 0%{?rhel}
+%if 0%{?rhel} || 0%{?fedora}
 BuildRequires:  yum
 %endif
 
@@ -393,7 +405,7 @@
 BuildRequires:  python >= 2.7
 BuildRequires:  python-devel >= 2.7
 # requirements/base.txt
-%if 0%{?rhel}
+%if 0%{?rhel} || 0%{?fedora}
 BuildRequires:  python-jinja2
 BuildRequires:  python-yaml
 BuildRequires:  python-markupsafe
@@ -407,7 +419,7 @@
 BuildRequires:  python-msgpack-python > 0.3
 BuildRequires:  python-psutil
 BuildRequires:  python-requests >= 1.0.0
-%if 0%{?suse_version} >= 1500 || 0%{?rhel} >= 8
+%if 0%{?suse_version} >= 1500 || 0%{?rhel} >= 8 || 0%{?fedora} >= 30
 # We can't cope with tornado 5.x and newer (boo#1101780); this is only 
relevant for SLE >= 15 and TW
 # where tornado exists in multiple versions
 BuildRequires: (python-tornado >= 4.2.1 with python-tornado < 5)
@@ -443,7 +455,7 @@
 Requires:       python-certifi
 %endif
 # requirements/base.txt
-%if 0%{?rhel}
+%if 0%{?rhel} || 0%{?fedora}
 Requires:       python-jinja2
 Requires:       python-yaml
 Requires:       python-markupsafe
@@ -507,7 +519,7 @@
 %endif
 BuildRequires:  python3-devel
 # requirements/base.txt
-%if 0%{?rhel}
+%if 0%{?rhel} || 0%{?fedora}
 BuildRequires:  python3-jinja2
 BuildRequires:  python3-markupsafe
 BuildRequires:  python3-msgpack > 0.3
@@ -528,7 +540,7 @@
 BuildRequires:  python3-PyYAML
 BuildRequires:  python3-psutil
 BuildRequires:  python3-requests >= 1.0.0
-%if 0%{?suse_version} >= 1500 || 0%{?rhel} >= 8
+%if 0%{?suse_version} >= 1500 || 0%{?rhel} >= 8 || 0%{?fedora} >= 30
 # We can't cope with tornado 5.x and newer (boo#1101780); this is only 
relevant for SLE >= 15 and TW,
 # where tornado exists in multiple versions
 BuildRequires: (python3-tornado >= 4.2.1 with python3-tornado < 5)
@@ -562,14 +574,15 @@
 Requires:       python3-certifi
 %endif
 # requirements/base.txt
-%if 0%{?rhel}
+%if 0%{?rhel} || 0%{?fedora}
 Requires:       python3-jinja2
 Requires:       yum
 Requires:       python3-markupsafe
 Requires:       python3-msgpack > 0.3
 Requires:       python3-m2crypto
 Requires:       python3-zmq >= 2.2.0
-%if 0%{?rhel} == 8
+
+%if 0%{?rhel} == 8 || 0%{?fedora} >= 30
 Requires:       dnf
 %endif
 %if 0%{?rhel} == 6
@@ -952,6 +965,9 @@
 %patch113 -p1
 %patch114 -p1
 %patch115 -p1
+%patch116 -p1
+%patch117 -p1
+%patch118 -p1
 
 %build
 %if 0%{?build_py2}

++++++ _lastrevision ++++++
--- /var/tmp/diff_new_pack.EPCQX3/_old  2020-04-08 12:48:57.898360420 +0200
+++ /var/tmp/diff_new_pack.EPCQX3/_new  2020-04-08 12:48:57.898360420 +0200
@@ -1 +1 @@
-6c4669ed512eb32e435e4bfd2cebdcba315841bb
\ No newline at end of file
+eb41abdc4d70c344f2d84dc6d693b9fb9cb4c247
\ No newline at end of file

++++++ add-new-custom-suse-capability-for-saltutil-state-mo.patch ++++++
>From 4a8c36db7ece51efdd04caa37c79b0cb6a22ecf6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
 <psuarezhernan...@suse.com>
Date: Thu, 26 Mar 2020 13:08:16 +0000
Subject: [PATCH] Add new custom SUSE capability for saltutil state
 module

---
 salt/grains/extra.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/salt/grains/extra.py b/salt/grains/extra.py
index 
4fb58674bf04d44ada1cb1620384aba7d3a76bad..e78cc64e0dedc728cbef68942b8767c526daa1a8
 100644
--- a/salt/grains/extra.py
+++ b/salt/grains/extra.py
@@ -80,5 +80,6 @@ def config():
 def suse_backported_capabilities():
     return {
         '__suse_reserved_pkg_all_versions_support': True,
-        '__suse_reserved_pkg_patches_support': True
+        '__suse_reserved_pkg_patches_support': True,
+        '__suse_reserved_saltutil_states_support': True
     }
-- 
2.23.0


++++++ backport-saltutil-state-module-to-2019.2-codebase.patch ++++++
>From d99badeda9818ba54a3af1d0145c5278fe0edee1 Mon Sep 17 00:00:00 2001
From: Christian McHugh <mchu...@dnb.com>
Date: Wed, 24 Oct 2018 15:49:58 +0100
Subject: [PATCH] Backport "saltutil" state module to 2019.2 codebase

add individual syncers and release note

add individual syncers

adjust state comment output and cleanup tests
---
 salt/states/saltutil.py            | 311 +++++++++++++++++++++++++++++
 tests/unit/states/test_saltutil.py | 121 +++++++++++
 2 files changed, 432 insertions(+)
 create mode 100644 salt/states/saltutil.py
 create mode 100644 tests/unit/states/test_saltutil.py

diff --git a/salt/states/saltutil.py b/salt/states/saltutil.py
new file mode 100644
index 
0000000000000000000000000000000000000000..99952a1b7d1b8a70bac940ec083eb75776c146c6
--- /dev/null
+++ b/salt/states/saltutil.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+'''
+Saltutil State
+==============
+
+This state wraps the saltutil execution modules to make them easier to run
+from a states. Rather than needing to to use ``module.run`` this state allows 
for
+improved change detection.
+
+    .. versionadded: Neon
+'''
+from __future__ import absolute_import, unicode_literals, print_function
+
+import logging
+
+# Define the module's virtual name
+__virtualname__ = 'saltutil'
+
+log = logging.getLogger(__name__)
+
+
+def __virtual__():
+    '''
+    Named saltutil
+    '''
+    return __virtualname__
+
+
+def _sync_single(name, module, **kwargs):
+    ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''}
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = "saltutil.sync_{0} would have been run".format(module)
+        return ret
+
+    try:
+        sync_status = __salt__['saltutil.sync_{0}'.format(module)](**kwargs)
+        if sync_status:
+            ret['changes'][module] = sync_status
+            ret['comment'] = "Updated {0}.".format(module)
+    except Exception as e:
+        log.error("Failed to run saltutil.sync_%s: %s", module, e)
+        ret['result'] = False
+        ret['comment'] = "Failed to run sync_{0}: {1}".format(module, e)
+        return ret
+
+    if not ret['changes']:
+        ret['comment'] = "No updates to sync"
+
+    return ret
+
+
+def sync_all(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_all module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_all:
+            - refresh: True
+    '''
+    ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''}
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = "saltutil.sync_all would have been run"
+        return ret
+
+    try:
+        sync_status = __salt__['saltutil.sync_all'](**kwargs)
+        for key, value in sync_status.items():
+            if value:
+                ret['changes'][key] = value
+                ret['comment'] = "Sync performed"
+    except Exception as e:
+        log.error("Failed to run saltutil.sync_all: %s", e)
+        ret['result'] = False
+        ret['comment'] = "Failed to run sync_all: {0}".format(e)
+        return ret
+
+    if not ret['changes']:
+        ret['comment'] = "No updates to sync"
+
+    return ret
+
+
+def sync_beacons(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_beacons module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_beacons:
+            - refresh: True
+    '''
+    return _sync_single(name, "beacons", **kwargs)
+
+
+def sync_clouds(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_clouds module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_clouds:
+            - refresh: True
+    '''
+    return _sync_single(name, "clouds", **kwargs)
+
+
+def sync_engines(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_engines module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_engines:
+            - refresh: True
+    '''
+    return _sync_single(name, "engines", **kwargs)
+
+
+def sync_grains(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_grains module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_grains:
+            - refresh: True
+    '''
+    return _sync_single(name, "grains", **kwargs)
+
+
+def sync_log_handlers(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_log_handlers module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_log_handlers:
+            - refresh: True
+    '''
+    return _sync_single(name, "log_handlers", **kwargs)
+
+
+def sync_modules(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_modules module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_modules:
+            - refresh: True
+    '''
+    return _sync_single(name, "modules", **kwargs)
+
+
+def sync_output(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_output module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_output:
+            - refresh: True
+    '''
+    return _sync_single(name, "output", **kwargs)
+
+
+def sync_outputters(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_outputters module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_outputters:
+            - refresh: True
+    '''
+    return _sync_single(name, "outputters", **kwargs)
+
+
+def sync_pillar(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_pillar module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_pillar:
+            - refresh: True
+    '''
+    return _sync_single(name, "pillar", **kwargs)
+
+
+def sync_proxymodules(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_proxymodules module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_proxymodules:
+            - refresh: True
+    '''
+    return _sync_single(name, "proxymodules", **kwargs)
+
+
+def sync_renderers(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_renderers module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_renderers:
+            - refresh: True
+    '''
+    return _sync_single(name, "renderers", **kwargs)
+
+
+def sync_returners(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_returners module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_returners:
+            - refresh: True
+    '''
+    return _sync_single(name, "returners", **kwargs)
+
+
+def sync_sdb(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_sdb module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_sdb:
+            - refresh: True
+    '''
+    return _sync_single(name, "sdb", **kwargs)
+
+
+def sync_states(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_states module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_states:
+            - refresh: True
+    '''
+    return _sync_single(name, "states", **kwargs)
+
+
+def sync_thorium(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_thorium module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_thorium:
+            - refresh: True
+    '''
+    return _sync_single(name, "thorium", **kwargs)
+
+
+def sync_utils(name, **kwargs):
+    '''
+    Performs the same task as saltutil.sync_utils module
+    See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
+
+    .. code-block:: yaml
+
+        sync_everything:
+          saltutil.sync_utils:
+            - refresh: True
+    '''
+    return _sync_single(name, "utils", **kwargs)
diff --git a/tests/unit/states/test_saltutil.py 
b/tests/unit/states/test_saltutil.py
new file mode 100644
index 
0000000000000000000000000000000000000000..707201e9e2d3c285ed497e7f497158907a1834eb
--- /dev/null
+++ b/tests/unit/states/test_saltutil.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+'''
+    Tests for the saltutil state
+'''
+# Import Python libs
+from __future__ import absolute_import, print_function, unicode_literals
+
+# Import Salt Testing Libs
+from tests.support.mixins import LoaderModuleMockMixin
+from tests.support.unit import skipIf, TestCase
+from tests.support.mock import (
+    NO_MOCK,
+    NO_MOCK_REASON,
+    MagicMock,
+    patch
+)
+
+# Import Salt Libs
+import salt.states.saltutil as saltutil
+
+
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+class Saltutil(TestCase, LoaderModuleMockMixin):
+    '''
+    Test cases for salt.states.saltutil
+    '''
+    def setup_loader_modules(self):
+        return {saltutil: {'__opts__': {'test': False}}}
+
+    def test_saltutil_sync_all_nochange(self):
+        sync_output =   {
+                            "clouds": [],
+                            "engines": [],
+                            "grains": [],
+                            "beacons": [],
+                            "utils": [],
+                            "returners": [],
+                            "modules": [],
+                            "renderers": [],
+                            "log_handlers": [],
+                            "thorium": [],
+                            "states": [],
+                            "sdb": [],
+                            "proxymodules": [],
+                            "output": [],
+                            "pillar": []
+                        }
+        state_id = 'somename'
+        state_result = {'changes': {},
+                        'comment': 'No updates to sync',
+                        'name': 'somename',
+                        'result': True
+                       }
+
+        mock_moduleout = MagicMock(return_value=sync_output)
+        with patch.dict(saltutil.__salt__, {'saltutil.sync_all': 
mock_moduleout}):
+            result = saltutil.sync_all(state_id, refresh=True)
+            self.assertEqual(result, state_result)
+
+    def  test_saltutil_sync_all_test(self):
+        sync_output =   {
+                            "clouds": [],
+                            "engines": [],
+                            "grains": [],
+                            "beacons": [],
+                            "utils": [],
+                            "returners": [],
+                            "modules": [],
+                            "renderers": [],
+                            "log_handlers": [],
+                            "thorium": [],
+                            "states": [],
+                            "sdb": [],
+                            "proxymodules": [],
+                            "output": [],
+                            "pillar": []
+                        }
+        state_id = 'somename'
+        state_result = {'changes': {},
+                        'comment': 'saltutil.sync_all would have been run',
+                        'name': 'somename',
+                        'result': None
+                       }
+
+        mock_moduleout = MagicMock(return_value=sync_output)
+        with patch.dict(saltutil.__salt__, {'saltutil.sync_all': 
mock_moduleout}):
+            with patch.dict(saltutil.__opts__, {"test": True}):
+                result = saltutil.sync_all(state_id, refresh=True)
+                self.assertEqual(result, state_result)
+
+
+    def test_saltutil_sync_all_change(self):
+        sync_output =   {
+                            "clouds": [],
+                            "engines": [],
+                            "grains": [],
+                            "beacons": [],
+                            "utils": [],
+                            "returners": [],
+                            "modules": ["modules.file"],
+                            "renderers": [],
+                            "log_handlers": [],
+                            "thorium": [],
+                            "states": ["states.saltutil", "states.ssh_auth"],
+                            "sdb": [],
+                            "proxymodules": [],
+                            "output": [],
+                            "pillar": []
+                        }
+        state_id = 'somename'
+        state_result = {'changes': {'modules': ['modules.file'],
+                                    'states': ['states.saltutil', 
'states.ssh_auth']},
+                        'comment': 'Sync performed',
+                        'name': 'somename',
+                        'result': True
+                       }
+
+        mock_moduleout = MagicMock(return_value=sync_output)
+        with patch.dict(saltutil.__salt__, {'saltutil.sync_all': 
mock_moduleout}):
+            result = saltutil.sync_all(state_id, refresh=True)
+            self.assertEqual(result, state_result)
-- 
2.23.0


++++++ fix-load-cached-grain-osrelease_info.patch ++++++
>From 14f7acde30ea99abed535ab9491e9881ce609fdf Mon Sep 17 00:00:00 2001
From: Sergey Yurchik <srg91....@gmail.com>
Date: Fri, 10 Jan 2020 12:19:54 +0300
Subject: [PATCH] Fix load cached grain "osrelease_info"

Before fix the module `loader._load_cached_grains` loaded `osrelease_info` 
grain as list.
But everythere it expects `tuple`.
Now we force `osrelease_info` type and it works as expected.

Fixes #54908

Sanitize grains loaded from roster_grains.json

Ensure _format_cached_grains is called on state.pkg test
---
 salt/loader.py                   | 13 ++++++++++++-
 salt/modules/state.py            |  3 ++-
 tests/unit/modules/test_state.py |  4 +++-
 tests/unit/test_loader.py        | 28 ++++++++++++++++++++++++++++
 4 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/salt/loader.py b/salt/loader.py
index 
26b44de511d77dd498a1db655a44144f6e9fbf0a..984d80758ba8bd733a211f42d89e2d4e4fb25341
 100644
--- a/salt/loader.py
+++ b/salt/loader.py
@@ -689,6 +689,17 @@ def grain_funcs(opts, proxy=None):
     return ret
 
 
+def _format_cached_grains(cached_grains):
+    """
+    Returns cached grains with fixed types, like tuples.
+    """
+    if cached_grains.get('osrelease_info'):
+        osrelease_info = cached_grains['osrelease_info']
+        if isinstance(osrelease_info, list):
+            cached_grains['osrelease_info'] = tuple(osrelease_info)
+    return cached_grains
+
+
 def _load_cached_grains(opts, cfn):
     '''
     Returns the grains cached in cfn, or None if the cache is too old or is
@@ -721,7 +732,7 @@ def _load_cached_grains(opts, cfn):
             log.debug('Cached grains are empty, cache might be corrupted. 
Refreshing.')
             return None
 
-        return cached_grains
+        return _format_cached_grains(cached_grains)
     except (IOError, OSError):
         return None
 
diff --git a/salt/modules/state.py b/salt/modules/state.py
index 
a757e401d4f5c8fff33ad4b0f30f2882631fab80..197d69e1ff53e759ab268b9d562e6eb54f828e4c
 100644
--- a/salt/modules/state.py
+++ b/salt/modules/state.py
@@ -42,6 +42,7 @@ import salt.defaults.exitcodes
 from salt.exceptions import CommandExecutionError, SaltInvocationError
 from salt.runners.state import orchestrate as _orchestrate
 from salt.utils.odict import OrderedDict
+from salt.loader import _format_cached_grains
 
 # Import 3rd-party libs
 from salt.ext import six
@@ -2177,7 +2178,7 @@ def pkg(pkg_path,
     roster_grains_json = os.path.join(root, 'roster_grains.json')
     if os.path.isfile(roster_grains_json):
         with salt.utils.files.fopen(roster_grains_json, 'r') as fp_:
-            roster_grains = salt.utils.json.load(fp_)
+            roster_grains = _format_cached_grains(salt.utils.json.load(fp_))
 
     if os.path.isfile(roster_grains_json):
         popts['grains'] = roster_grains
diff --git a/tests/unit/modules/test_state.py b/tests/unit/modules/test_state.py
index 
0d15458be0a9b187efc7b2963612ea4bb078918e..8fc90d33bf22bf37a9597ecd1c6ff7ad43a60b6d
 100644
--- a/tests/unit/modules/test_state.py
+++ b/tests/unit/modules/test_state.py
@@ -1164,8 +1164,10 @@ class StateTestCase(TestCase, LoaderModuleMockMixin):
 
                 MockTarFile.path = ""
                 with patch('salt.utils.files.fopen', mock_open()), \
-                        patch.object(salt.utils.json, 'loads', 
mock_json_loads_true):
+                        patch.object(salt.utils.json, 'loads', 
mock_json_loads_true), \
+                            patch.object(state, '_format_cached_grains', 
MagicMock()):
                     self.assertEqual(state.pkg(tar_file, 0, "md5"), True)
+                    state._format_cached_grains.assert_called_once()
 
                 MockTarFile.path = ""
                 if six.PY2:
diff --git a/tests/unit/test_loader.py b/tests/unit/test_loader.py
index 
38dcb181090c61935835f14dd780bad2aa02d9e8..4c2c1b44af2d2931505ddcbb4339e087be88b4fc
 100644
--- a/tests/unit/test_loader.py
+++ b/tests/unit/test_loader.py
@@ -1334,3 +1334,31 @@ class LazyLoaderOptimizationOrderTest(TestCase):
         basename = os.path.basename(filename)
         expected = 'lazyloadertest.py' if six.PY3 else 'lazyloadertest.pyc'
         assert basename == expected, basename
+
+
+class LoaderLoadCachedGrainsTest(TestCase):
+    '''
+    Test how the loader works with cached grains
+    '''
+
+    @classmethod
+    def setUpClass(cls):
+        cls.opts = salt.config.minion_config(None)
+        if not os.path.isdir(RUNTIME_VARS.TMP):
+            os.makedirs(RUNTIME_VARS.TMP)
+
+    def setUp(self):
+        self.cache_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
+        self.addCleanup(shutil.rmtree, self.cache_dir, ignore_errors=True)
+
+        self.opts['cachedir'] = self.cache_dir
+        self.opts['grains_cache'] = True
+        self.opts['grains'] = salt.loader.grains(self.opts)
+
+    def test_osrelease_info_has_correct_type(self):
+        '''
+        Make sure osrelease_info is tuple after caching
+        '''
+        grains = salt.loader.grains(self.opts)
+        osrelease_info = grains['osrelease_info']
+        assert isinstance(osrelease_info, tuple), osrelease_info
-- 
2.23.0



Reply via email to