diff --git a/3003.3-do-not-consider-skipped-targets-as-failed-for.patch b/3003.3-do-not-consider-skipped-targets-as-failed-for.patch index 2d95be7..fa963c0 100644 --- a/3003.3-do-not-consider-skipped-targets-as-failed-for.patch +++ b/3003.3-do-not-consider-skipped-targets-as-failed-for.patch @@ -1,7 +1,6 @@ -From b279ec17a06619c97d9fbec215c7dd1ec3af088e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Wed, 6 Oct 2021 09:21:42 +0100 +From c2dbf6ccdf320f1cce3d42f746f3c65f8633ee55 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Wed, 19 Jan 2022 17:28:29 +0100 Subject: [PATCH] 3003.3: Do not consider skipped targets as failed for ansible.playbooks state (bsc#1190446) (#436) @@ -14,16 +13,16 @@ Subject: [PATCH] 3003.3: Do not consider skipped targets as failed for * Fix remaining problems in unit tests after backport --- salt/states/ansiblegate.py | 19 +- + tests/pytests/unit/states/test_ansiblegate.py | 30 + .../success_example_with_skipped.json | 1320 +++++++++++++++++ - tests/unit/states/test_ansiblegate.py | 33 +- - 3 files changed, 1356 insertions(+), 16 deletions(-) + 3 files changed, 1354 insertions(+), 15 deletions(-) create mode 100644 tests/unit/files/playbooks/success_example_with_skipped.json diff --git a/salt/states/ansiblegate.py b/salt/states/ansiblegate.py -index bd00653928..d97a0ebb5b 100644 +index af5cb0f0e5..4de7fb096d 100644 --- a/salt/states/ansiblegate.py +++ b/salt/states/ansiblegate.py -@@ -188,19 +188,13 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= +@@ -189,19 +189,13 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= ret["result"] = False ret["changes"] = {} elif all( @@ -45,7 +44,7 @@ index bd00653928..d97a0ebb5b 100644 for check in checks["stats"].values() ): ret["comment"] = "Changes will be made from playbook {}".format(name) -@@ -221,10 +215,7 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= +@@ -222,10 +216,7 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= ret["result"] = False ret["changes"] = {} elif all( @@ -57,7 +56,7 @@ index bd00653928..d97a0ebb5b 100644 for check in results["stats"].values() ): ret["comment"] = "No changes to be made from playbook {}".format(name) -@@ -233,9 +224,7 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= +@@ -234,9 +225,7 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= else: ret["changes"] = _changes(results) ret["result"] = all( @@ -68,6 +67,47 @@ index bd00653928..d97a0ebb5b 100644 for check in results["stats"].values() ) if ret["result"]: +diff --git a/tests/pytests/unit/states/test_ansiblegate.py b/tests/pytests/unit/states/test_ansiblegate.py +index fa1a1cd264..02157d3420 100644 +--- a/tests/pytests/unit/states/test_ansiblegate.py ++++ b/tests/pytests/unit/states/test_ansiblegate.py +@@ -42,6 +42,36 @@ def test_ansible_playbooks_states_success(playbooks_examples_dir): + } + + ++def test_ansible_playbooks_states_success_with_skipped(playbooks_examples_dir): ++ """ ++ Test ansible.playbooks states executions success. ++ """ ++ ++ success_output = json.loads( ++ playbooks_examples_dir.joinpath("success_example_with_skipped.json").read_text() ++ ) ++ ++ with patch.dict( ++ ansiblegate.__salt__, ++ {"ansible.playbooks": MagicMock(return_value=success_output)}, ++ ), patch("salt.utils.path.which", return_value=True), patch.dict( ++ ansiblegate.__opts__, {"test": False} ++ ): ++ ret = ansiblegate.playbooks("foobar") ++ assert ret["result"] is True ++ assert ret["comment"] == "No changes to be made from playbook foobar" ++ assert ret["changes"] == { ++ "all": { ++ "install git CentOS": {"uyuni-stable-min-sles15sp3.tf.local": {}}, ++ "install git SUSE": {"uyuni-stable-min-centos7.tf.local": {}}, ++ "install git Ubuntu": { ++ "uyuni-stable-min-centos7.tf.local": {}, ++ "uyuni-stable-min-sles15sp3.tf.local": {}, ++ }, ++ } ++ } ++ ++ + def test_ansible_playbooks_states_failed(playbooks_examples_dir): + """ + Test ansible.playbooks failed states executions. diff --git a/tests/unit/files/playbooks/success_example_with_skipped.json b/tests/unit/files/playbooks/success_example_with_skipped.json new file mode 100644 index 0000000000..21bdb290c1 @@ -1394,58 +1434,7 @@ index 0000000000..21bdb290c1 + }, + "retcode": 0 +} -diff --git a/tests/unit/states/test_ansiblegate.py b/tests/unit/states/test_ansiblegate.py -index ac677fc5db..6bf4494326 100644 ---- a/tests/unit/states/test_ansiblegate.py -+++ b/tests/unit/states/test_ansiblegate.py -@@ -12,7 +12,6 @@ - # See the License for the specific language governing permissions and - # limitations under the License. - --# Import Salt Testing Libs - - import json - import os -@@ -73,6 +72,38 @@ class AnsiblegateTestCase(TestCase, LoaderModuleMockMixin): - }, - ) - -+ @patch("salt.utils.path.which", MagicMock(return_value=True)) -+ def test_ansible_playbooks_states_success_with_skipped(self): -+ """ -+ Test ansible.playbooks states executions success. -+ """ -+ -+ with salt.utils.files.fopen( -+ os.path.join(self.playbooks_examples_dir, "success_example_with_skipped.json") -+ ) as f: -+ success_output = json.loads(f.read()) -+ -+ with patch.dict( -+ ansible.__salt__, -+ {"ansible.playbooks": MagicMock(return_value=success_output)}, -+ ), patch("salt.utils.path.which", return_value=True), patch.dict( -+ ansible.__opts__, {"test": False} -+ ): -+ ret = ansible.playbooks("foobar") -+ assert ret["result"] is True -+ assert ret["comment"] == "No changes to be made from playbook foobar" -+ assert ret["changes"] == { -+ "all": { -+ "install git CentOS": {"uyuni-stable-min-sles15sp3.tf.local": {}}, -+ "install git SUSE": {"uyuni-stable-min-centos7.tf.local": {}}, -+ "install git Ubuntu": { -+ "uyuni-stable-min-centos7.tf.local": {}, -+ "uyuni-stable-min-sles15sp3.tf.local": {}, -+ }, -+ } -+ } -+ -+ @patch("salt.utils.path.which", MagicMock(return_value=True)) - def test_ansible_playbooks_states_failed(self): - """ - Test ansible.playbooks failed states executions. -- -2.33.0 +2.34.1 diff --git a/3003.3-postgresql-json-support-in-pillar-423.patch b/3003.3-postgresql-json-support-in-pillar-423.patch index 5d2fbbf..ca9d794 100644 --- a/3003.3-postgresql-json-support-in-pillar-423.patch +++ b/3003.3-postgresql-json-support-in-pillar-423.patch @@ -1,6 +1,6 @@ -From 6ffe3270d58527576688011e2b3bd826ec3941ee Mon Sep 17 00:00:00 2001 -From: Cedric Bosdonnat -Date: Fri, 24 Sep 2021 17:01:34 +0200 +From 5c2624552e1ac2dbec3b54ff8c147ae50494969e Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Wed, 19 Jan 2022 17:25:37 +0100 Subject: [PATCH] 3003.3 - postgresql JSON support in pillar (#423) * Allow single field returns from SQL pillar @@ -40,10 +40,10 @@ index 0000000000..3fe39286a8 @@ -0,0 +1 @@ +Support querying for JSON data in SQL external pillar diff --git a/salt/pillar/sql_base.py b/salt/pillar/sql_base.py -index 976ca8c0d8..9d9f0c9c9f 100644 +index f7d87105af..8020d5503b 100644 --- a/salt/pillar/sql_base.py +++ b/salt/pillar/sql_base.py -@@ -137,6 +137,33 @@ These columns define list grouping +@@ -136,6 +136,33 @@ These columns define list grouping The range for with_lists is 1 to number_of_fields, inclusive. Numbers outside this range are ignored. @@ -77,15 +77,15 @@ index 976ca8c0d8..9d9f0c9c9f 100644 Finally, if you pass the queries in via a mapping, the key will be the first level name where as passing them in as a list will place them in the root. This isolates the query results into their own subtrees. -@@ -179,6 +206,7 @@ from salt.ext import six - from salt.ext.six.moves import range +@@ -171,6 +198,7 @@ More complete example for MySQL (to also show configuration) + import abc # Added in python2.6 so always available + import logging - # Import Salt libs +from salt.utils.dictupdate import update from salt.utils.odict import OrderedDict # Please don't strip redundant parentheses from this file. -@@ -208,6 +236,7 @@ class SqlBaseExtPillar(six.with_metaclass(abc.ABCMeta, object)): +@@ -200,6 +228,7 @@ class SqlBaseExtPillar(metaclass=abc.ABCMeta): num_fields = 0 depth = 0 as_list = False @@ -93,7 +93,7 @@ index 976ca8c0d8..9d9f0c9c9f 100644 with_lists = None ignore_null = False -@@ -267,6 +296,7 @@ class SqlBaseExtPillar(six.with_metaclass(abc.ABCMeta, object)): +@@ -259,6 +288,7 @@ class SqlBaseExtPillar(metaclass=abc.ABCMeta): "query": "", "depth": 0, "as_list": False, @@ -101,7 +101,7 @@ index 976ca8c0d8..9d9f0c9c9f 100644 "with_lists": None, "ignore_null": False, } -@@ -324,6 +354,13 @@ class SqlBaseExtPillar(six.with_metaclass(abc.ABCMeta, object)): +@@ -314,6 +344,13 @@ class SqlBaseExtPillar(metaclass=abc.ABCMeta): for ret in rows: # crd is the Current Return Data level, to make this non-recursive. crd = self.focus @@ -115,7 +115,7 @@ index 976ca8c0d8..9d9f0c9c9f 100644 # Walk and create dicts above the final layer for i in range(0, self.depth - 1): # At the end we'll use listify to find values to make a list of -@@ -443,6 +480,7 @@ class SqlBaseExtPillar(six.with_metaclass(abc.ABCMeta, object)): +@@ -433,6 +470,7 @@ class SqlBaseExtPillar(metaclass=abc.ABCMeta): ) self.enter_root(root) self.as_list = details["as_list"] @@ -173,10 +173,10 @@ index 0000000000..0d44c2d608 + "f": [{"g": 7, "h": "test"}], + } diff --git a/tests/unit/pillar/test_mysql.py b/tests/unit/pillar/test_mysql.py -index bc81eb4174..9db724329d 100644 +index ddfb67d230..de6212b2c8 100644 --- a/tests/unit/pillar/test_mysql.py +++ b/tests/unit/pillar/test_mysql.py -@@ -26,6 +26,7 @@ class MysqlPillarTestCase(TestCase): +@@ -18,6 +18,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -184,7 +184,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -47,6 +48,7 @@ class MysqlPillarTestCase(TestCase): +@@ -39,6 +40,7 @@ class MysqlPillarTestCase(TestCase): {"query": "SELECT blah7", "as_list": True}, {"query": "SELECT blah8", "with_lists": "1"}, {"query": "SELECT blah9", "with_lists": "1,2"}, @@ -192,7 +192,7 @@ index bc81eb4174..9db724329d 100644 ], {}, ) -@@ -59,6 +61,7 @@ class MysqlPillarTestCase(TestCase): +@@ -51,6 +53,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -200,7 +200,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -69,6 +72,7 @@ class MysqlPillarTestCase(TestCase): +@@ -61,6 +64,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -208,7 +208,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -79,6 +83,7 @@ class MysqlPillarTestCase(TestCase): +@@ -71,6 +75,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -216,7 +216,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -89,6 +94,7 @@ class MysqlPillarTestCase(TestCase): +@@ -81,6 +86,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -224,7 +224,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -99,6 +105,7 @@ class MysqlPillarTestCase(TestCase): +@@ -91,6 +97,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -232,7 +232,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -109,6 +116,7 @@ class MysqlPillarTestCase(TestCase): +@@ -101,6 +108,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah6", "depth": 2, "as_list": False, @@ -240,7 +240,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -119,6 +127,7 @@ class MysqlPillarTestCase(TestCase): +@@ -111,6 +119,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah7", "depth": 0, "as_list": True, @@ -248,7 +248,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -129,6 +138,7 @@ class MysqlPillarTestCase(TestCase): +@@ -121,6 +130,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah8", "depth": 0, "as_list": False, @@ -256,7 +256,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": [1], "ignore_null": False, }, -@@ -139,10 +149,22 @@ class MysqlPillarTestCase(TestCase): +@@ -131,10 +141,22 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah9", "depth": 0, "as_list": False, @@ -279,7 +279,7 @@ index bc81eb4174..9db724329d 100644 ], qbuffer, ) -@@ -159,6 +181,7 @@ class MysqlPillarTestCase(TestCase): +@@ -151,6 +173,7 @@ class MysqlPillarTestCase(TestCase): "5": {"query": "SELECT blah5"}, "6": {"query": "SELECT blah6", "depth": 2}, "7": {"query": "SELECT blah7", "as_list": True}, @@ -287,7 +287,7 @@ index bc81eb4174..9db724329d 100644 }, ) qbuffer = return_data.extract_queries(args, kwargs) -@@ -170,6 +193,7 @@ class MysqlPillarTestCase(TestCase): +@@ -162,6 +185,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -295,7 +295,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -180,6 +204,7 @@ class MysqlPillarTestCase(TestCase): +@@ -172,6 +196,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -303,7 +303,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -190,6 +215,7 @@ class MysqlPillarTestCase(TestCase): +@@ -182,6 +207,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -311,7 +311,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -200,6 +226,7 @@ class MysqlPillarTestCase(TestCase): +@@ -192,6 +218,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -319,7 +319,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -210,6 +237,7 @@ class MysqlPillarTestCase(TestCase): +@@ -202,6 +229,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -327,7 +327,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -220,6 +248,7 @@ class MysqlPillarTestCase(TestCase): +@@ -212,6 +240,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah6", "depth": 2, "as_list": False, @@ -335,7 +335,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -230,6 +259,18 @@ class MysqlPillarTestCase(TestCase): +@@ -222,6 +251,18 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah7", "depth": 0, "as_list": True, @@ -354,7 +354,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -261,6 +302,7 @@ class MysqlPillarTestCase(TestCase): +@@ -253,6 +294,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah1", "depth": 0, "as_list": False, @@ -362,7 +362,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -271,6 +313,7 @@ class MysqlPillarTestCase(TestCase): +@@ -263,6 +305,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 2, "as_list": False, @@ -370,7 +370,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -281,6 +324,7 @@ class MysqlPillarTestCase(TestCase): +@@ -273,6 +316,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": True, @@ -378,7 +378,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -291,6 +335,7 @@ class MysqlPillarTestCase(TestCase): +@@ -283,6 +327,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah1", "depth": 0, "as_list": False, @@ -386,7 +386,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -301,6 +346,7 @@ class MysqlPillarTestCase(TestCase): +@@ -293,6 +338,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 2, "as_list": False, @@ -394,7 +394,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -311,6 +357,7 @@ class MysqlPillarTestCase(TestCase): +@@ -303,6 +349,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": True, @@ -402,7 +402,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -349,6 +396,7 @@ class MysqlPillarTestCase(TestCase): +@@ -341,6 +388,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -410,7 +410,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -359,6 +407,7 @@ class MysqlPillarTestCase(TestCase): +@@ -351,6 +399,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -418,7 +418,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -369,6 +418,7 @@ class MysqlPillarTestCase(TestCase): +@@ -361,6 +410,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -426,7 +426,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -379,6 +429,7 @@ class MysqlPillarTestCase(TestCase): +@@ -371,6 +421,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -434,7 +434,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -389,6 +440,7 @@ class MysqlPillarTestCase(TestCase): +@@ -381,6 +432,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -442,7 +442,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -399,6 +451,7 @@ class MysqlPillarTestCase(TestCase): +@@ -391,6 +443,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah6", "depth": 0, "as_list": False, @@ -450,7 +450,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -409,6 +462,7 @@ class MysqlPillarTestCase(TestCase): +@@ -401,6 +454,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah7", "depth": 2, "as_list": False, @@ -458,7 +458,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -419,6 +473,7 @@ class MysqlPillarTestCase(TestCase): +@@ -411,6 +465,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah8", "depth": 0, "as_list": True, @@ -466,7 +466,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -440,6 +495,7 @@ class MysqlPillarTestCase(TestCase): +@@ -432,6 +487,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -474,7 +474,7 @@ index bc81eb4174..9db724329d 100644 "with_lists": None, "ignore_null": False, }, -@@ -450,6 +506,7 @@ class MysqlPillarTestCase(TestCase): +@@ -442,6 +498,7 @@ class MysqlPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -483,10 +483,10 @@ index bc81eb4174..9db724329d 100644 "ignore_null": False, }, diff --git a/tests/unit/pillar/test_sqlcipher.py b/tests/unit/pillar/test_sqlcipher.py -index d7e9eed6f6..6f7b21fb3f 100644 +index 99edcb094c..1330c3bbfc 100644 --- a/tests/unit/pillar/test_sqlcipher.py +++ b/tests/unit/pillar/test_sqlcipher.py -@@ -38,6 +38,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -30,6 +30,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -494,7 +494,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -48,6 +49,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -40,6 +41,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -502,7 +502,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -58,6 +60,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -50,6 +52,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -510,7 +510,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -68,6 +71,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -60,6 +63,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -518,7 +518,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -78,6 +82,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -70,6 +74,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -526,7 +526,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -88,6 +93,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -80,6 +85,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah6", "depth": 2, "as_list": False, @@ -534,7 +534,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -98,6 +104,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -90,6 +96,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah7", "depth": 0, "as_list": True, @@ -542,7 +542,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -108,6 +115,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -100,6 +107,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah8", "depth": 0, "as_list": False, @@ -550,7 +550,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": [1], "ignore_null": False, }, -@@ -118,6 +126,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -110,6 +118,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah9", "depth": 0, "as_list": False, @@ -558,7 +558,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": [1, 2], "ignore_null": False, }, -@@ -149,6 +158,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -141,6 +150,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -566,7 +566,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -159,6 +169,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -151,6 +161,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -574,7 +574,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -169,6 +180,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -161,6 +172,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -582,7 +582,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -179,6 +191,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -171,6 +183,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -590,7 +590,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -189,6 +202,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -181,6 +194,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -598,7 +598,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -199,6 +213,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -191,6 +205,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah6", "depth": 2, "as_list": False, @@ -606,7 +606,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -209,6 +224,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -201,6 +216,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah7", "depth": 0, "as_list": True, @@ -614,7 +614,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -240,6 +256,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -232,6 +248,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah1", "depth": 0, "as_list": False, @@ -622,7 +622,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -250,6 +267,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -242,6 +259,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 2, "as_list": False, @@ -630,7 +630,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -260,6 +278,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -252,6 +270,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": True, @@ -638,7 +638,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -270,6 +289,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -262,6 +281,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah1", "depth": 0, "as_list": False, @@ -646,7 +646,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -280,6 +300,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -272,6 +292,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 2, "as_list": False, @@ -654,7 +654,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -290,6 +311,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -282,6 +303,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": True, @@ -662,7 +662,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -328,6 +350,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -320,6 +342,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -670,7 +670,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -338,6 +361,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -330,6 +353,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -678,7 +678,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -348,6 +372,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -340,6 +364,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -686,7 +686,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -358,6 +383,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -350,6 +375,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -694,7 +694,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -368,6 +394,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -360,6 +386,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -702,7 +702,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -378,6 +405,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -370,6 +397,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah6", "depth": 0, "as_list": False, @@ -710,7 +710,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -388,6 +416,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -380,6 +408,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah7", "depth": 2, "as_list": False, @@ -718,7 +718,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -398,6 +427,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -390,6 +419,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah8", "depth": 0, "as_list": True, @@ -726,7 +726,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -419,6 +449,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -411,6 +441,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -734,7 +734,7 @@ index d7e9eed6f6..6f7b21fb3f 100644 "with_lists": None, "ignore_null": False, }, -@@ -429,6 +460,7 @@ class SQLCipherPillarTestCase(TestCase): +@@ -421,6 +452,7 @@ class SQLCipherPillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -743,10 +743,10 @@ index d7e9eed6f6..6f7b21fb3f 100644 "ignore_null": False, }, diff --git a/tests/unit/pillar/test_sqlite3.py b/tests/unit/pillar/test_sqlite3.py -index da780682e7..69efd0a3e8 100644 +index 1d0b187729..fee651db32 100644 --- a/tests/unit/pillar/test_sqlite3.py +++ b/tests/unit/pillar/test_sqlite3.py -@@ -38,6 +38,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -30,6 +30,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -754,7 +754,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -48,6 +49,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -40,6 +41,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -762,7 +762,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -58,6 +60,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -50,6 +52,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -770,7 +770,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -68,6 +71,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -60,6 +63,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -778,7 +778,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -78,6 +82,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -70,6 +74,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -786,7 +786,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -88,6 +93,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -80,6 +85,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah6", "depth": 2, "as_list": False, @@ -794,7 +794,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -98,6 +104,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -90,6 +96,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah7", "depth": 0, "as_list": True, @@ -802,7 +802,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -108,6 +115,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -100,6 +107,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah8", "depth": 0, "as_list": False, @@ -810,7 +810,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": [1], "ignore_null": False, }, -@@ -118,6 +126,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -110,6 +118,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah9", "depth": 0, "as_list": False, @@ -818,7 +818,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": [1, 2], "ignore_null": False, }, -@@ -149,6 +158,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -141,6 +150,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -826,7 +826,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -159,6 +169,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -151,6 +161,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -834,7 +834,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -169,6 +180,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -161,6 +172,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -842,7 +842,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -179,6 +191,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -171,6 +183,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -850,7 +850,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -189,6 +202,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -181,6 +194,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -858,7 +858,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -199,6 +213,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -191,6 +205,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah6", "depth": 2, "as_list": False, @@ -866,7 +866,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -209,6 +224,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -201,6 +216,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah7", "depth": 0, "as_list": True, @@ -874,7 +874,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -240,6 +256,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -232,6 +248,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah1", "depth": 0, "as_list": False, @@ -882,7 +882,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -250,6 +267,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -242,6 +259,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah2", "depth": 2, "as_list": False, @@ -890,7 +890,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -260,6 +278,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -252,6 +270,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": True, @@ -898,7 +898,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -270,6 +289,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -262,6 +281,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah1", "depth": 0, "as_list": False, @@ -906,7 +906,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -280,6 +300,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -272,6 +292,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah2", "depth": 2, "as_list": False, @@ -914,7 +914,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -290,6 +311,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -282,6 +303,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": True, @@ -922,7 +922,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -328,6 +350,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -320,6 +342,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -930,7 +930,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -338,6 +361,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -330,6 +353,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -938,7 +938,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -348,6 +372,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -340,6 +364,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah3", "depth": 0, "as_list": False, @@ -946,7 +946,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -358,6 +383,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -350,6 +375,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah4", "depth": 2, "as_list": False, @@ -954,7 +954,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -368,6 +394,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -360,6 +386,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah5", "depth": 0, "as_list": False, @@ -962,7 +962,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -378,6 +405,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -370,6 +397,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah6", "depth": 0, "as_list": False, @@ -970,7 +970,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -388,6 +416,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -380,6 +408,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah7", "depth": 2, "as_list": False, @@ -978,7 +978,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -398,6 +427,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -390,6 +419,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah8", "depth": 0, "as_list": True, @@ -986,7 +986,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -419,6 +449,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -411,6 +441,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah", "depth": 0, "as_list": False, @@ -994,7 +994,7 @@ index da780682e7..69efd0a3e8 100644 "with_lists": None, "ignore_null": False, }, -@@ -429,6 +460,7 @@ class SQLite3PillarTestCase(TestCase): +@@ -421,6 +452,7 @@ class SQLite3PillarTestCase(TestCase): "query": "SELECT blah2", "depth": 0, "as_list": False, @@ -1003,6 +1003,6 @@ index da780682e7..69efd0a3e8 100644 "ignore_null": False, }, -- -2.33.0 +2.34.1 diff --git a/_lastrevision b/_lastrevision index ee9a323..0348950 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -21e5e5ac757d79b2899ba18b18ae369d713013dd \ No newline at end of file +4dd4ea97489b05983b3cd9277bca1edc25c50985 \ No newline at end of file diff --git a/_service b/_service index ec6074d..177b899 100644 --- a/_service +++ b/_service @@ -3,7 +3,7 @@ https://github.com/openSUSE/salt-packaging.git salt package - 3003.3 + 3004 git @@ -12,8 +12,8 @@ codeload.github.com - openSUSE/salt/tar.gz/v3003.3-suse - v3003.3.tar.gz + openSUSE/salt/tar.gz/v3004-suse + v3004.tar.gz diff --git a/add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch b/add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch deleted file mode 100644 index 3e076ca..0000000 --- a/add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 2e810cc876f7b7110326231de51d78ff5d12eae6 Mon Sep 17 00:00:00 2001 -From: Pau Garcia Quiles -Date: Tue, 13 Apr 2021 10:31:09 +0200 -Subject: [PATCH] Add Alibaba Cloud Linux 2 by backporting upstream's - grain and discarding my own (#352) - ---- - salt/grains/core.py | 2 ++ - tests/unit/grains/test_core.py | 28 ++++++++++++++++++++++++++++ - 2 files changed, 30 insertions(+) - -diff --git a/salt/grains/core.py b/salt/grains/core.py -index 19937f008e..bce8c95179 100644 ---- a/salt/grains/core.py -+++ b/salt/grains/core.py -@@ -1560,6 +1560,7 @@ _OS_NAME_MAP = { - "linuxmint": "Mint", - "neon": "KDE neon", - "pop": "Pop", -+ "alibabaclo": "Alinux", - } - - # Map the 'os' grain to the 'os_family' grain -@@ -1637,6 +1638,7 @@ _OS_FAMILY_MAP = { - "TurnKey": "Debian", - "Pop": "Debian", - "AstraLinuxCE": "Debian", -+ "Alinux": "RedHat", - } - - # Matches any possible format: -diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py -index ac2d515bcd..fa06bb27ab 100644 ---- a/tests/unit/grains/test_core.py -+++ b/tests/unit/grains/test_core.py -@@ -846,6 +846,34 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - } - self._run_os_grains_tests("astralinuxce-2.12.22", _os_release_map, expectation) - -+ @skipIf(not salt.utils.platform.is_linux(), 'System is not Linux') -+ def test_aliyunlinux2_os_grains(self): -+ ''' -+ Test if OS grains are parsed correctly in Alibaba Cloud Linux (Aliyun Linux) 2.1903 LTS -+ ''' -+ _os_release_map = { -+ "os_release_file": { -+ "NAME": "Alibaba Cloud Linux (Aliyun Linux)", -+ "VERSION": "2.1903 LTS (Hunting Beagle)", -+ "VERSION_ID": "2.1903", -+ "PRETTY_NAME": "Alibaba Cloud Linux (Aliyun Linux) 2.1903 LTS (Hunting Beagle)", -+ "ID": "alinux", -+ "ANSI_COLOR": "0;31", -+ }, -+ "_linux_distribution": ("alinux", "2.1903", "LTS"), -+ } -+ expectation = { -+ "os": "Alinux", -+ "os_family": "RedHat", -+ "oscodename": "Alibaba Cloud Linux (Aliyun Linux) 2.1903 LTS (Hunting Beagle)", -+ "osfullname": "Alibaba Cloud Linux (Aliyun Linux)", -+ "osrelease": "2.1903", -+ "osrelease_info": (2, 1903), -+ "osmajorrelease": 2, -+ "osfinger": "Alibaba Cloud Linux (Aliyun Linux)-2", -+ } -+ self._run_os_grains_tests(None, _os_release_map, expectation) -+ - @skipIf(not salt.utils.platform.is_windows(), "System is not Windows") - def test_windows_platform_data(self): - """ --- -2.33.0 - - diff --git a/add-astra-linux-common-edition-to-the-os-family-list.patch b/add-astra-linux-common-edition-to-the-os-family-list.patch deleted file mode 100644 index cf2be72..0000000 --- a/add-astra-linux-common-edition-to-the-os-family-list.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 30366101c20eefd2411482138edfa0ca0c8a3b06 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Julio=20Gonz=C3=A1lez=20Gil?= - -Date: Wed, 12 Feb 2020 10:05:45 +0100 -Subject: [PATCH] Add Astra Linux Common Edition to the OS Family list - (#209) - ---- - salt/grains/core.py | 1 + - tests/unit/grains/test_core.py | 20 ++++++++++++++++++++ - 2 files changed, 21 insertions(+) - -diff --git a/salt/grains/core.py b/salt/grains/core.py -index e007f40c92..19937f008e 100644 ---- a/salt/grains/core.py -+++ b/salt/grains/core.py -@@ -1636,6 +1636,7 @@ _OS_FAMILY_MAP = { - "AIX": "AIX", - "TurnKey": "Debian", - "Pop": "Debian", -+ "AstraLinuxCE": "Debian", - } - - # Matches any possible format: -diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py -index 7173f04979..e8845e2dfa 100644 ---- a/tests/unit/grains/test_core.py -+++ b/tests/unit/grains/test_core.py -@@ -826,6 +826,26 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - } - self._run_os_grains_tests("pop-20.10", _os_release_map, expectation) - -+ @skipIf(not salt.utils.platform.is_linux(), "System is not Linux") -+ def test_astralinuxce_2_os_grains(self): -+ """ -+ Test if OS grains are parsed correctly in Astra Linux CE 2.12.22 "orel" -+ """ -+ _os_release_map = { -+ "_linux_distribution": ("AstraLinuxCE", "2.12.22", "orel"), -+ } -+ expectation = { -+ "os": "AstraLinuxCE", -+ "os_family": "Debian", -+ "oscodename": "orel", -+ "osfullname": "AstraLinuxCE", -+ "osrelease": "2.12.22", -+ "osrelease_info": (2, 12, 22), -+ "osmajorrelease": 2, -+ "osfinger": "AstraLinuxCE-2", -+ } -+ self._run_os_grains_tests("astralinuxce-2.12.22", _os_release_map, expectation) -+ - @skipIf(not salt.utils.platform.is_windows(), "System is not Windows") - def test_windows_platform_data(self): - """ --- -2.33.0 - - diff --git a/add-custom-suse-capabilities-as-grains.patch b/add-custom-suse-capabilities-as-grains.patch index 8e3a2f0..39e1b58 100644 --- a/add-custom-suse-capabilities-as-grains.patch +++ b/add-custom-suse-capabilities-as-grains.patch @@ -1,7 +1,6 @@ -From bdb48ed82c755407bc413fa445e057a6da5f1e87 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Thu, 21 Jun 2018 11:57:57 +0100 +From 1c20e6e1acf21d301d6e53432afaa7cc42db2380 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 12:59:43 +0100 Subject: [PATCH] Add custom SUSE capabilities as Grains Add new custom SUSE capability for saltutil state module @@ -10,13 +9,13 @@ Add new custom SUSE capability for saltutil state module 1 file changed, 8 insertions(+) diff --git a/salt/grains/extra.py b/salt/grains/extra.py -index 2fdbe6526a..0eec27e628 100644 +index 300052f1ee..f2504dbf19 100644 --- a/salt/grains/extra.py +++ b/salt/grains/extra.py -@@ -66,3 +66,11 @@ def config(): - log.warning("Bad syntax in grains file! Skipping.") - return {} - return {} +@@ -96,3 +96,11 @@ def uefi(): + def transactional(): + """Determine if the system is transactional.""" + return {"transactional": bool(salt.utils.path.which("transactional-update"))} + + +def suse_backported_capabilities(): @@ -26,6 +25,6 @@ index 2fdbe6526a..0eec27e628 100644 + '__suse_reserved_saltutil_states_support': True + } -- -2.33.0 +2.34.1 diff --git a/add-environment-variable-to-know-if-yum-is-invoked-f.patch b/add-environment-variable-to-know-if-yum-is-invoked-f.patch index a03e838..76f7d79 100644 --- a/add-environment-variable-to-know-if-yum-is-invoked-f.patch +++ b/add-environment-variable-to-know-if-yum-is-invoked-f.patch @@ -1,18 +1,18 @@ -From 7b2b5fc53d30397b8f7a11e59f5c7a57bcb63058 Mon Sep 17 00:00:00 2001 -From: Marcelo Chiaradia -Date: Thu, 7 Jun 2018 10:29:41 +0200 -Subject: [PATCH] Add environment variable to know if yum is invoked - from Salt(bsc#1057635) +From 6ba30d3900bc328efd3480c0ff3d9e9b126fc5cb Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 12:57:21 +0100 +Subject: [PATCH] Add environment variable to know if yum is invoked from + Salt(bsc#1057635) --- salt/modules/yumpkg.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py -index b547fe6be7..c58b3e4c70 100644 +index cf684e20f7..8d089c6aa4 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py -@@ -917,7 +917,9 @@ def list_repo_pkgs(*args, **kwargs): +@@ -965,7 +965,9 @@ def list_repo_pkgs(*args, **kwargs): None if _yum() != "yum" else _LooseVersion( @@ -23,7 +23,7 @@ index b547fe6be7..c58b3e4c70 100644 .splitlines()[0] .strip() ) -@@ -2347,7 +2349,9 @@ def list_holds(pattern=__HOLD_PATTERN, full=True): +@@ -2422,7 +2424,9 @@ def list_holds(pattern=__HOLD_PATTERN, full=True): """ _check_versionlock() @@ -34,7 +34,7 @@ index b547fe6be7..c58b3e4c70 100644 ret = [] for line in salt.utils.itertools.split(out, "\n"): match = _get_hold(line, pattern=pattern, full=full) -@@ -2415,7 +2419,10 @@ def group_list(): +@@ -2490,7 +2494,10 @@ def group_list(): } out = __salt__["cmd.run_stdout"]( @@ -46,7 +46,7 @@ index b547fe6be7..c58b3e4c70 100644 ) key = None for line in salt.utils.itertools.split(out, "\n"): -@@ -2486,7 +2493,9 @@ def group_info(name, expand=False, ignore_groups=None): +@@ -2561,7 +2568,9 @@ def group_info(name, expand=False, ignore_groups=None): ret[pkgtype] = set() cmd = [_yum(), "--quiet", "groupinfo", name] @@ -57,7 +57,7 @@ index b547fe6be7..c58b3e4c70 100644 g_info = {} for line in salt.utils.itertools.split(out, "\n"): -@@ -3203,7 +3212,9 @@ def download(*packages, **kwargs): +@@ -3278,7 +3287,9 @@ def download(*packages, **kwargs): cmd = ["yumdownloader", "-q", "--destdir={}".format(CACHE_DIR)] cmd.extend(packages) @@ -68,16 +68,16 @@ index b547fe6be7..c58b3e4c70 100644 ret = {} for dld_result in os.listdir(CACHE_DIR): if not dld_result.endswith(".rpm"): -@@ -3279,7 +3290,7 @@ def _get_patches(installed_only=False): +@@ -3354,7 +3365,7 @@ def _get_patches(installed_only=False): patches = {} cmd = [_yum(), "--quiet", "updateinfo", "list", "all"] - ret = __salt__["cmd.run_stdout"](cmd, python_shell=False) + ret = __salt__["cmd.run_stdout"](cmd, python_shell=False, env={"SALT_RUNNING": "1"}) + parsing_errors = False + for line in salt.utils.itertools.split(ret, os.linesep): - inst, advisory_id, sev, pkg = re.match( - r"([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)", line -- -2.29.2 +2.34.1 diff --git a/add-migrated-state-and-gpg-key-management-functions-.patch b/add-migrated-state-and-gpg-key-management-functions-.patch index 11e6c63..e1552d7 100644 --- a/add-migrated-state-and-gpg-key-management-functions-.patch +++ b/add-migrated-state-and-gpg-key-management-functions-.patch @@ -1,6 +1,6 @@ -From acd8fbfd7b2c1fdf84b0250e245418d8c6e387ec Mon Sep 17 00:00:00 2001 -From: Alberto Planas -Date: Tue, 20 Oct 2020 11:43:09 +0200 +From 82ceb569ea57fc14ff3e2fa1c3f7ef5b95bb5eb0 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 18:40:40 +0100 Subject: [PATCH] Add "migrated" state and GPG key management functions (#290) @@ -20,35 +20,21 @@ same virtual package, based on the counterpart from rpm_lowpkg API. Convert test to pytests --- - changelog/58782.added | 1 + - salt/modules/aptpkg.py | 7 +- - salt/modules/rpm_lowpkg.py | 151 ++ - salt/modules/yumpkg.py | 89 +- - salt/modules/zypperpkg.py | 88 + - salt/states/pkgrepo.py | 207 +++ - tests/pytests/unit/modules/test_yumpkg.py | 87 +- - tests/pytests/unit/modules/test_zypperpkg.py | 66 +- - tests/pytests/unit/states/test_pkgrepo.py | 448 +++++ - tests/unit/modules/test_rpm_lowpkg.py | 232 ++- - tests/unit/modules/test_yumpkg.py | 1754 ------------------ - tests/unit/modules/test_zypperpkg.py | 44 +- - 12 files changed, 1399 insertions(+), 1775 deletions(-) - create mode 100644 changelog/58782.added - delete mode 100644 tests/unit/modules/test_yumpkg.py + salt/modules/aptpkg.py | 7 +- + salt/modules/rpm_lowpkg.py | 151 +++++++ + salt/modules/yumpkg.py | 88 ++++ + salt/modules/zypperpkg.py | 88 ++++ + salt/states/pkgrepo.py | 207 +++++++++ + tests/pytests/unit/modules/test_yumpkg.py | 44 +- + tests/pytests/unit/modules/test_zypperpkg.py | 45 +- + tests/pytests/unit/states/test_pkgrepo.py | 448 +++++++++++++++++++ + 8 files changed, 1073 insertions(+), 5 deletions(-) -diff --git a/changelog/58782.added b/changelog/58782.added -new file mode 100644 -index 0000000000..f9e69f64f2 ---- /dev/null -+++ b/changelog/58782.added -@@ -0,0 +1 @@ -+Add GPG key functions in "lowpkg" and a "migrated" function in the "pkgrepo" state for repository and GPG key migration. -\ No newline at end of file diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py -index 692d99f97e..1d9557b497 100644 +index 0d378355ab..558033c931 100644 --- a/salt/modules/aptpkg.py +++ b/salt/modules/aptpkg.py -@@ -1918,7 +1918,7 @@ def _convert_if_int(value): +@@ -2036,7 +2036,7 @@ def _convert_if_int(value): return value @@ -57,7 +43,7 @@ index 692d99f97e..1d9557b497 100644 """ .. versionadded:: 2017.7.0 -@@ -2000,7 +2000,9 @@ def get_repo_keys(): +@@ -2118,7 +2118,9 @@ def get_repo_keys(): return ret @@ -68,7 +54,7 @@ index 692d99f97e..1d9557b497 100644 """ .. versionadded:: 2017.7.0 -@@ -2026,7 +2028,6 @@ def add_repo_key(path=None, text=None, keyserver=None, keyid=None, saltenv="base +@@ -2144,7 +2146,6 @@ def add_repo_key(path=None, text=None, keyserver=None, keyid=None, saltenv="base salt '*' pkg.add_repo_key keyserver='keyserver.example' keyid='0000AAAA' """ cmd = ["apt-key"] @@ -77,10 +63,10 @@ index 692d99f97e..1d9557b497 100644 current_repo_keys = get_repo_keys() diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py -index f610ca412c..370bd5b728 100644 +index d65a46a703..c8e984c021 100644 --- a/salt/modules/rpm_lowpkg.py +++ b/salt/modules/rpm_lowpkg.py -@@ -828,3 +828,154 @@ def checksum(*paths, **kwargs): +@@ -859,3 +859,154 @@ def checksum(*paths, **kwargs): ) return ret @@ -236,18 +222,10 @@ index f610ca412c..370bd5b728 100644 + cmd.extend(["-e", key]) + return __salt__["cmd.retcode"](cmd) == 0 diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py -index 77cd14aaf2..fd79109e40 100644 +index 8d089c6aa4..9737508377 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py -@@ -3391,7 +3391,6 @@ def services_need_restart(**kwargs): - - Requires systemd. - -- - CLI Examples: - - .. code-block:: bash -@@ -3417,3 +3416,91 @@ def services_need_restart(**kwargs): +@@ -3471,3 +3471,91 @@ def services_need_restart(**kwargs): services.add(service) return list(services) @@ -340,10 +318,10 @@ index 77cd14aaf2..fd79109e40 100644 + """ + return __salt__["lowpkg.remove_gpg_key"](keyid, root) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py -index 7216e25b86..863be3c894 100644 +index 43c4e91109..4fc045c313 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py -@@ -3125,3 +3125,91 @@ def services_need_restart(root=None, **kwargs): +@@ -3134,3 +3134,91 @@ def services_need_restart(root=None, **kwargs): services = zypper_output.split() return services @@ -436,7 +414,7 @@ index 7216e25b86..863be3c894 100644 + """ + return __salt__["lowpkg.remove_gpg_key"](keyid, root) diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py -index c8c75e3244..e3d7f7084e 100644 +index f395dec1ed..00d3cbfcd8 100644 --- a/salt/states/pkgrepo.py +++ b/salt/states/pkgrepo.py @@ -85,6 +85,7 @@ package managers are APT, DNF, YUM and Zypper. Here is some example SLS: @@ -658,19 +636,19 @@ index c8c75e3244..e3d7f7084e 100644 + + return ret diff --git a/tests/pytests/unit/modules/test_yumpkg.py b/tests/pytests/unit/modules/test_yumpkg.py -index 66c86972a0..ef7100fe9d 100644 +index ea8135bcef..475e1d6094 100644 --- a/tests/pytests/unit/modules/test_yumpkg.py +++ b/tests/pytests/unit/modules/test_yumpkg.py @@ -7,7 +7,7 @@ import salt.modules.rpm_lowpkg as rpm import salt.modules.yumpkg as yumpkg import salt.utils.platform from salt.exceptions import CommandExecutionError, SaltInvocationError --from tests.support.mock import MagicMock, Mock, patch +-from tests.support.mock import MagicMock, Mock, call, patch +from tests.support.mock import MagicMock, Mock, call, mock_open, patch try: import pytest -@@ -1683,6 +1683,91 @@ def test_get_repo_with_non_existent_repo(list_repos_var): +@@ -1849,6 +1849,48 @@ def test_get_repo_with_non_existent_repo(list_repos_var): assert ret == expected, ret @@ -716,54 +694,11 @@ index 66c86972a0..ef7100fe9d 100644 + salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") + + -+def test_pkg_update_dnf(): -+ """ -+ Tests that the proper CLI options are added when obsoletes=False -+ """ -+ name = "foo" -+ old = "1.2.2-1.fc31" -+ new = "1.2.3-1.fc31" -+ cmd_mock = MagicMock(return_value={"retcode": 0}) -+ list_pkgs_mock = MagicMock(side_effect=[{name: old}, {name: new}]) -+ parse_targets_mock = MagicMock(return_value=({"foo": None}, "repository")) -+ with patch.dict( -+ yumpkg.__salt__, -+ {"cmd.run_all": cmd_mock, "pkg_resource.parse_targets": parse_targets_mock}, -+ ), patch.object(yumpkg, "refresh_db", MagicMock()), patch.object( -+ yumpkg, "list_pkgs", list_pkgs_mock -+ ), patch.object( -+ yumpkg, "_yum", MagicMock(return_value="dnf") -+ ), patch( -+ "salt.utils.systemd.has_scope", MagicMock(return_value=False) -+ ): -+ ret = yumpkg.update(name, setopt="obsoletes=0,plugins=0") -+ expected = {name: {"old": old, "new": new}} -+ assert ret == expected, ret -+ -+ cmd_mock.assert_called_once_with( -+ [ -+ "dnf", -+ "--quiet", -+ "-y", -+ "--setopt", -+ "obsoletes=0", -+ "--setopt", -+ "plugins=0", -+ "--obsoletes=False", -+ "upgrade", -+ "foo", -+ ], -+ env={}, -+ output_loglevel="trace", -+ python_shell=False, -+ ) -+ -+ - def test_call_yum_default(): + def test_pkg_update_dnf(): """ - Call default Yum/Dnf. + Tests that the proper CLI options are added when obsoletes=False diff --git a/tests/pytests/unit/modules/test_zypperpkg.py b/tests/pytests/unit/modules/test_zypperpkg.py -index aece43ea29..37bbef87b7 100644 +index 4a0055e11c..eb1e63f6d7 100644 --- a/tests/pytests/unit/modules/test_zypperpkg.py +++ b/tests/pytests/unit/modules/test_zypperpkg.py @@ -8,7 +8,8 @@ import os @@ -776,31 +711,10 @@ index aece43ea29..37bbef87b7 100644 @pytest.fixture -@@ -55,3 +56,66 @@ def test_list_pkgs_no_context(): - pkgs = zypper.list_pkgs(versions_as_list=True, use_context=False) - list_pkgs_context_mock.assert_not_called() - list_pkgs_context_mock.reset_mock() -+ -+ -+def test_normalize_name(): -+ """ -+ Test that package is normalized only when it should be -+ """ -+ with patch.dict(zypper.__grains__, {"osarch": "x86_64"}): -+ result = zypper.normalize_name("foo") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.x86_64") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.noarch") -+ assert result == "foo", result -+ -+ with patch.dict(zypper.__grains__, {"osarch": "aarch64"}): -+ result = zypper.normalize_name("foo") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.aarch64") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.noarch") -+ assert result == "foo", result +@@ -78,3 +79,45 @@ def test_normalize_name(): + assert result == "foo", result + result = zypper.normalize_name("foo.noarch") + assert result == "foo", result + + +def test_get_repo_keys(): @@ -1299,2074 +1213,7 @@ index daa913bcc2..cbb12cfb9b 100644 + }, + "comment": "There are keys or repositories to migrate or drop", + } -diff --git a/tests/unit/modules/test_rpm_lowpkg.py b/tests/unit/modules/test_rpm_lowpkg.py -index e7e8230510..280a19b911 100644 ---- a/tests/unit/modules/test_rpm_lowpkg.py -+++ b/tests/unit/modules/test_rpm_lowpkg.py -@@ -5,6 +5,7 @@ - - # Import Python Libs - from __future__ import absolute_import -+import datetime - - # Import Salt Libs - import salt.modules.rpm_lowpkg as rpm -@@ -255,14 +256,223 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - - :return: - """ -- self.assertEqual(-1, rpm.version_cmp("1", "2")) -- self.assertEqual(mock_version_cmp.called, True) -- self.assertEqual(mock_log.warning.called, True) -- self.assertEqual( -- mock_log.warning.mock_calls[0][1][0], -- "Please install a package that provides rpm.labelCompare for more accurate version comparisons.", -- ) -- self.assertEqual( -- mock_log.warning.mock_calls[1][1][0], -- "Falling back on salt.utils.versions.version_cmp() for version comparisons", -- ) -+ with patch( -+ "salt.modules.rpm_lowpkg.rpm.labelCompare", MagicMock(return_value=0) -+ ), patch("salt.modules.rpm_lowpkg.HAS_RPM", False): -+ self.assertEqual( -+ -1, rpm.version_cmp("1", "2") -+ ) # mock returns -1, a python implementation was called -+ -+ def test_list_gpg_keys_no_info(self): -+ """ -+ Test list_gpg_keys with no extra information -+ """ -+ mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ self.assertEqual(rpm.list_gpg_keys(), ["gpg-pubkey-1", "gpg-pubkey-2"]) -+ self.assertFalse(_called_with_root(mock)) -+ -+ def test_list_gpg_keys_no_info_root(self): -+ """ -+ Test list_gpg_keys with no extra information and root -+ """ -+ mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ self.assertEqual( -+ rpm.list_gpg_keys(root="/mnt"), ["gpg-pubkey-1", "gpg-pubkey-2"] -+ ) -+ self.assertTrue(_called_with_root(mock)) -+ -+ @patch("salt.modules.rpm_lowpkg.info_gpg_key") -+ def test_list_gpg_keys_info(self, info_gpg_key): -+ """ -+ Test list_gpg_keys with extra information -+ """ -+ info_gpg_key.side_effect = lambda x, root: { -+ "Description": "key for {}".format(x) -+ } -+ mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ self.assertEqual( -+ rpm.list_gpg_keys(info=True), -+ { -+ "gpg-pubkey-1": {"Description": "key for gpg-pubkey-1"}, -+ "gpg-pubkey-2": {"Description": "key for gpg-pubkey-2"}, -+ }, -+ ) -+ self.assertFalse(_called_with_root(mock)) -+ -+ def test_info_gpg_key(self): -+ """ -+ Test info_gpg_keys from a normal output -+ """ -+ info = """Name : gpg-pubkey -+Version : 3dbdc284 -+Release : 53674dd4 -+Architecture: (none) -+Install Date: Fri 08 Mar 2019 11:57:44 AM UTC -+Group : Public Keys -+Size : 0 -+License : pubkey -+Signature : (none) -+Source RPM : (none) -+Build Date : Mon 05 May 2014 10:37:40 AM UTC -+Build Host : localhost -+Packager : openSUSE Project Signing Key -+Summary : gpg(openSUSE Project Signing Key ) -+Description : -+-----BEGIN PGP PUBLIC KEY BLOCK----- -+Version: rpm-4.14.2.1 (NSS-3) -+ -+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G -+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ -+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO -+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig -+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD -+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl -+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC -+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C -+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI -+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha -+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr -+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk -+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a -+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y -+=i2TA -+-----END PGP PUBLIC KEY BLOCK----- -+ -+""" -+ mock = MagicMock(return_value=info) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ self.assertEqual( -+ rpm.info_gpg_key("key"), -+ { -+ "Name": "gpg-pubkey", -+ "Version": "3dbdc284", -+ "Release": "53674dd4", -+ "Architecture": None, -+ "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44), -+ "Group": "Public Keys", -+ "Size": 0, -+ "License": "pubkey", -+ "Signature": None, -+ "Source RPM": None, -+ "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40), -+ "Build Host": "localhost", -+ "Packager": "openSUSE Project Signing Key ", -+ "Summary": "gpg(openSUSE Project Signing Key )", -+ "Description": """-----BEGIN PGP PUBLIC KEY BLOCK----- -+Version: rpm-4.14.2.1 (NSS-3) -+ -+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G -+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ -+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO -+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig -+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD -+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl -+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC -+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C -+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI -+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha -+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr -+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk -+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a -+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y -+=i2TA -+-----END PGP PUBLIC KEY BLOCK-----""", -+ }, -+ ) -+ self.assertFalse(_called_with_root(mock)) -+ -+ def test_info_gpg_key_extended(self): -+ """ -+ Test info_gpg_keys from an extended output -+ """ -+ info = """Name : gpg-pubkey -+Version : 3dbdc284 -+Release : 53674dd4 -+Architecture: (none) -+Install Date: Fri 08 Mar 2019 11:57:44 AM UTC -+Group : Public Keys -+Size : 0 -+License : pubkey -+Signature : (none) -+Source RPM : (none) -+Build Date : Mon 05 May 2014 10:37:40 AM UTC -+Build Host : localhost -+Packager : openSUSE Project Signing Key -+Summary : gpg(openSUSE Project Signing Key ) -+Description : -+-----BEGIN PGP PUBLIC KEY BLOCK----- -+Version: rpm-4.14.2.1 (NSS-3) -+ -+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G -+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ -+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO -+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig -+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD -+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl -+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC -+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C -+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI -+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha -+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr -+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk -+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a -+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y -+=i2TA -+-----END PGP PUBLIC KEY BLOCK----- -+ -+Distribution: (none) -+""" -+ mock = MagicMock(return_value=info) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ self.assertEqual( -+ rpm.info_gpg_key("key"), -+ { -+ "Name": "gpg-pubkey", -+ "Version": "3dbdc284", -+ "Release": "53674dd4", -+ "Architecture": None, -+ "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44), -+ "Group": "Public Keys", -+ "Size": 0, -+ "License": "pubkey", -+ "Signature": None, -+ "Source RPM": None, -+ "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40), -+ "Build Host": "localhost", -+ "Packager": "openSUSE Project Signing Key ", -+ "Summary": "gpg(openSUSE Project Signing Key )", -+ "Description": """-----BEGIN PGP PUBLIC KEY BLOCK----- -+Version: rpm-4.14.2.1 (NSS-3) -+ -+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G -+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ -+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO -+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig -+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD -+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl -+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC -+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C -+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI -+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha -+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr -+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk -+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a -+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y -+=i2TA -+-----END PGP PUBLIC KEY BLOCK-----""", -+ "Distribution": None, -+ }, -+ ) -+ self.assertFalse(_called_with_root(mock)) -+ -+ def test_remove_gpg_key(self): -+ """ -+ Test remove_gpg_key -+ """ -+ mock = MagicMock(return_value=0) -+ with patch.dict(rpm.__salt__, {"cmd.retcode": mock}): -+ self.assertTrue(rpm.remove_gpg_key("gpg-pubkey-1")) -+ self.assertFalse(_called_with_root(mock)) -diff --git a/tests/unit/modules/test_yumpkg.py b/tests/unit/modules/test_yumpkg.py -deleted file mode 100644 -index fd57faad32..0000000000 ---- a/tests/unit/modules/test_yumpkg.py -+++ /dev/null -@@ -1,1754 +0,0 @@ --import os -- --import salt.modules.cmdmod as cmdmod --import salt.modules.pkg_resource as pkg_resource --import salt.modules.rpm_lowpkg as rpm --import salt.modules.yumpkg as yumpkg --import salt.utils.platform --from salt.exceptions import CommandExecutionError, SaltInvocationError --from tests.support.mixins import LoaderModuleMockMixin --from tests.support.mock import MagicMock, Mock, call, patch --from tests.support.unit import TestCase, skipIf -- --try: -- import pytest --except ImportError: -- pytest = None -- --LIST_REPOS = { -- "base": { -- "file": "/etc/yum.repos.d/CentOS-Base.repo", -- "gpgcheck": "1", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "mirrorlist": "http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra", -- "name": "CentOS-$releasever - Base", -- }, -- "base-source": { -- "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", -- "enabled": "0", -- "file": "/etc/yum.repos.d/CentOS-Sources.repo", -- "gpgcheck": "1", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "name": "CentOS-$releasever - Base Sources", -- }, -- "updates": { -- "file": "/etc/yum.repos.d/CentOS-Base.repo", -- "gpgcheck": "1", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "mirrorlist": "http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra", -- "name": "CentOS-$releasever - Updates", -- }, -- "updates-source": { -- "baseurl": "http://vault.centos.org/centos/$releasever/updates/Source/", -- "enabled": "0", -- "file": "/etc/yum.repos.d/CentOS-Sources.repo", -- "gpgcheck": "1", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "name": "CentOS-$releasever - Updates Sources", -- }, --} -- -- --class YumTestCase(TestCase, LoaderModuleMockMixin): -- """ -- Test cases for salt.modules.yumpkg -- """ -- -- def setup_loader_modules(self): -- return { -- yumpkg: { -- "__context__": {"yum_bin": "yum"}, -- "__grains__": { -- "osarch": "x86_64", -- "os": "CentOS", -- "os_family": "RedHat", -- "osmajorrelease": 7, -- }, -- }, -- pkg_resource: {}, -- } -- -- def test_list_pkgs(self): -- """ -- Test packages listing. -- -- :return: -- """ -- -- def _add_data(data, key, value): -- data.setdefault(key, []).append(value) -- -- rpm_out = [ -- "python-urlgrabber_|-(none)_|-3.10_|-8.el7_|-noarch_|-(none)_|-1487838471", -- "alsa-lib_|-(none)_|-1.1.1_|-1.el7_|-x86_64_|-(none)_|-1487838475", -- "gnupg2_|-(none)_|-2.0.22_|-4.el7_|-x86_64_|-(none)_|-1487838477", -- "rpm-python_|-(none)_|-4.11.3_|-21.el7_|-x86_64_|-(none)_|-1487838477", -- "pygpgme_|-(none)_|-0.3_|-9.el7_|-x86_64_|-(none)_|-1487838478", -- "yum_|-(none)_|-3.4.3_|-150.el7.centos_|-noarch_|-(none)_|-1487838479", -- "lzo_|-(none)_|-2.06_|-8.el7_|-x86_64_|-(none)_|-1487838479", -- "qrencode-libs_|-(none)_|-3.4.1_|-3.el7_|-x86_64_|-(none)_|-1487838480", -- "ustr_|-(none)_|-1.0.4_|-16.el7_|-x86_64_|-(none)_|-1487838480", -- "shadow-utils_|-2_|-4.1.5.1_|-24.el7_|-x86_64_|-(none)_|-1487838481", -- "util-linux_|-(none)_|-2.23.2_|-33.el7_|-x86_64_|-(none)_|-1487838484", -- "openssh_|-(none)_|-6.6.1p1_|-33.el7_3_|-x86_64_|-(none)_|-1487838485", -- "virt-what_|-(none)_|-1.13_|-8.el7_|-x86_64_|-(none)_|-1487838486", -- ] -- with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( -- yumpkg.__salt__, -- {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, -- ), patch.dict(yumpkg.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( -- yumpkg.__salt__, -- {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, -- ), patch.dict( -- yumpkg.__salt__, {"pkg_resource.stringify": MagicMock()} -- ), patch.dict( -- pkg_resource.__salt__, {"pkg.parse_arch": yumpkg.parse_arch} -- ): -- pkgs = yumpkg.list_pkgs(versions_as_list=True) -- for pkg_name, pkg_version in { -- "python-urlgrabber": "3.10-8.el7", -- "alsa-lib": "1.1.1-1.el7", -- "gnupg2": "2.0.22-4.el7", -- "rpm-python": "4.11.3-21.el7", -- "pygpgme": "0.3-9.el7", -- "yum": "3.4.3-150.el7.centos", -- "lzo": "2.06-8.el7", -- "qrencode-libs": "3.4.1-3.el7", -- "ustr": "1.0.4-16.el7", -- "shadow-utils": "2:4.1.5.1-24.el7", -- "util-linux": "2.23.2-33.el7", -- "openssh": "6.6.1p1-33.el7_3", -- "virt-what": "1.13-8.el7", -- }.items(): -- self.assertTrue(pkgs.get(pkg_name)) -- self.assertEqual(pkgs[pkg_name], [pkg_version]) -- -- def test_list_pkgs_with_attr(self): -- """ -- Test packages listing with the attr parameter -- -- :return: -- """ -- -- def _add_data(data, key, value): -- data.setdefault(key, []).append(value) -- -- rpm_out = [ -- "python-urlgrabber_|-(none)_|-3.10_|-8.el7_|-noarch_|-(none)_|-1487838471", -- "alsa-lib_|-(none)_|-1.1.1_|-1.el7_|-x86_64_|-(none)_|-1487838475", -- "gnupg2_|-(none)_|-2.0.22_|-4.el7_|-x86_64_|-(none)_|-1487838477", -- "rpm-python_|-(none)_|-4.11.3_|-21.el7_|-x86_64_|-(none)_|-1487838477", -- "pygpgme_|-(none)_|-0.3_|-9.el7_|-x86_64_|-(none)_|-1487838478", -- "yum_|-(none)_|-3.4.3_|-150.el7.centos_|-noarch_|-(none)_|-1487838479", -- "lzo_|-(none)_|-2.06_|-8.el7_|-x86_64_|-(none)_|-1487838479", -- "qrencode-libs_|-(none)_|-3.4.1_|-3.el7_|-x86_64_|-(none)_|-1487838480", -- "ustr_|-(none)_|-1.0.4_|-16.el7_|-x86_64_|-(none)_|-1487838480", -- "shadow-utils_|-2_|-4.1.5.1_|-24.el7_|-x86_64_|-(none)_|-1487838481", -- "util-linux_|-(none)_|-2.23.2_|-33.el7_|-x86_64_|-(none)_|-1487838484", -- "openssh_|-(none)_|-6.6.1p1_|-33.el7_3_|-x86_64_|-(none)_|-1487838485", -- "virt-what_|-(none)_|-1.13_|-8.el7_|-x86_64_|-(none)_|-1487838486", -- ] -- with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( -- yumpkg.__salt__, -- {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, -- ), patch.dict(yumpkg.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( -- yumpkg.__salt__, -- {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, -- ), patch.dict( -- yumpkg.__salt__, {"pkg_resource.stringify": MagicMock()} -- ), patch.dict( -- pkg_resource.__salt__, {"pkg.parse_arch": yumpkg.parse_arch} -- ): -- pkgs = yumpkg.list_pkgs( -- attr=["epoch", "release", "arch", "install_date_time_t"] -- ) -- for pkg_name, pkg_attr in { -- "python-urlgrabber": { -- "version": "3.10", -- "release": "8.el7", -- "arch": "noarch", -- "install_date_time_t": 1487838471, -- "epoch": None, -- }, -- "alsa-lib": { -- "version": "1.1.1", -- "release": "1.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838475, -- "epoch": None, -- }, -- "gnupg2": { -- "version": "2.0.22", -- "release": "4.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838477, -- "epoch": None, -- }, -- "rpm-python": { -- "version": "4.11.3", -- "release": "21.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838477, -- "epoch": None, -- }, -- "pygpgme": { -- "version": "0.3", -- "release": "9.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838478, -- "epoch": None, -- }, -- "yum": { -- "version": "3.4.3", -- "release": "150.el7.centos", -- "arch": "noarch", -- "install_date_time_t": 1487838479, -- "epoch": None, -- }, -- "lzo": { -- "version": "2.06", -- "release": "8.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838479, -- "epoch": None, -- }, -- "qrencode-libs": { -- "version": "3.4.1", -- "release": "3.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838480, -- "epoch": None, -- }, -- "ustr": { -- "version": "1.0.4", -- "release": "16.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838480, -- "epoch": None, -- }, -- "shadow-utils": { -- "epoch": "2", -- "version": "4.1.5.1", -- "release": "24.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838481, -- }, -- "util-linux": { -- "version": "2.23.2", -- "release": "33.el7", -- "arch": "x86_64", -- "install_date_time_t": 1487838484, -- "epoch": None, -- }, -- "openssh": { -- "version": "6.6.1p1", -- "release": "33.el7_3", -- "arch": "x86_64", -- "install_date_time_t": 1487838485, -- "epoch": None, -- }, -- "virt-what": { -- "version": "1.13", -- "release": "8.el7", -- "install_date_time_t": 1487838486, -- "arch": "x86_64", -- "epoch": None, -- }, -- }.items(): -- -- self.assertTrue(pkgs.get(pkg_name)) -- self.assertEqual(pkgs[pkg_name], [pkg_attr]) -- -- def test_list_pkgs_with_attr_multiple_versions(self): -- """ -- Test packages listing with the attr parameter reporting multiple version installed -- -- :return: -- """ -- -- def _add_data(data, key, value): -- data.setdefault(key, []).append(value) -- -- rpm_out = [ -- "glibc_|-(none)_|-2.12_|-1.212.el6_|-i686_|-(none)_|-1542394210" -- "glibc_|-(none)_|-2.12_|-1.212.el6_|-x86_64_|-(none)_|-1542394204", -- "virt-what_|-(none)_|-1.13_|-8.el7_|-x86_64_|-(none)_|-1487838486", -- "virt-what_|-(none)_|-1.10_|-2.el7_|-x86_64_|-(none)_|-1387838486", -- ] -- with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( -- yumpkg.__salt__, -- {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, -- ), patch.dict(yumpkg.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( -- yumpkg.__salt__, -- {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, -- ), patch.dict( -- yumpkg.__salt__, {"pkg_resource.stringify": MagicMock()} -- ), patch.dict( -- pkg_resource.__salt__, {"pkg.parse_arch": yumpkg.parse_arch} -- ): -- pkgs = yumpkg.list_pkgs( -- attr=["epoch", "release", "arch", "install_date_time_t"] -- ) -- expected_pkg_list = { -- "glibc": [ -- { -- "version": "2.12", -- "release": "1.212.el6", -- "install_date_time_t": 1542394210, -- "arch": "i686", -- "epoch": None, -- }, -- { -- "version": "2.12", -- "release": "1.212.el6", -- "install_date_time_t": 1542394204, -- "arch": "x86_64", -- "epoch": None, -- }, -- ], -- "virt-what": [ -- { -- "version": "1.10", -- "release": "2.el7", -- "install_date_time_t": 1387838486, -- "arch": "x86_64", -- "epoch": None, -- }, -- { -- "version": "1.13", -- "release": "8.el7", -- "install_date_time_t": 1487838486, -- "arch": "x86_64", -- "epoch": None, -- }, -- ], -- } -- for pkgname, pkginfo in pkgs.items(): -- self.assertCountEqual(pkginfo, expected_pkg_list[pkgname]) -- -- def test_list_patches(self): -- """ -- Test patches listing. -- -- :return: -- """ -- yum_out = [ -- "i my-fake-patch-not-installed-1234 recommended spacewalk-usix-2.7.5.2-2.2.noarch", -- " my-fake-patch-not-installed-1234 recommended spacewalksd-5.0.26.2-21.2.x86_64", -- "i my-fake-patch-not-installed-1234 recommended suseRegisterInfo-3.1.1-18.2.x86_64", -- "i my-fake-patch-installed-1234 recommended my-package-one-1.1-0.1.x86_64", -- "i my-fake-patch-installed-1234 recommended my-package-two-1.1-0.1.x86_64", -- ] -- -- expected_patches = { -- "my-fake-patch-not-installed-1234": { -- "installed": False, -- "summary": [ -- "spacewalk-usix-2.7.5.2-2.2.noarch", -- "spacewalksd-5.0.26.2-21.2.x86_64", -- "suseRegisterInfo-3.1.1-18.2.x86_64", -- ], -- }, -- "my-fake-patch-installed-1234": { -- "installed": True, -- "summary": [ -- "my-package-one-1.1-0.1.x86_64", -- "my-package-two-1.1-0.1.x86_64", -- ], -- }, -- } -- -- with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( -- yumpkg.__salt__, -- {"cmd.run_stdout": MagicMock(return_value=os.linesep.join(yum_out))}, -- ): -- patches = yumpkg.list_patches() -- self.assertFalse(patches["my-fake-patch-not-installed-1234"]["installed"]) -- self.assertTrue( -- len(patches["my-fake-patch-not-installed-1234"]["summary"]) == 3 -- ) -- for _patch in expected_patches["my-fake-patch-not-installed-1234"][ -- "summary" -- ]: -- self.assertTrue( -- _patch in patches["my-fake-patch-not-installed-1234"]["summary"] -- ) -- -- self.assertTrue(patches["my-fake-patch-installed-1234"]["installed"]) -- self.assertTrue( -- len(patches["my-fake-patch-installed-1234"]["summary"]) == 2 -- ) -- for _patch in expected_patches["my-fake-patch-installed-1234"]["summary"]: -- self.assertTrue( -- _patch in patches["my-fake-patch-installed-1234"]["summary"] -- ) -- -- def test_latest_version_with_options(self): -- with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})): -- -- # with fromrepo -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.latest_version( -- "foo", refresh=False, fromrepo="good", branch="foo" -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "available", -- "foo", -- ], -- env={}, -- ignore_retcode=True, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- # without fromrepo -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.latest_version( -- "foo", -- refresh=False, -- enablerepo="good", -- disablerepo="bad", -- branch="foo", -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "available", -- "foo", -- ], -- env={}, -- ignore_retcode=True, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- # without fromrepo, but within the scope -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch("salt.utils.systemd.has_scope", MagicMock(return_value=True)): -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=True)}, -- ): -- yumpkg.latest_version( -- "foo", -- refresh=False, -- enablerepo="good", -- disablerepo="bad", -- branch="foo", -- ) -- cmd.assert_called_once_with( -- [ -- "systemd-run", -- "--scope", -- "yum", -- "--quiet", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "available", -- "foo", -- ], -- env={}, -- ignore_retcode=True, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- def test_list_repo_pkgs_with_options(self): -- """ -- Test list_repo_pkgs with and without fromrepo -- -- NOTE: mock_calls is a stack. The most recent call is indexed -- with 0, while the first call would have the highest index. -- """ -- really_old_yum = MagicMock(return_value="3.2.0") -- older_yum = MagicMock(return_value="3.4.0") -- newer_yum = MagicMock(return_value="3.4.5") -- list_repos_mock = MagicMock(return_value=LIST_REPOS) -- kwargs = { -- "output_loglevel": "trace", -- "ignore_retcode": True, -- "python_shell": False, -- "env": {}, -- } -- -- with patch.object(yumpkg, "list_repos", list_repos_mock): -- -- # Test with really old yum. The fromrepo argument has no effect on -- # the yum commands we'd run. -- with patch.dict(yumpkg.__salt__, {"cmd.run": really_old_yum}): -- -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_repo_pkgs("foo") -- # We should have called cmd.run_all twice -- assert len(cmd.mock_calls) == 2 -- -- # Check args from first call -- assert cmd.mock_calls[1][1] == ( -- ["yum", "--quiet", "list", "available"], -- ) -- -- # Check kwargs from first call -- assert cmd.mock_calls[1][2] == kwargs -- -- # Check args from second call -- assert cmd.mock_calls[0][1] == ( -- ["yum", "--quiet", "list", "installed"], -- ) -- -- # Check kwargs from second call -- assert cmd.mock_calls[0][2] == kwargs -- -- # Test with really old yum. The fromrepo argument has no effect on -- # the yum commands we'd run. -- with patch.dict(yumpkg.__salt__, {"cmd.run": older_yum}): -- -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_repo_pkgs("foo") -- # We should have called cmd.run_all twice -- assert len(cmd.mock_calls) == 2 -- -- # Check args from first call -- assert cmd.mock_calls[1][1] == ( -- ["yum", "--quiet", "--showduplicates", "list", "available"], -- ) -- -- # Check kwargs from first call -- assert cmd.mock_calls[1][2] == kwargs -- -- # Check args from second call -- assert cmd.mock_calls[0][1] == ( -- ["yum", "--quiet", "--showduplicates", "list", "installed"], -- ) -- -- # Check kwargs from second call -- assert cmd.mock_calls[0][2] == kwargs -- -- # Test with newer yum. We should run one yum command per repo, so -- # fromrepo would limit how many calls we make. -- with patch.dict(yumpkg.__salt__, {"cmd.run": newer_yum}): -- -- # When fromrepo is used, we would only run one yum command, for -- # that specific repo. -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_repo_pkgs("foo", fromrepo="base") -- # We should have called cmd.run_all once -- assert len(cmd.mock_calls) == 1 -- -- # Check args -- assert cmd.mock_calls[0][1] == ( -- [ -- "yum", -- "--quiet", -- "--showduplicates", -- "repository-packages", -- "base", -- "list", -- "foo", -- ], -- ) -- # Check kwargs -- assert cmd.mock_calls[0][2] == kwargs -- -- # Test enabling base-source and disabling updates. We should -- # get two calls, one for each enabled repo. Because dict -- # iteration order will vary, different Python versions will be -- # do them in different orders, which is OK, but it will just -- # mean that we will have to check both the first and second -- # mock call both times. -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_repo_pkgs( -- "foo", enablerepo="base-source", disablerepo="updates" -- ) -- # We should have called cmd.run_all twice -- assert len(cmd.mock_calls) == 2 -- -- for repo in ("base", "base-source"): -- for index in (0, 1): -- try: -- # Check args -- assert cmd.mock_calls[index][1] == ( -- [ -- "yum", -- "--quiet", -- "--showduplicates", -- "repository-packages", -- repo, -- "list", -- "foo", -- ], -- ) -- # Check kwargs -- assert cmd.mock_calls[index][2] == kwargs -- break -- except AssertionError: -- continue -- else: -- self.fail("repo '{}' not checked".format(repo)) -- -- def test_list_upgrades_dnf(self): -- """ -- The subcommand should be "upgrades" with dnf -- """ -- with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}): -- # with fromrepo -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_upgrades(refresh=False, fromrepo="good", branch="foo") -- cmd.assert_called_once_with( -- [ -- "dnf", -- "--quiet", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "upgrades", -- ], -- env={}, -- output_loglevel="trace", -- ignore_retcode=True, -- python_shell=False, -- ) -- -- # without fromrepo -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_upgrades( -- refresh=False, enablerepo="good", disablerepo="bad", branch="foo" -- ) -- cmd.assert_called_once_with( -- [ -- "dnf", -- "--quiet", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "upgrades", -- ], -- env={}, -- output_loglevel="trace", -- ignore_retcode=True, -- python_shell=False, -- ) -- -- def test_list_upgrades_yum(self): -- """ -- The subcommand should be "updates" with yum -- """ -- # with fromrepo -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_upgrades(refresh=False, fromrepo="good", branch="foo") -- cmd.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "updates", -- ], -- env={}, -- output_loglevel="trace", -- ignore_retcode=True, -- python_shell=False, -- ) -- -- # without fromrepo -- cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.list_upgrades( -- refresh=False, enablerepo="good", disablerepo="bad", branch="foo" -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- "list", -- "updates", -- ], -- env={}, -- output_loglevel="trace", -- ignore_retcode=True, -- python_shell=False, -- ) -- -- def test_refresh_db_with_options(self): -- -- with patch("salt.utils.pkg.clear_rtag", Mock()): -- -- # With check_update=True we will do a cmd.run to run the clean_cmd, and -- # then a separate cmd.retcode to check for updates. -- -- # with fromrepo -- yum_call = MagicMock() -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.refresh_db(check_update=True, fromrepo="good", branch="foo") -- -- assert yum_call.call_count == 2 -- yum_call.assert_any_call( -- [ -- "yum", -- "--quiet", -- "--assumeyes", -- "clean", -- "expire-cache", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- ], -- env={}, -- ignore_retcode=True, -- output_loglevel="trace", -- python_shell=False, -- ) -- yum_call.assert_any_call( -- [ -- "yum", -- "--quiet", -- "--assumeyes", -- "check-update", -- "--setopt=autocheck_running_kernel=false", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- ], -- output_loglevel="trace", -- env={}, -- ignore_retcode=True, -- python_shell=False, -- ) -- -- # without fromrepo -- yum_call = MagicMock() -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.refresh_db( -- check_update=True, -- enablerepo="good", -- disablerepo="bad", -- branch="foo", -- ) -- assert yum_call.call_count == 2 -- yum_call.assert_any_call( -- [ -- "yum", -- "--quiet", -- "--assumeyes", -- "clean", -- "expire-cache", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- ], -- env={}, -- ignore_retcode=True, -- output_loglevel="trace", -- python_shell=False, -- ) -- yum_call.assert_any_call( -- [ -- "yum", -- "--quiet", -- "--assumeyes", -- "check-update", -- "--setopt=autocheck_running_kernel=false", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- ], -- output_loglevel="trace", -- env={}, -- ignore_retcode=True, -- python_shell=False, -- ) -- -- # With check_update=False we will just do a cmd.run for the clean_cmd -- -- # with fromrepo -- yum_call = MagicMock() -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.refresh_db(check_update=False, fromrepo="good", branch="foo") -- assert yum_call.call_count == 1 -- yum_call.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "--assumeyes", -- "clean", -- "expire-cache", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- ], -- env={}, -- output_loglevel="trace", -- ignore_retcode=True, -- python_shell=False, -- ) -- -- # without fromrepo -- yum_call = MagicMock() -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg.refresh_db( -- check_update=False, -- enablerepo="good", -- disablerepo="bad", -- branch="foo", -- ) -- assert yum_call.call_count == 1 -- yum_call.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "--assumeyes", -- "clean", -- "expire-cache", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- ], -- env={}, -- output_loglevel="trace", -- ignore_retcode=True, -- python_shell=False, -- ) -- -- def test_install_with_options(self): -- parse_targets = MagicMock(return_value=({"foo": None}, "repository")) -- with patch.object( -- yumpkg, "list_pkgs", MagicMock(return_value={}) -- ), patch.object(yumpkg, "list_holds", MagicMock(return_value=[])), patch.dict( -- yumpkg.__salt__, {"pkg_resource.parse_targets": parse_targets} -- ), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- -- # with fromrepo -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): -- yumpkg.install( -- refresh=False, -- fromrepo="good", -- branch="foo", -- setopt="obsoletes=0,plugins=0", -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "-y", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- "--setopt", -- "obsoletes=0", -- "--setopt", -- "plugins=0", -- "install", -- "foo", -- ], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ignore_retcode=False, -- redirect_stderr=True, -- ) -- -- # without fromrepo -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): -- yumpkg.install( -- refresh=False, -- enablerepo="good", -- disablerepo="bad", -- branch="foo", -- setopt="obsoletes=0,plugins=0", -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "-y", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- "--setopt", -- "obsoletes=0", -- "--setopt", -- "plugins=0", -- "install", -- "foo", -- ], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ignore_retcode=False, -- redirect_stderr=True, -- ) -- -- def test_install_with_epoch(self): -- """ -- Tests that we properly identify a version containing an epoch as an -- upgrade instead of a downgrade. -- """ -- name = "foo" -- old = "8:3.8.12-6.n.el7" -- new = "9:3.8.12-4.n.el7" -- list_pkgs_mock = MagicMock( -- side_effect=lambda **kwargs: { -- name: [old] if kwargs.get("versions_as_list", False) else old -- } -- ) -- cmd_mock = MagicMock( -- return_value={"pid": 12345, "retcode": 0, "stdout": "", "stderr": ""} -- ) -- salt_mock = { -- "cmd.run_all": cmd_mock, -- "lowpkg.version_cmp": rpm.version_cmp, -- "pkg_resource.parse_targets": MagicMock( -- return_value=({name: new}, "repository") -- ), -- } -- full_pkg_string = "-".join((name, new[2:])) -- with patch.object(yumpkg, "list_pkgs", list_pkgs_mock), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ), patch.dict(yumpkg.__salt__, salt_mock): -- -- # Test yum -- expected = ["yum", "-y", "install", full_pkg_string] -- with patch.dict(yumpkg.__context__, {"yum_bin": "yum"}), patch.dict( -- yumpkg.__grains__, {"os": "CentOS", "osrelease": 7} -- ): -- yumpkg.install("foo", version=new) -- call = cmd_mock.mock_calls[0][1][0] -- assert call == expected, call -- -- # Test dnf -- expected = [ -- "dnf", -- "-y", -- "--best", -- "--allowerasing", -- "install", -- full_pkg_string, -- ] -- yumpkg.__context__.pop("yum_bin") -- cmd_mock.reset_mock() -- with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( -- yumpkg.__grains__, {"os": "Fedora", "osrelease": 27} -- ): -- yumpkg.install("foo", version=new) -- call = cmd_mock.mock_calls[0][1][0] -- assert call == expected, call -- -- @skipIf(not salt.utils.platform.is_linux(), "Only run on Linux") -- def test_install_error_reporting(self): -- """ -- Tests that we properly report yum/dnf errors. -- """ -- name = "foo" -- old = "8:3.8.12-6.n.el7" -- new = "9:3.8.12-4.n.el7" -- list_pkgs_mock = MagicMock( -- side_effect=lambda **kwargs: { -- name: [old] if kwargs.get("versions_as_list", False) else old -- } -- ) -- salt_mock = { -- "cmd.run_all": cmdmod.run_all, -- "lowpkg.version_cmp": rpm.version_cmp, -- "pkg_resource.parse_targets": MagicMock( -- return_value=({name: new}, "repository") -- ), -- } -- full_pkg_string = "-".join((name, new[2:])) -- with patch.object(yumpkg, "list_pkgs", list_pkgs_mock), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ), patch.dict(yumpkg.__salt__, salt_mock), patch.object( -- yumpkg, "_yum", MagicMock(return_value="cat") -- ): -- -- expected = { -- "changes": {}, -- "errors": [ -- "cat: invalid option -- 'y'\n" -- "Try 'cat --help' for more information." -- ], -- } -- with pytest.raises(CommandExecutionError) as exc_info: -- yumpkg.install("foo", version=new) -- assert exc_info.value.info == expected, exc_info.value.info -- -- def test_upgrade_with_options(self): -- with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- -- # with fromrepo -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): -- yumpkg.upgrade( -- refresh=False, -- fromrepo="good", -- exclude="kernel*", -- branch="foo", -- setopt="obsoletes=0,plugins=0", -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "-y", -- "--disablerepo=*", -- "--enablerepo=good", -- "--branch=foo", -- "--setopt", -- "obsoletes=0", -- "--setopt", -- "plugins=0", -- "--exclude=kernel*", -- "upgrade", -- ], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- # without fromrepo -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): -- yumpkg.upgrade( -- refresh=False, -- enablerepo="good", -- disablerepo="bad", -- exclude="kernel*", -- branch="foo", -- setopt="obsoletes=0,plugins=0", -- ) -- cmd.assert_called_once_with( -- [ -- "yum", -- "--quiet", -- "-y", -- "--disablerepo=bad", -- "--enablerepo=good", -- "--branch=foo", -- "--setopt", -- "obsoletes=0", -- "--setopt", -- "plugins=0", -- "--exclude=kernel*", -- "upgrade", -- ], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- def test_info_installed_with_all_versions(self): -- """ -- Test the return information of all versions for the named package(s), installed on the system. -- -- :return: -- """ -- run_out = { -- "virgo-dummy": [ -- { -- "build_date": "2015-07-09T10:55:19Z", -- "vendor": "openSUSE Build Service", -- "description": "This is the Virgo dummy package used for testing SUSE Manager", -- "license": "GPL-2.0", -- "build_host": "sheep05", -- "url": "http://www.suse.com", -- "build_date_time_t": 1436432119, -- "relocations": "(not relocatable)", -- "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", -- "install_date": "2016-02-23T16:31:57Z", -- "install_date_time_t": 1456241517, -- "summary": "Virgo dummy package", -- "version": "1.0", -- "signature": "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9", -- "release": "1.1", -- "group": "Applications/System", -- "arch": "i686", -- "size": "17992", -- }, -- { -- "build_date": "2015-07-09T10:15:19Z", -- "vendor": "openSUSE Build Service", -- "description": "This is the Virgo dummy package used for testing SUSE Manager", -- "license": "GPL-2.0", -- "build_host": "sheep05", -- "url": "http://www.suse.com", -- "build_date_time_t": 1436432119, -- "relocations": "(not relocatable)", -- "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", -- "install_date": "2016-02-23T16:31:57Z", -- "install_date_time_t": 14562415127, -- "summary": "Virgo dummy package", -- "version": "1.0", -- "signature": "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9", -- "release": "1.1", -- "group": "Applications/System", -- "arch": "x86_64", -- "size": "13124", -- }, -- ], -- "libopenssl1_0_0": [ -- { -- "build_date": "2015-11-04T23:20:34Z", -- "vendor": "SUSE LLC ", -- "description": "The OpenSSL Project is a collaborative effort.", -- "license": "OpenSSL", -- "build_host": "sheep11", -- "url": "https://www.openssl.org/", -- "build_date_time_t": 1446675634, -- "relocations": "(not relocatable)", -- "source_rpm": "openssl-1.0.1i-34.1.src.rpm", -- "install_date": "2016-02-23T16:31:35Z", -- "install_date_time_t": 1456241495, -- "summary": "Secure Sockets and Transport Layer Security", -- "version": "1.0.1i", -- "signature": "RSA/SHA256, Wed Nov 4 22:21:34 2015, Key ID 70af9e8139db7c82", -- "release": "34.1", -- "group": "Productivity/Networking/Security", -- "packager": "https://www.suse.com/", -- "arch": "x86_64", -- "size": "2576912", -- } -- ], -- } -- with patch.dict( -- yumpkg.__salt__, {"lowpkg.info": MagicMock(return_value=run_out)} -- ): -- installed = yumpkg.info_installed(all_versions=True) -- # Test overall products length -- self.assertEqual(len(installed), 2) -- -- # Test multiple versions for the same package -- for pkg_name, pkg_info_list in installed.items(): -- self.assertEqual( -- len(pkg_info_list), 2 if pkg_name == "virgo-dummy" else 1 -- ) -- for info in pkg_info_list: -- self.assertTrue(info["arch"] in ("x86_64", "i686")) -- -- def test_pkg_hold_yum(self): -- """ -- Tests that we properly identify versionlock plugin when using yum -- for RHEL/CentOS 7 and Fedora < 22 -- """ -- -- # Test RHEL/CentOS 7 -- list_pkgs_mock = { -- "yum-plugin-versionlock": "0:1.0.0-0.n.el7", -- "yum-versionlock": "0:1.0.0-0.n.el7", -- } -- -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.object( -- yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) -- ), patch.object(yumpkg, "list_holds", MagicMock(return_value=[])), patch.dict( -- yumpkg.__salt__, {"cmd.run_all": cmd} -- ), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- yumpkg.hold("foo") -- cmd.assert_called_once_with( -- ["yum", "versionlock", "foo"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- # Test Fedora 20 -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__context__, {"yum_bin": "yum"}), patch.dict( -- yumpkg.__grains__, {"os": "Fedora", "osrelease": 20} -- ), patch.object( -- yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) -- ), patch.object( -- yumpkg, "list_holds", MagicMock(return_value=[]) -- ), patch.dict( -- yumpkg.__salt__, {"cmd.run_all": cmd} -- ), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- yumpkg.hold("foo") -- cmd.assert_called_once_with( -- ["yum", "versionlock", "foo"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- def test_pkg_hold_tdnf(self): -- """ -- Tests that we raise a SaltInvocationError if we try to use -- hold-related functions on Photon OS. -- """ -- with patch.dict(yumpkg.__context__, {"yum_bin": "tdnf"}): -- self.assertRaises(SaltInvocationError, yumpkg.hold, "foo") -- -- def test_pkg_hold_dnf(self): -- """ -- Tests that we properly identify versionlock plugin when using dnf -- for RHEL/CentOS 8 and Fedora >= 22 -- """ -- -- # Test RHEL/CentOS 8 -- list_pkgs_mock = { -- "python2-dnf-plugin-versionlock": "0:1.0.0-0.n.el8", -- "python3-dnf-plugin-versionlock": "0:1.0.0-0.n.el8", -- } -- -- yumpkg.__context__.pop("yum_bin") -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( -- yumpkg.__grains__, {"osmajorrelease": 8} -- ), patch.object( -- yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) -- ), patch.object( -- yumpkg, "list_holds", MagicMock(return_value=[]) -- ), patch.dict( -- yumpkg.__salt__, {"cmd.run_all": cmd} -- ), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- yumpkg.hold("foo") -- cmd.assert_called_once_with( -- ["dnf", "versionlock", "foo"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- # Test Fedora 26+ -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( -- yumpkg.__grains__, {"os": "Fedora", "osrelease": 26} -- ), patch.object( -- yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) -- ), patch.object( -- yumpkg, "list_holds", MagicMock(return_value=[]) -- ), patch.dict( -- yumpkg.__salt__, {"cmd.run_all": cmd} -- ), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- yumpkg.hold("foo") -- cmd.assert_called_once_with( -- ["dnf", "versionlock", "foo"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- # Test Fedora 22-25 -- list_pkgs_mock = { -- "python-dnf-plugins-extras-versionlock": "0:1.0.0-0.n.el8", -- "python3-dnf-plugins-extras-versionlock": "0:1.0.0-0.n.el8", -- } -- -- cmd = MagicMock(return_value={"retcode": 0}) -- with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( -- yumpkg.__grains__, {"os": "Fedora", "osrelease": 25} -- ), patch.object( -- yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) -- ), patch.object( -- yumpkg, "list_holds", MagicMock(return_value=[]) -- ), patch.dict( -- yumpkg.__salt__, {"cmd.run_all": cmd} -- ), patch( -- "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- ): -- yumpkg.hold("foo") -- cmd.assert_called_once_with( -- ["dnf", "versionlock", "foo"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- @skipIf(not yumpkg.HAS_YUM, "Could not import yum") -- def test_yum_base_error(self): -- with patch("yum.YumBase") as mock_yum_yumbase: -- mock_yum_yumbase.side_effect = CommandExecutionError -- with pytest.raises(CommandExecutionError): -- yumpkg._get_yum_config() -- -- def test_group_info(self): -- """ -- Test yumpkg.group_info parsing -- """ -- expected = { -- "conditional": [], -- "default": ["qgnomeplatform", "xdg-desktop-portal-gtk"], -- "description": "GNOME is a highly intuitive and user friendly desktop environment.", -- "group": "GNOME", -- "id": "gnome-desktop", -- "mandatory": [ -- "NetworkManager-libreswan-gnome", -- "PackageKit-command-not-found", -- "PackageKit-gtk3-module", -- "abrt-desktop", -- "at-spi2-atk", -- "at-spi2-core", -- "avahi", -- "baobab", -- "caribou", -- "caribou-gtk2-module", -- "caribou-gtk3-module", -- "cheese", -- "chrome-gnome-shell", -- "compat-cheese314", -- "control-center", -- "dconf", -- "empathy", -- "eog", -- "evince", -- "evince-nautilus", -- "file-roller", -- "file-roller-nautilus", -- "firewall-config", -- "firstboot", -- "fprintd-pam", -- "gdm", -- "gedit", -- "glib-networking", -- "gnome-bluetooth", -- "gnome-boxes", -- "gnome-calculator", -- "gnome-classic-session", -- "gnome-clocks", -- "gnome-color-manager", -- "gnome-contacts", -- "gnome-dictionary", -- "gnome-disk-utility", -- "gnome-font-viewer", -- "gnome-getting-started-docs", -- "gnome-icon-theme", -- "gnome-icon-theme-extras", -- "gnome-icon-theme-symbolic", -- "gnome-initial-setup", -- "gnome-packagekit", -- "gnome-packagekit-updater", -- "gnome-screenshot", -- "gnome-session", -- "gnome-session-xsession", -- "gnome-settings-daemon", -- "gnome-shell", -- "gnome-software", -- "gnome-system-log", -- "gnome-system-monitor", -- "gnome-terminal", -- "gnome-terminal-nautilus", -- "gnome-themes-standard", -- "gnome-tweak-tool", -- "gnome-user-docs", -- "gnome-weather", -- "gucharmap", -- "gvfs-afc", -- "gvfs-afp", -- "gvfs-archive", -- "gvfs-fuse", -- "gvfs-goa", -- "gvfs-gphoto2", -- "gvfs-mtp", -- "gvfs-smb", -- "initial-setup-gui", -- "libcanberra-gtk2", -- "libcanberra-gtk3", -- "libproxy-mozjs", -- "librsvg2", -- "libsane-hpaio", -- "metacity", -- "mousetweaks", -- "nautilus", -- "nautilus-sendto", -- "nm-connection-editor", -- "orca", -- "redhat-access-gui", -- "sane-backends-drivers-scanners", -- "seahorse", -- "setroubleshoot", -- "sushi", -- "totem", -- "totem-nautilus", -- "vinagre", -- "vino", -- "xdg-user-dirs-gtk", -- "yelp", -- ], -- "optional": [ -- "", -- "alacarte", -- "dconf-editor", -- "dvgrab", -- "fonts-tweak-tool", -- "gconf-editor", -- "gedit-plugins", -- "gnote", -- "libappindicator-gtk3", -- "seahorse-nautilus", -- "seahorse-sharing", -- "vim-X11", -- "xguest", -- ], -- "type": "package group", -- } -- cmd_out = """Group: GNOME -- Group-Id: gnome-desktop -- Description: GNOME is a highly intuitive and user friendly desktop environment. -- Mandatory Packages: -- =NetworkManager-libreswan-gnome -- =PackageKit-command-not-found -- =PackageKit-gtk3-module -- abrt-desktop -- =at-spi2-atk -- =at-spi2-core -- =avahi -- =baobab -- -caribou -- -caribou-gtk2-module -- -caribou-gtk3-module -- =cheese -- =chrome-gnome-shell -- =compat-cheese314 -- =control-center -- =dconf -- =empathy -- =eog -- =evince -- =evince-nautilus -- =file-roller -- =file-roller-nautilus -- =firewall-config -- =firstboot -- fprintd-pam -- =gdm -- =gedit -- =glib-networking -- =gnome-bluetooth -- =gnome-boxes -- =gnome-calculator -- =gnome-classic-session -- =gnome-clocks -- =gnome-color-manager -- =gnome-contacts -- =gnome-dictionary -- =gnome-disk-utility -- =gnome-font-viewer -- =gnome-getting-started-docs -- =gnome-icon-theme -- =gnome-icon-theme-extras -- =gnome-icon-theme-symbolic -- =gnome-initial-setup -- =gnome-packagekit -- =gnome-packagekit-updater -- =gnome-screenshot -- =gnome-session -- =gnome-session-xsession -- =gnome-settings-daemon -- =gnome-shell -- =gnome-software -- =gnome-system-log -- =gnome-system-monitor -- =gnome-terminal -- =gnome-terminal-nautilus -- =gnome-themes-standard -- =gnome-tweak-tool -- =gnome-user-docs -- =gnome-weather -- =gucharmap -- =gvfs-afc -- =gvfs-afp -- =gvfs-archive -- =gvfs-fuse -- =gvfs-goa -- =gvfs-gphoto2 -- =gvfs-mtp -- =gvfs-smb -- initial-setup-gui -- =libcanberra-gtk2 -- =libcanberra-gtk3 -- =libproxy-mozjs -- =librsvg2 -- =libsane-hpaio -- =metacity -- =mousetweaks -- =nautilus -- =nautilus-sendto -- =nm-connection-editor -- =orca -- -redhat-access-gui -- =sane-backends-drivers-scanners -- =seahorse -- =setroubleshoot -- =sushi -- =totem -- =totem-nautilus -- =vinagre -- =vino -- =xdg-user-dirs-gtk -- =yelp -- Default Packages: -- =qgnomeplatform -- =xdg-desktop-portal-gtk -- Optional Packages: -- alacarte -- dconf-editor -- dvgrab -- fonts-tweak-tool -- gconf-editor -- gedit-plugins -- gnote -- libappindicator-gtk3 -- seahorse-nautilus -- seahorse-sharing -- vim-X11 -- xguest -- """ -- with patch.dict( -- yumpkg.__salt__, {"cmd.run_stdout": MagicMock(return_value=cmd_out)} -- ): -- info = yumpkg.group_info("@gnome-desktop") -- self.assertDictEqual(info, expected) -- -- def test_get_repo_with_existent_repo(self): -- """ -- Test get_repo with an existent repository -- Expected return is a populated dictionary -- """ -- repo = "base-source" -- kwargs = { -- "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "name": "CentOS-$releasever - Base Sources", -- "enabled": True, -- } -- parse_repo_file_return = ( -- "", -- { -- "base-source": { -- "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "name": "CentOS-$releasever - Base Sources", -- "enabled": "1", -- } -- }, -- ) -- expected = { -- "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "name": "CentOS-$releasever - Base Sources", -- "enabled": "1", -- } -- patch_list_repos = patch.object( -- yumpkg, "list_repos", autospec=True, return_value=LIST_REPOS -- ) -- patch_parse_repo_file = patch.object( -- yumpkg, -- "_parse_repo_file", -- autospec=True, -- return_value=parse_repo_file_return, -- ) -- -- with patch_list_repos, patch_parse_repo_file: -- ret = yumpkg.get_repo(repo, **kwargs) -- assert ret == expected, ret -- -- def test_get_repo_with_non_existent_repo(self): -- """ -- Test get_repo with an non existent repository -- Expected return is an empty dictionary -- """ -- repo = "non-existent-repository" -- kwargs = { -- "baseurl": "http://fake.centos.org/centos/$releasever/os/Non-Existent/", -- "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", -- "name": "CentOS-$releasever - Non-Existent Repository", -- "enabled": True, -- } -- expected = {} -- patch_list_repos = patch.object( -- yumpkg, "list_repos", autospec=True, return_value=LIST_REPOS -- ) -- -- with patch_list_repos: -- ret = yumpkg.get_repo(repo, **kwargs) -- assert ret == expected, ret -- -- --@skipIf(pytest is None, "PyTest is missing") --class YumUtilsTestCase(TestCase, LoaderModuleMockMixin): -- """ -- Yum/Dnf utils tests. -- """ -- -- def setup_loader_modules(self): -- return { -- yumpkg: { -- "__context__": {"yum_bin": "fake-yum"}, -- "__grains__": { -- "osarch": "x86_64", -- "os_family": "RedHat", -- "osmajorrelease": 7, -- }, -- } -- } -- -- def test_call_yum_default(self): -- """ -- Call default Yum/Dnf. -- :return: -- """ -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg._call_yum(["-y", "--do-something"]) # pylint: disable=W0106 -- yumpkg.__salt__["cmd.run_all"].assert_called_once_with( -- ["fake-yum", "-y", "--do-something"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- @patch("salt.utils.systemd.has_scope", MagicMock(return_value=True)) -- def test_call_yum_in_scope(self): -- """ -- Call Yum/Dnf within the scope. -- :return: -- """ -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=True)}, -- ): -- yumpkg._call_yum(["-y", "--do-something"]) # pylint: disable=W0106 -- yumpkg.__salt__["cmd.run_all"].assert_called_once_with( -- ["systemd-run", "--scope", "fake-yum", "-y", "--do-something"], -- env={}, -- output_loglevel="trace", -- python_shell=False, -- ) -- -- def test_call_yum_with_kwargs(self): -- """ -- Call Yum/Dnf with the optinal keyword arguments. -- :return: -- """ -- with patch.dict( -- yumpkg.__salt__, -- {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=False)}, -- ): -- yumpkg._call_yum( -- ["-y", "--do-something"], -- python_shell=True, -- output_loglevel="quiet", -- ignore_retcode=False, -- username="Darth Vader", -- ) # pylint: disable=W0106 -- yumpkg.__salt__["cmd.run_all"].assert_called_once_with( -- ["fake-yum", "-y", "--do-something"], -- env={}, -- ignore_retcode=False, -- output_loglevel="quiet", -- python_shell=True, -- username="Darth Vader", -- ) -- -- @skipIf(not salt.utils.systemd.booted(), "Requires systemd") -- @patch("salt.modules.yumpkg._yum", Mock(return_value="dnf")) -- def test_services_need_restart(self): -- """ -- Test that dnf needs-restarting output is parsed and -- salt.utils.systemd.pid_to_service is called as expected. -- """ -- expected = ["firewalld", "salt-minion"] -- -- dnf_mock = Mock( -- return_value="123 : /usr/bin/firewalld\n456 : /usr/bin/salt-minion\n" -- ) -- systemd_mock = Mock(side_effect=["firewalld", "salt-minion"]) -- with patch.dict(yumpkg.__salt__, {"cmd.run_stdout": dnf_mock}), patch( -- "salt.utils.systemd.pid_to_service", systemd_mock -- ): -- assert sorted(yumpkg.services_need_restart()) == expected -- systemd_mock.assert_has_calls([call("123"), call("456")]) -- -- @patch("salt.modules.yumpkg._yum", Mock(return_value="dnf")) -- def test_services_need_restart_requires_systemd(self): -- """Test that yumpkg.services_need_restart raises an error if systemd is unavailable.""" -- with patch("salt.utils.systemd.booted", Mock(return_value=False)): -- pytest.raises(CommandExecutionError, yumpkg.services_need_restart) -- -- @patch("salt.modules.yumpkg._yum", Mock(return_value="yum")) -- def test_services_need_restart_requires_dnf(self): -- """Test that yumpkg.services_need_restart raises an error if DNF is unavailable.""" -- pytest.raises(CommandExecutionError, yumpkg.services_need_restart) -diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py -index 78fe226914..2440954d89 100644 ---- a/tests/unit/modules/test_zypperpkg.py -+++ b/tests/unit/modules/test_zypperpkg.py -@@ -12,11 +12,9 @@ import salt.modules.pkg_resource as pkg_resource - import salt.modules.zypperpkg as zypper - import salt.utils.files - import salt.utils.pkg --from salt.exceptions import CommandExecutionError --from salt.ext import six --from salt.ext.six.moves import configparser -+from salt.exceptions import CommandExecutionError, SaltInvocationError - from tests.support.mixins import LoaderModuleMockMixin --from tests.support.mock import MagicMock, Mock, call, mock_open, patch -+from tests.support.mock import MagicMock, Mock, call, patch - from tests.support.unit import TestCase - - -@@ -2441,3 +2439,41 @@ pattern() = package-c""" - python_shell=False, - env={"ZYPP_READONLY_HACK": "1"}, - ) -+ self.assertEqual(zypper.__context__, {"pkg.other_data": None}) -+ -+ def test_get_repo_keys(self): -+ salt_mock = {"lowpkg.list_gpg_keys": MagicMock(return_value=True)} -+ with patch.dict(zypper.__salt__, salt_mock): -+ self.assertTrue(zypper.get_repo_keys(info=True, root="/mnt")) -+ salt_mock["lowpkg.list_gpg_keys"].assert_called_once_with(True, "/mnt") -+ -+ def test_add_repo_key_fail(self): -+ with self.assertRaises(SaltInvocationError): -+ zypper.add_repo_key() -+ -+ with self.assertRaises(SaltInvocationError): -+ zypper.add_repo_key(path="path", text="text") -+ -+ def test_add_repo_key_path(self): -+ salt_mock = { -+ "cp.cache_file": MagicMock(return_value="path"), -+ "lowpkg.import_gpg_key": MagicMock(return_value=True), -+ } -+ with patch("salt.utils.files.fopen", mock_open(read_data="text")), patch.dict( -+ zypper.__salt__, salt_mock -+ ): -+ self.assertTrue(zypper.add_repo_key(path="path", root="/mnt")) -+ salt_mock["cp.cache_file"].assert_called_once_with("path", "base") -+ salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") -+ -+ def test_add_repo_key_text(self): -+ salt_mock = {"lowpkg.import_gpg_key": MagicMock(return_value=True)} -+ with patch.dict(zypper.__salt__, salt_mock): -+ self.assertTrue(zypper.add_repo_key(text="text", root="/mnt")) -+ salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") -+ -+ def test_del_repo_key(self): -+ salt_mock = {"lowpkg.remove_gpg_key": MagicMock(return_value=True)} -+ with patch.dict(zypper.__salt__, salt_mock): -+ self.assertTrue(zypper.del_repo_key(keyid="keyid", root="/mnt")) -+ salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") -- -2.33.0 +2.34.1 diff --git a/add-missing-aarch64-to-rpm-package-architectures-405.patch b/add-missing-aarch64-to-rpm-package-architectures-405.patch deleted file mode 100644 index 593198a..0000000 --- a/add-missing-aarch64-to-rpm-package-architectures-405.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 03b40485102e88e217814ea4e08fb857ad16cbff Mon Sep 17 00:00:00 2001 -From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> -Date: Wed, 18 Aug 2021 15:05:42 +0300 -Subject: [PATCH] Add missing aarch64 to rpm package architectures (#405) - -Required to prevent false negative results on using pkg.installed -with architecture specification in package name (ex. `bash.aarch64`) ---- - salt/utils/pkg/rpm.py | 2 +- - tests/unit/modules/test_zypperpkg.py | 34 ++++++++++++++++++++++++++++ - 2 files changed, 35 insertions(+), 1 deletion(-) - -diff --git a/salt/utils/pkg/rpm.py b/salt/utils/pkg/rpm.py -index 3e990cc05d..8203d2f989 100644 ---- a/salt/utils/pkg/rpm.py -+++ b/salt/utils/pkg/rpm.py -@@ -30,7 +30,7 @@ ARCHES_ALPHA = ( - "alphaev68", - "alphaev7", - ) --ARCHES_ARM = ("armv5tel", "armv5tejl", "armv6l", "armv7l") -+ARCHES_ARM = ("armv5tel", "armv5tejl", "armv6l", "armv7l", "aarch64") - ARCHES_SH = ("sh3", "sh4", "sh4a") - - ARCHES = ( -diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py -index 2d7e5f0858..20bf5eaaad 100644 ---- a/tests/unit/modules/test_zypperpkg.py -+++ b/tests/unit/modules/test_zypperpkg.py -@@ -2475,3 +2475,37 @@ pattern() = package-c""" - with patch.dict(zypper.__salt__, salt_mock): - self.assertTrue(zypper.del_repo_key(keyid="keyid", root="/mnt")) - salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") -+ -+ def test_services_need_restart(self): -+ """ -+ Test that zypper ps is used correctly to list services that need to -+ be restarted. -+ """ -+ expected = ["salt-minion", "firewalld"] -+ zypper_output = "salt-minion\nfirewalld" -+ zypper_mock = Mock() -+ zypper_mock(root=None).nolock.call = Mock(return_value=zypper_output) -+ -+ with patch("salt.modules.zypperpkg.__zypper__", zypper_mock): -+ assert zypper.services_need_restart() == expected -+ zypper_mock(root=None).nolock.call.assert_called_with("ps", "-sss") -+ -+ def test_normalize_name(self): -+ """ -+ Test that package is normalized only when it should be -+ """ -+ with patch.dict(zypper.__grains__, {"osarch": "x86_64"}): -+ result = zypper.normalize_name("foo") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.x86_64") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.noarch") -+ assert result == "foo", result -+ -+ with patch.dict(zypper.__grains__, {"osarch": "aarch64"}): -+ result = zypper.normalize_name("foo") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.aarch64") -+ assert result == "foo", result -+ result = zypper.normalize_name("foo.noarch") -+ assert result == "foo", result --- -2.33.0 - - diff --git a/add-rpm_vercmp-python-library-for-version-comparison.patch b/add-rpm_vercmp-python-library-for-version-comparison.patch index d3e8724..89c9ead 100644 --- a/add-rpm_vercmp-python-library-for-version-comparison.patch +++ b/add-rpm_vercmp-python-library-for-version-comparison.patch @@ -1,6 +1,6 @@ -From 0ccc9aa260032ba86481f121132a10f439a20700 Mon Sep 17 00:00:00 2001 -From: Victor Zhestkov -Date: Mon, 15 Nov 2021 17:22:35 +0300 +From a15321796586b033d8fa8366074087ceddaa4d23 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Wed, 19 Jan 2022 17:41:11 +0100 Subject: [PATCH] Add rpm_vercmp python library for version comparison - 3003.3 (#448) @@ -13,13 +13,9 @@ Subject: [PATCH] Add rpm_vercmp python library for version comparison - Co-authored-by: Megan Wilhite --- changelog/60814.added | 1 + - salt/modules/rpm_lowpkg.py | 21 +- - tests/pytests/unit/modules/test_rpm_lowpkg.py | 545 ++++++++++++++++++ - tests/unit/modules/test_rpm_lowpkg.py | 478 --------------- - 4 files changed, 566 insertions(+), 479 deletions(-) + tests/pytests/unit/modules/test_rpm_lowpkg.py | 326 ++++++++++++++---- + 2 files changed, 263 insertions(+), 64 deletions(-) create mode 100644 changelog/60814.added - create mode 100644 tests/pytests/unit/modules/test_rpm_lowpkg.py - delete mode 100644 tests/unit/modules/test_rpm_lowpkg.py diff --git a/changelog/60814.added b/changelog/60814.added new file mode 100644 @@ -28,338 +24,72 @@ index 0000000000..7a9ffe1b25 +++ b/changelog/60814.added @@ -0,0 +1 @@ +Add the python rpm-vercmp library in the rpm_lowpkg.py module. -diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py -index 370bd5b728..aba5b939b6 100644 ---- a/salt/modules/rpm_lowpkg.py -+++ b/salt/modules/rpm_lowpkg.py -@@ -29,6 +29,13 @@ try: - except ImportError: - HAS_RPMUTILS = False - -+try: -+ import rpm_vercmp -+ -+ HAS_PY_RPM = True -+except ImportError: -+ HAS_PY_RPM = False -+ - - log = logging.getLogger(__name__) - -@@ -710,6 +717,8 @@ def version_cmp(ver1, ver2, ignore_epoch=False): - "labelCompare function. Not using rpm.labelCompare for " - "version comparison." - ) -+ elif HAS_PY_RPM: -+ cmp_func = rpm_vercmp.vercmp - else: - log.warning( - "Please install a package that provides rpm.labelCompare for " -@@ -778,7 +787,17 @@ def version_cmp(ver1, ver2, ignore_epoch=False): - if not ver1_r or not ver2_r: - ver1_r = ver2_r = "" - -- cmp_result = cmp_func((ver1_e, ver1_v, ver1_r), (ver2_e, ver2_v, ver2_r)) -+ if HAS_PY_RPM: -+ # handle epoch version comparison first -+ # rpm_vercmp.vercmp does not handle epoch version comparison -+ ret = salt.utils.versions.version_cmp(ver1_e, ver2_e) -+ if ret in (1, -1): -+ return ret -+ cmp_result = cmp_func(ver1, ver2) -+ else: -+ cmp_result = cmp_func( -+ (ver1_e, ver1_v, ver1_r), (ver2_e, ver2_v, ver2_r) -+ ) - if cmp_result not in (-1, 0, 1): - raise CommandExecutionError( - "Comparison result '{}' is invalid".format(cmp_result) diff --git a/tests/pytests/unit/modules/test_rpm_lowpkg.py b/tests/pytests/unit/modules/test_rpm_lowpkg.py -new file mode 100644 -index 0000000000..c9d1ac2b1c ---- /dev/null +index f19afa854e..c9d1ac2b1c 100644 +--- a/tests/pytests/unit/modules/test_rpm_lowpkg.py +++ b/tests/pytests/unit/modules/test_rpm_lowpkg.py -@@ -0,0 +1,545 @@ -+""" -+ :codeauthor: Jayesh Kariya -+""" -+ -+ +@@ -3,6 +3,7 @@ + """ + + +import datetime -+import pytest -+import salt.modules.cmdmod -+import salt.modules.rpm_lowpkg as rpm -+import salt.utils.path -+from tests.support.mock import MagicMock, patch -+ -+# pylint: disable=unused-import -+try: -+ import rpm as rpm_lib -+ -+ HAS_RPM = True -+except ImportError: -+ HAS_RPM = False -+ -+try: -+ import rpm_vercmp -+ -+ HAS_PY_RPM = True -+except ImportError: -+ HAS_PY_RPM = False -+# pylint: enable=unused-import -+ -+ -+def _called_with_root(mock): -+ cmd = " ".join(mock.call_args[0][0]) -+ return cmd.startswith("rpm --root /") -+ -+ -+@pytest.fixture -+def configure_loader_modules(): -+ return {rpm: {"rpm": MagicMock(return_value=MagicMock)}} -+ -+ -+# 'list_pkgs' function tests: 2 -+ -+ -+def test_list_pkgs(): -+ """ -+ Test if it list the packages currently installed in a dict -+ """ -+ mock = MagicMock(return_value="") -+ with patch.dict(rpm.__salt__, {"cmd.run": mock}): -+ assert rpm.list_pkgs() == {} -+ assert not _called_with_root(mock) -+ -+ -+def test_list_pkgs_root(): -+ """ -+ Test if it list the packages currently installed in a dict, -+ called with root parameter -+ """ -+ mock = MagicMock(return_value="") -+ with patch.dict(rpm.__salt__, {"cmd.run": mock}): -+ rpm.list_pkgs(root="/") -+ assert _called_with_root(mock) -+ -+ -+# 'verify' function tests: 2 -+ -+ -+def test_verify(): -+ """ -+ Test if it runs an rpm -Va on a system, and returns the -+ results in a dict -+ """ -+ mock = MagicMock( -+ return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345} -+ ) -+ with patch.dict(rpm.__salt__, {"cmd.run_all": mock}): -+ assert rpm.verify("httpd") == {} -+ assert not _called_with_root(mock) -+ -+ -+def test_verify_root(): -+ """ -+ Test if it runs an rpm -Va on a system, and returns the -+ results in a dict, called with root parameter -+ """ -+ mock = MagicMock( -+ return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345} -+ ) -+ with patch.dict(rpm.__salt__, {"cmd.run_all": mock}): -+ rpm.verify("httpd", root="/") -+ assert _called_with_root(mock) -+ -+ -+# 'file_list' function tests: 2 -+ -+ -+def test_file_list(): -+ """ -+ Test if it list the files that belong to a package. -+ """ -+ mock = MagicMock(return_value="") -+ with patch.dict(rpm.__salt__, {"cmd.run": mock}): -+ assert rpm.file_list("httpd") == {"errors": [], "files": []} -+ assert not _called_with_root(mock) -+ -+ -+def test_file_list_root(): -+ """ -+ Test if it list the files that belong to a package, using the -+ root parameter. -+ """ -+ -+ mock = MagicMock(return_value="") -+ with patch.dict(rpm.__salt__, {"cmd.run": mock}): -+ rpm.file_list("httpd", root="/") -+ assert _called_with_root(mock) -+ -+ -+# 'file_dict' function tests: 2 -+ -+ -+def test_file_dict(): -+ """ -+ Test if it list the files that belong to a package -+ """ -+ mock = MagicMock(return_value="") -+ with patch.dict(rpm.__salt__, {"cmd.run": mock}): -+ assert rpm.file_dict("httpd") == {"errors": [], "packages": {}} -+ assert not _called_with_root(mock) -+ -+ -+def test_file_dict_root(): -+ """ -+ Test if it list the files that belong to a package -+ """ -+ mock = MagicMock(return_value="") -+ with patch.dict(rpm.__salt__, {"cmd.run": mock}): -+ rpm.file_dict("httpd", root="/") -+ assert _called_with_root(mock) -+ -+ -+# 'owner' function tests: 1 -+ -+ -+def test_owner(): -+ """ -+ Test if it return the name of the package that owns the file. -+ """ -+ assert rpm.owner() == "" -+ -+ ret = "file /usr/bin/salt-jenkins-build is not owned by any package" -+ mock = MagicMock(return_value=ret) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ assert rpm.owner("/usr/bin/salt-jenkins-build") == "" -+ assert not _called_with_root(mock) -+ -+ ret = { -+ "/usr/bin/vim": "vim-enhanced-7.4.160-1.e17.x86_64", -+ "/usr/bin/python": "python-2.7.5-16.e17.x86_64", -+ } -+ mock = MagicMock( -+ side_effect=[ -+ "python-2.7.5-16.e17.x86_64", -+ "vim-enhanced-7.4.160-1.e17.x86_64", -+ ] -+ ) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ assert rpm.owner("/usr/bin/python", "/usr/bin/vim") == ret -+ assert not _called_with_root(mock) -+ -+ -+def test_owner_root(): -+ """ -+ Test if it return the name of the package that owns the file, -+ using the parameter root. -+ """ -+ assert rpm.owner() == "" -+ -+ ret = "file /usr/bin/salt-jenkins-build is not owned by any package" -+ mock = MagicMock(return_value=ret) -+ with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -+ rpm.owner("/usr/bin/salt-jenkins-build", root="/") -+ assert _called_with_root(mock) -+ -+ -+# 'checksum' function tests: 2 -+ -+ -+def test_checksum(): -+ """ -+ Test if checksum validate as expected -+ """ -+ ret = { -+ "file1.rpm": True, -+ "file2.rpm": False, -+ "file3.rpm": False, -+ } -+ -+ mock = MagicMock(side_effect=[True, 0, True, 1, False, 0]) -+ with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}): -+ assert rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm") == ret -+ assert not _called_with_root(mock) -+ -+ -+def test_checksum_root(): -+ """ -+ Test if checksum validate as expected, using the parameter -+ root -+ """ -+ mock = MagicMock(side_effect=[True, 0]) -+ with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}): -+ rpm.checksum("file1.rpm", root="/") -+ assert _called_with_root(mock) -+ -+ -+@pytest.mark.parametrize("rpm_lib", ["HAS_RPM", "HAS_PY_RPM", "rpmdev-vercmp"]) -+def test_version_cmp_rpm_all_libraries(rpm_lib): -+ """ -+ Test package version when each library is installed -+ """ -+ rpmdev = salt.utils.path.which("rpmdev-vercmp") -+ patch_cmd = patch.dict(rpm.__salt__, {"cmd.run_all": salt.modules.cmdmod.run_all}) -+ if rpm_lib == "rpmdev-vercmp": -+ if rpmdev: -+ patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False) -+ patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) -+ else: -+ pytest.skip("The rpmdev-vercmp binary is not installed") -+ elif rpm_lib == "HAS_RPM": -+ if HAS_RPM: -+ patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", True) -+ patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) -+ else: -+ pytest.skip("The RPM lib is not installed, skipping") -+ elif rpm_lib == "HAS_PY_RPM": -+ if HAS_PY_RPM: -+ patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False) -+ patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", True) -+ else: -+ pytest.skip("The Python RPM lib is not installed, skipping") -+ -+ with patch_rpm, patch_py_rpm, patch_cmd: -+ assert -1 == rpm.version_cmp("1", "2") -+ assert -1 == rpm.version_cmp("2.9.1-6.el7_2.3", "2.9.1-6.el7.4") -+ assert 1 == rpm.version_cmp("3.2", "3.0") -+ assert 0 == rpm.version_cmp("3.0", "3.0") -+ assert 1 == rpm.version_cmp("1:2.9.1-6.el7_2.3", "2.9.1-6.el7.4") -+ assert -1 == rpm.version_cmp("1:2.9.1-6.el7_2.3", "1:2.9.1-6.el7.4") -+ assert 1 == rpm.version_cmp("2:2.9.1-6.el7_2.3", "1:2.9.1-6.el7.4") -+ assert 0 == rpm.version_cmp("3:2.9.1-6.el7.4", "3:2.9.1-6.el7.4") -+ assert -1 == rpm.version_cmp("3:2.9.1-6.el7.4", "3:2.9.1-7.el7.4") -+ assert 1 == rpm.version_cmp("3:2.9.1-8.el7.4", "3:2.9.1-7.el7.4") -+ -+ + import pytest + import salt.modules.cmdmod + import salt.modules.rpm_lowpkg as rpm +@@ -250,92 +251,57 @@ def test_version_cmp_rpm_all_libraries(rpm_lib): + assert 1 == rpm.version_cmp("3:2.9.1-8.el7.4", "3:2.9.1-7.el7.4") + + +-def test_version_cmp_rpm(): +@patch("salt.modules.rpm_lowpkg.HAS_RPM", True) +@patch("salt.modules.rpm_lowpkg.rpm.labelCompare", return_value=-1) +@patch("salt.modules.rpm_lowpkg.log") +def test_version_cmp_rpm(mock_log, mock_labelCompare): -+ """ -+ Test package version if RPM-Python is installed -+ -+ :return: -+ """ + """ + Test package version if RPM-Python is installed + + :return: + """ +- mock_label = MagicMock(return_value=-1) +- mock_log = MagicMock() +- patch_label = patch("salt.modules.rpm_lowpkg.rpm.labelCompare", mock_label) +- patch_log = patch("salt.modules.rpm_lowpkg.log", mock_log) +- patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", True) +- with patch_label, patch_rpm, patch_log: +- assert -1 == rpm.version_cmp("1", "2") +- assert not mock_log.warning.called +- assert mock_label.called + assert -1 == rpm.version_cmp("1", "2") + assert not mock_log.warning.called + assert mock_labelCompare.called -+ -+ + + +-def test_version_cmp_rpmutils(): +@patch("salt.modules.rpm_lowpkg.HAS_RPM", False) +@patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", True) +@patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) +@patch("salt.modules.rpm_lowpkg.rpmUtils", create=True) +@patch("salt.modules.rpm_lowpkg.log") +def test_version_cmp_rpmutils(mock_log, mock_rpmUtils): -+ """ -+ Test package version if rpmUtils.miscutils called -+ -+ :return: -+ """ -+ mock_rpmUtils.miscutils = MagicMock() -+ mock_rpmUtils.miscutils.compareEVR = MagicMock(return_value=-1) + """ + Test package version if rpmUtils.miscutils called + + :return: + """ +- mock_log = MagicMock() +- mock_rpmUtils = MagicMock() + mock_rpmUtils.miscutils = MagicMock() + mock_rpmUtils.miscutils.compareEVR = MagicMock(return_value=-1) +- patch_utils = patch("salt.modules.rpm_lowpkg.rpmUtils", mock_rpmUtils, create=True) +- patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False) +- patch_utils_lib = patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", True) +- patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) +- patch_log = patch("salt.modules.rpm_lowpkg.log", mock_log) +- +- with patch_utils, patch_rpm, patch_py_rpm, patch_utils_lib, patch_log: +- assert -1 == rpm.version_cmp("1", "2") +- assert mock_log.warning.called +- assert mock_rpmUtils.miscutils.compareEVR.called + assert -1 == rpm.version_cmp("1", "2") + assert mock_log.warning.called + assert mock_rpmUtils.miscutils.compareEVR.called @@ -367,32 +97,73 @@ index 0000000000..c9d1ac2b1c + mock_log.warning.mock_calls[0][1][0] + == "Please install a package that provides rpm.labelCompare for more accurate version comparisons." + ) -+ -+ + + +-def test_version_cmp_rpmdev_vercmp(): +@patch("salt.modules.rpm_lowpkg.HAS_RPM", False) +@patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False) +@patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) +@patch("salt.utils.path.which", return_value=True) +@patch("salt.modules.rpm_lowpkg.log") +def test_version_cmp_rpmdev_vercmp(mock_log, mock_which): -+ """ -+ Test package version if rpmdev-vercmp is installed -+ -+ :return: -+ """ -+ mock__salt__ = MagicMock(return_value={"retcode": 12}) + """ + Test package version if rpmdev-vercmp is installed + + :return: + """ + mock__salt__ = MagicMock(return_value={"retcode": 12}) +- mock_log = MagicMock() +- patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False) +- patch_rpmutils = patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False) +- patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) +- patch_which = patch("salt.utils.path.which", return_value=True) +- patch_log = patch("salt.modules.rpm_lowpkg.log", mock_log) +- +- with patch_rpm, patch_rpmutils, patch_py_rpm, patch_which, patch_log: +- with patch.dict(rpm.__salt__, {"cmd.run_all": mock__salt__}): +- assert -1 == rpm.version_cmp("1", "2") +- assert mock__salt__.called +- assert mock_log.warning.called +- assert ( +- mock_log.warning.mock_calls[0][1][0] +- == "Please install a package that provides rpm.labelCompare for more accurate version comparisons." +- ) +- assert ( +- mock_log.warning.mock_calls[1][1][0] +- == "Installing the rpmdevtools package may surface dev tools in production." +- ) +- +- +-def test_version_cmp_python(): +- """ +- Test package version if falling back to python +- +- :return: +- """ +- mock_log = MagicMock() +- patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False) +- patch_rpmutils = patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False) +- mock_version_cmp = MagicMock(return_value=-1) +- patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False) +- patch_cmp = patch("salt.utils.versions.version_cmp", mock_version_cmp) +- patch_which = patch("salt.utils.path.which", return_value=False) +- patch_log = patch("salt.modules.rpm_lowpkg.log", mock_log) +- +- with patch_rpm, patch_rpmutils, patch_py_rpm, patch_cmp, patch_which, patch_log: + with patch.dict(rpm.__salt__, {"cmd.run_all": mock__salt__}): -+ assert -1 == rpm.version_cmp("1", "2") + assert -1 == rpm.version_cmp("1", "2") +- assert mock_version_cmp.called + assert mock__salt__.called -+ assert mock_log.warning.called -+ assert ( -+ mock_log.warning.mock_calls[0][1][0] -+ == "Please install a package that provides rpm.labelCompare for more accurate version comparisons." -+ ) -+ assert ( -+ mock_log.warning.mock_calls[1][1][0] + assert mock_log.warning.called + assert ( + mock_log.warning.mock_calls[0][1][0] +@@ -343,5 +309,237 @@ def test_version_cmp_python(): + ) + assert ( + mock_log.warning.mock_calls[1][1][0] +- == "Falling back on salt.utils.versions.version_cmp() for version comparisons" + == "Installing the rpmdevtools package may surface dev tools in production." -+ ) + ) + + +@patch("salt.modules.rpm_lowpkg.HAS_RPM", False) @@ -625,491 +396,7 @@ index 0000000000..c9d1ac2b1c + with patch.dict(rpm.__salt__, {"cmd.retcode": mock}): + assert rpm.remove_gpg_key("gpg-pubkey-1") + assert not _called_with_root(mock) -diff --git a/tests/unit/modules/test_rpm_lowpkg.py b/tests/unit/modules/test_rpm_lowpkg.py -deleted file mode 100644 -index 280a19b911..0000000000 ---- a/tests/unit/modules/test_rpm_lowpkg.py -+++ /dev/null -@@ -1,478 +0,0 @@ --# -*- coding: utf-8 -*- --""" -- :codeauthor: Jayesh Kariya --""" -- --# Import Python Libs --from __future__ import absolute_import --import datetime -- --# Import Salt Libs --import salt.modules.rpm_lowpkg as rpm -- --# Import Salt Testing Libs --from tests.support.mixins import LoaderModuleMockMixin --from tests.support.mock import MagicMock, patch --from tests.support.unit import TestCase -- -- --def _called_with_root(mock): -- cmd = " ".join(mock.call_args[0][0]) -- return cmd.startswith("rpm --root /") -- -- --class RpmTestCase(TestCase, LoaderModuleMockMixin): -- """ -- Test cases for salt.modules.rpm -- """ -- -- def setup_loader_modules(self): -- return {rpm: {"rpm": MagicMock(return_value=MagicMock)}} -- -- # 'list_pkgs' function tests: 2 -- -- def test_list_pkgs(self): -- """ -- Test if it list the packages currently installed in a dict -- """ -- mock = MagicMock(return_value="") -- with patch.dict(rpm.__salt__, {"cmd.run": mock}): -- self.assertDictEqual(rpm.list_pkgs(), {}) -- self.assertFalse(_called_with_root(mock)) -- -- def test_list_pkgs_root(self): -- """ -- Test if it list the packages currently installed in a dict, -- called with root parameter -- """ -- mock = MagicMock(return_value="") -- with patch.dict(rpm.__salt__, {"cmd.run": mock}): -- rpm.list_pkgs(root="/") -- self.assertTrue(_called_with_root(mock)) -- -- # 'verify' function tests: 2 -- -- def test_verify(self): -- """ -- Test if it runs an rpm -Va on a system, and returns the -- results in a dict -- """ -- mock = MagicMock( -- return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345} -- ) -- with patch.dict(rpm.__salt__, {"cmd.run_all": mock}): -- self.assertDictEqual(rpm.verify("httpd"), {}) -- self.assertFalse(_called_with_root(mock)) -- -- def test_verify_root(self): -- """ -- Test if it runs an rpm -Va on a system, and returns the -- results in a dict, called with root parameter -- """ -- mock = MagicMock( -- return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345} -- ) -- with patch.dict(rpm.__salt__, {"cmd.run_all": mock}): -- rpm.verify("httpd", root="/") -- self.assertTrue(_called_with_root(mock)) -- -- # 'file_list' function tests: 2 -- -- def test_file_list(self): -- """ -- Test if it list the files that belong to a package. -- """ -- mock = MagicMock(return_value="") -- with patch.dict(rpm.__salt__, {"cmd.run": mock}): -- self.assertDictEqual(rpm.file_list("httpd"), {"errors": [], "files": []}) -- self.assertFalse(_called_with_root(mock)) -- -- def test_file_list_root(self): -- """ -- Test if it list the files that belong to a package, using the -- root parameter. -- """ -- -- mock = MagicMock(return_value="") -- with patch.dict(rpm.__salt__, {"cmd.run": mock}): -- rpm.file_list("httpd", root="/") -- self.assertTrue(_called_with_root(mock)) -- -- # 'file_dict' function tests: 2 -- -- def test_file_dict(self): -- """ -- Test if it list the files that belong to a package -- """ -- mock = MagicMock(return_value="") -- with patch.dict(rpm.__salt__, {"cmd.run": mock}): -- self.assertDictEqual(rpm.file_dict("httpd"), {"errors": [], "packages": {}}) -- self.assertFalse(_called_with_root(mock)) -- -- def test_file_dict_root(self): -- """ -- Test if it list the files that belong to a package -- """ -- mock = MagicMock(return_value="") -- with patch.dict(rpm.__salt__, {"cmd.run": mock}): -- rpm.file_dict("httpd", root="/") -- self.assertTrue(_called_with_root(mock)) -- -- # 'owner' function tests: 1 -- -- def test_owner(self): -- """ -- Test if it return the name of the package that owns the file. -- """ -- self.assertEqual(rpm.owner(), "") -- -- ret = "file /usr/bin/salt-jenkins-build is not owned by any package" -- mock = MagicMock(return_value=ret) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertEqual(rpm.owner("/usr/bin/salt-jenkins-build"), "") -- self.assertFalse(_called_with_root(mock)) -- -- ret = { -- "/usr/bin/vim": "vim-enhanced-7.4.160-1.e17.x86_64", -- "/usr/bin/python": "python-2.7.5-16.e17.x86_64", -- } -- mock = MagicMock( -- side_effect=[ -- "python-2.7.5-16.e17.x86_64", -- "vim-enhanced-7.4.160-1.e17.x86_64", -- ] -- ) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertDictEqual(rpm.owner("/usr/bin/python", "/usr/bin/vim"), ret) -- self.assertFalse(_called_with_root(mock)) -- -- def test_owner_root(self): -- """ -- Test if it return the name of the package that owns the file, -- using the parameter root. -- """ -- self.assertEqual(rpm.owner(), "") -- -- ret = "file /usr/bin/salt-jenkins-build is not owned by any package" -- mock = MagicMock(return_value=ret) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- rpm.owner("/usr/bin/salt-jenkins-build", root="/") -- self.assertTrue(_called_with_root(mock)) -- -- # 'checksum' function tests: 2 -- -- def test_checksum(self): -- """ -- Test if checksum validate as expected -- """ -- ret = { -- "file1.rpm": True, -- "file2.rpm": False, -- "file3.rpm": False, -- } -- -- mock = MagicMock(side_effect=[True, 0, True, 1, False, 0]) -- with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}): -- self.assertDictEqual( -- rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm"), ret -- ) -- self.assertFalse(_called_with_root(mock)) -- -- def test_checksum_root(self): -- """ -- Test if checksum validate as expected, using the parameter -- root -- """ -- mock = MagicMock(side_effect=[True, 0]) -- with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}): -- rpm.checksum("file1.rpm", root="/") -- self.assertTrue(_called_with_root(mock)) -- -- @patch("salt.modules.rpm_lowpkg.HAS_RPM", True) -- @patch("salt.modules.rpm_lowpkg.rpm.labelCompare", return_value=-1) -- @patch("salt.modules.rpm_lowpkg.log") -- def test_version_cmp_rpm(self, mock_log, mock_labelCompare): -- """ -- Test package version if RPM-Python is installed -- -- :return: -- """ -- self.assertEqual(-1, rpm.version_cmp("1", "2")) -- self.assertEqual(mock_log.warning.called, False) -- self.assertEqual(mock_labelCompare.called, True) -- -- @patch("salt.modules.rpm_lowpkg.HAS_RPM", False) -- @patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", True) -- @patch("salt.modules.rpm_lowpkg.rpmUtils", create=True) -- @patch("salt.modules.rpm_lowpkg.log") -- def test_version_cmp_rpmutils(self, mock_log, mock_rpmUtils): -- """ -- Test package version if rpmUtils.miscutils called -- -- :return: -- """ -- mock_rpmUtils.miscutils = MagicMock() -- mock_rpmUtils.miscutils.compareEVR = MagicMock(return_value=-1) -- self.assertEqual(-1, rpm.version_cmp("1", "2")) -- self.assertEqual(mock_log.warning.called, True) -- self.assertEqual(mock_rpmUtils.miscutils.compareEVR.called, True) -- self.assertEqual( -- mock_log.warning.mock_calls[0][1][0], -- "Please install a package that provides rpm.labelCompare for more accurate version comparisons.", -- ) -- -- @patch("salt.modules.rpm_lowpkg.HAS_RPM", False) -- @patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False) -- @patch("salt.utils.path.which", return_value=True) -- @patch("salt.modules.rpm_lowpkg.log") -- def test_version_cmp_rpmdev_vercmp(self, mock_log, mock_which): -- """ -- Test package version if rpmdev-vercmp is installed -- -- :return: -- """ -- mock__salt__ = MagicMock(return_value={"retcode": 12}) -- with patch.dict(rpm.__salt__, {"cmd.run_all": mock__salt__}): -- self.assertEqual(-1, rpm.version_cmp("1", "2")) -- self.assertEqual(mock__salt__.called, True) -- self.assertEqual(mock_log.warning.called, True) -- self.assertEqual( -- mock_log.warning.mock_calls[0][1][0], -- "Please install a package that provides rpm.labelCompare for more accurate version comparisons.", -- ) -- self.assertEqual( -- mock_log.warning.mock_calls[1][1][0], -- "Installing the rpmdevtools package may surface dev tools in production.", -- ) -- -- @patch("salt.modules.rpm_lowpkg.HAS_RPM", False) -- @patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False) -- @patch("salt.utils.versions.version_cmp", return_value=-1) -- @patch("salt.utils.path.which", return_value=False) -- @patch("salt.modules.rpm_lowpkg.log") -- def test_version_cmp_python(self, mock_log, mock_which, mock_version_cmp): -- """ -- Test package version if falling back to python -- -- :return: -- """ -- with patch( -- "salt.modules.rpm_lowpkg.rpm.labelCompare", MagicMock(return_value=0) -- ), patch("salt.modules.rpm_lowpkg.HAS_RPM", False): -- self.assertEqual( -- -1, rpm.version_cmp("1", "2") -- ) # mock returns -1, a python implementation was called -- -- def test_list_gpg_keys_no_info(self): -- """ -- Test list_gpg_keys with no extra information -- """ -- mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertEqual(rpm.list_gpg_keys(), ["gpg-pubkey-1", "gpg-pubkey-2"]) -- self.assertFalse(_called_with_root(mock)) -- -- def test_list_gpg_keys_no_info_root(self): -- """ -- Test list_gpg_keys with no extra information and root -- """ -- mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertEqual( -- rpm.list_gpg_keys(root="/mnt"), ["gpg-pubkey-1", "gpg-pubkey-2"] -- ) -- self.assertTrue(_called_with_root(mock)) -- -- @patch("salt.modules.rpm_lowpkg.info_gpg_key") -- def test_list_gpg_keys_info(self, info_gpg_key): -- """ -- Test list_gpg_keys with extra information -- """ -- info_gpg_key.side_effect = lambda x, root: { -- "Description": "key for {}".format(x) -- } -- mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertEqual( -- rpm.list_gpg_keys(info=True), -- { -- "gpg-pubkey-1": {"Description": "key for gpg-pubkey-1"}, -- "gpg-pubkey-2": {"Description": "key for gpg-pubkey-2"}, -- }, -- ) -- self.assertFalse(_called_with_root(mock)) -- -- def test_info_gpg_key(self): -- """ -- Test info_gpg_keys from a normal output -- """ -- info = """Name : gpg-pubkey --Version : 3dbdc284 --Release : 53674dd4 --Architecture: (none) --Install Date: Fri 08 Mar 2019 11:57:44 AM UTC --Group : Public Keys --Size : 0 --License : pubkey --Signature : (none) --Source RPM : (none) --Build Date : Mon 05 May 2014 10:37:40 AM UTC --Build Host : localhost --Packager : openSUSE Project Signing Key --Summary : gpg(openSUSE Project Signing Key ) --Description : -------BEGIN PGP PUBLIC KEY BLOCK----- --Version: rpm-4.14.2.1 (NSS-3) -- --mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G --3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ --93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO --mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig --oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD --VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl --Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC --GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C --hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI --CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha --Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr --hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk --4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a --5v4gbqOcigKaFs9Lc3Bj8b/lE10Y --=i2TA -------END PGP PUBLIC KEY BLOCK----- -- --""" -- mock = MagicMock(return_value=info) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertEqual( -- rpm.info_gpg_key("key"), -- { -- "Name": "gpg-pubkey", -- "Version": "3dbdc284", -- "Release": "53674dd4", -- "Architecture": None, -- "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44), -- "Group": "Public Keys", -- "Size": 0, -- "License": "pubkey", -- "Signature": None, -- "Source RPM": None, -- "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40), -- "Build Host": "localhost", -- "Packager": "openSUSE Project Signing Key ", -- "Summary": "gpg(openSUSE Project Signing Key )", -- "Description": """-----BEGIN PGP PUBLIC KEY BLOCK----- --Version: rpm-4.14.2.1 (NSS-3) -- --mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G --3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ --93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO --mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig --oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD --VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl --Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC --GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C --hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI --CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha --Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr --hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk --4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a --5v4gbqOcigKaFs9Lc3Bj8b/lE10Y --=i2TA -------END PGP PUBLIC KEY BLOCK-----""", -- }, -- ) -- self.assertFalse(_called_with_root(mock)) -- -- def test_info_gpg_key_extended(self): -- """ -- Test info_gpg_keys from an extended output -- """ -- info = """Name : gpg-pubkey --Version : 3dbdc284 --Release : 53674dd4 --Architecture: (none) --Install Date: Fri 08 Mar 2019 11:57:44 AM UTC --Group : Public Keys --Size : 0 --License : pubkey --Signature : (none) --Source RPM : (none) --Build Date : Mon 05 May 2014 10:37:40 AM UTC --Build Host : localhost --Packager : openSUSE Project Signing Key --Summary : gpg(openSUSE Project Signing Key ) --Description : -------BEGIN PGP PUBLIC KEY BLOCK----- --Version: rpm-4.14.2.1 (NSS-3) -- --mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G --3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ --93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO --mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig --oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD --VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl --Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC --GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C --hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI --CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha --Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr --hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk --4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a --5v4gbqOcigKaFs9Lc3Bj8b/lE10Y --=i2TA -------END PGP PUBLIC KEY BLOCK----- -- --Distribution: (none) --""" -- mock = MagicMock(return_value=info) -- with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): -- self.assertEqual( -- rpm.info_gpg_key("key"), -- { -- "Name": "gpg-pubkey", -- "Version": "3dbdc284", -- "Release": "53674dd4", -- "Architecture": None, -- "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44), -- "Group": "Public Keys", -- "Size": 0, -- "License": "pubkey", -- "Signature": None, -- "Source RPM": None, -- "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40), -- "Build Host": "localhost", -- "Packager": "openSUSE Project Signing Key ", -- "Summary": "gpg(openSUSE Project Signing Key )", -- "Description": """-----BEGIN PGP PUBLIC KEY BLOCK----- --Version: rpm-4.14.2.1 (NSS-3) -- --mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G --3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ --93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO --mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig --oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD --VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl --Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC --GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C --hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI --CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha --Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr --hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk --4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a --5v4gbqOcigKaFs9Lc3Bj8b/lE10Y --=i2TA -------END PGP PUBLIC KEY BLOCK-----""", -- "Distribution": None, -- }, -- ) -- self.assertFalse(_called_with_root(mock)) -- -- def test_remove_gpg_key(self): -- """ -- Test remove_gpg_key -- """ -- mock = MagicMock(return_value=0) -- with patch.dict(rpm.__salt__, {"cmd.retcode": mock}): -- self.assertTrue(rpm.remove_gpg_key("gpg-pubkey-1")) -- self.assertFalse(_called_with_root(mock)) -- -2.33.1 +2.34.1 diff --git a/adding-preliminary-support-for-rocky.-59682-391.patch b/adding-preliminary-support-for-rocky.-59682-391.patch deleted file mode 100644 index a8db3c4..0000000 --- a/adding-preliminary-support-for-rocky.-59682-391.patch +++ /dev/null @@ -1,97 +0,0 @@ -From e3e55336b0d457cb55cd83236e9ac8e0dc671d2e Mon Sep 17 00:00:00 2001 -From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> -Date: Mon, 5 Jul 2021 18:57:26 +0300 -Subject: [PATCH] Adding preliminary support for Rocky. (#59682) (#391) - -* Adding preliminary support for Rocky. - -* Adding changelog and test per MR guidence. - -* Update test_core.py - -Fix a clean up issue - -Co-authored-by: Megan Wilhite -Co-authored-by: Gareth J. Greenaway - -Co-authored-by: StackKorora <42156355+StackKorora@users.noreply.github.com> -Co-authored-by: Megan Wilhite -Co-authored-by: Gareth J. Greenaway ---- - changelog/59682.added | 1 + - salt/grains/core.py | 2 ++ - tests/unit/grains/test_core.py | 29 +++++++++++++++++++++++++++++ - 3 files changed, 32 insertions(+) - create mode 100644 changelog/59682.added - -diff --git a/changelog/59682.added b/changelog/59682.added -new file mode 100644 -index 0000000000..93b4a3d1fc ---- /dev/null -+++ b/changelog/59682.added -@@ -0,0 +1 @@ -+Rocky Linux has been added to the RedHat os_family. -diff --git a/salt/grains/core.py b/salt/grains/core.py -index bce8c95179..f79110124f 100644 ---- a/salt/grains/core.py -+++ b/salt/grains/core.py -@@ -1560,6 +1560,7 @@ _OS_NAME_MAP = { - "linuxmint": "Mint", - "neon": "KDE neon", - "pop": "Pop", -+ "rocky": "Rocky", - "alibabaclo": "Alinux", - } - -@@ -1637,6 +1638,7 @@ _OS_FAMILY_MAP = { - "AIX": "AIX", - "TurnKey": "Debian", - "Pop": "Debian", -+ "Rocky": "RedHat", - "AstraLinuxCE": "Debian", - "Alinux": "RedHat", - } -diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py -index fa06bb27ab..fcc7586775 100644 ---- a/tests/unit/grains/test_core.py -+++ b/tests/unit/grains/test_core.py -@@ -708,6 +708,35 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - } - self._run_os_grains_tests(None, _os_release_map, expectation) - -+ @skipIf(not salt.utils.platform.is_linux(), "System is not Linux") -+ def test_rocky_8_os_grains(self): -+ """ -+ Test if OS grains are parsed correctly in Rocky 8 -+ """ -+ _os_release_map = { -+ "os_release_file": { -+ "NAME": "Rocky", -+ "VERSION_ID": "8.3", -+ "PRETTY_NAME": "Rocky 8", -+ "ID": "Rocky", -+ "ANSI_COLOR": "0;31", -+ "CPE_NAME": "cpe:/o:rocky:rocky:8.3", -+ }, -+ "_linux_distribution": ("rocky", "8.3", ""), -+ } -+ -+ expectation = { -+ "os": "Rocky", -+ "os_family": "RedHat", -+ "oscodename": "Rocky 8", -+ "osfullname": "Rocky", -+ "osrelease": "8.3", -+ "osrelease_info": (8, 3,), -+ "osmajorrelease": 8, -+ "osfinger": "Rocky-8", -+ } -+ self._run_os_grains_tests(None, _os_release_map, expectation) -+ - @skipIf(not salt.utils.platform.is_linux(), "System is not Linux") - def test_almalinux_8_os_grains(self): - """ --- -2.33.0 - - diff --git a/better-handling-of-bad-public-keys-from-minions-bsc-.patch b/better-handling-of-bad-public-keys-from-minions-bsc-.patch deleted file mode 100644 index a32e8c9..0000000 --- a/better-handling-of-bad-public-keys-from-minions-bsc-.patch +++ /dev/null @@ -1,218 +0,0 @@ -From f0025c6d00f174db587726bb15b78713cbbcf996 Mon Sep 17 00:00:00 2001 -From: "Daniel A. Wozniak" -Date: Mon, 2 Aug 2021 13:50:37 -0700 -Subject: [PATCH] Better handling of bad public keys from minions - (bsc#1189040) - -Add changelog for #57733 - -Fix pre-commit check - -Add missing test - -Fix test on older pythons ---- - changelog/57733.fixed | 1 + - salt/crypt.py | 11 ++++++-- - salt/exceptions.py | 6 ++++ - salt/key.py | 15 ++++++++-- - salt/transport/mixins/auth.py | 12 ++++---- - .../pytests/integration/cli/test_salt_key.py | 28 +++++++++++++++++++ - tests/pytests/unit/test_crypt.py | 20 +++++++++++++ - 7 files changed, 83 insertions(+), 10 deletions(-) - create mode 100644 changelog/57733.fixed - create mode 100644 tests/pytests/unit/test_crypt.py - -diff --git a/changelog/57733.fixed b/changelog/57733.fixed -new file mode 100644 -index 0000000000..0cd55b19a6 ---- /dev/null -+++ b/changelog/57733.fixed -@@ -0,0 +1 @@ -+Better handling of bad RSA public keys from minions -diff --git a/salt/crypt.py b/salt/crypt.py -index f3da78f9ba..789c562e25 100644 ---- a/salt/crypt.py -+++ b/salt/crypt.py -@@ -36,6 +36,7 @@ import salt.utils.verify - import salt.version - from salt.exceptions import ( - AuthenticationError, -+ InvalidKeyError, - MasterExit, - SaltClientError, - SaltReqTimeoutError, -@@ -220,10 +221,16 @@ def get_rsa_pub_key(path): - with salt.utils.files.fopen(path, "rb") as f: - data = f.read().replace(b"RSA ", b"") - bio = BIO.MemoryBuffer(data) -- key = RSA.load_pub_key_bio(bio) -+ try: -+ key = RSA.load_pub_key_bio(bio) -+ except RSA.RSAError: -+ raise InvalidKeyError("Encountered bad RSA public key") - else: - with salt.utils.files.fopen(path) as f: -- key = RSA.importKey(f.read()) -+ try: -+ key = RSA.importKey(f.read()) -+ except (ValueError, IndexError, TypeError): -+ raise InvalidKeyError("Encountered bad RSA public key") - return key - - -diff --git a/salt/exceptions.py b/salt/exceptions.py -index 033a19cc54..1da15f9e69 100644 ---- a/salt/exceptions.py -+++ b/salt/exceptions.py -@@ -111,6 +111,12 @@ class AuthenticationError(SaltException): - """ - - -+class InvalidKeyError(SaltException): -+ """ -+ Raised when we encounter an invalid RSA key. -+ """ -+ -+ - class CommandNotFoundError(SaltException): - """ - Used in modules or grains when a required binary is not available -diff --git a/salt/key.py b/salt/key.py -index 16d20b1303..3b931152cc 100644 ---- a/salt/key.py -+++ b/salt/key.py -@@ -9,6 +9,7 @@ import itertools - import logging - import os - import shutil -+import sys - - import salt.cache - import salt.client -@@ -643,17 +644,27 @@ class Key: - keydirs.append(self.REJ) - if include_denied: - keydirs.append(self.DEN) -+ invalid_keys = [] - for keydir in keydirs: - for key in matches.get(keydir, []): -+ key_path = os.path.join(self.opts["pki_dir"], keydir, key) -+ try: -+ salt.crypt.get_rsa_pub_key(key_path) -+ except salt.exceptions.InvalidKeyError: -+ log.error("Invalid RSA public key: %s", key) -+ invalid_keys.append((keydir, key)) -+ continue - try: - shutil.move( -- os.path.join(self.opts["pki_dir"], keydir, key), -- os.path.join(self.opts["pki_dir"], self.ACC, key), -+ key_path, os.path.join(self.opts["pki_dir"], self.ACC, key), - ) - eload = {"result": True, "act": "accept", "id": key} - self.event.fire_event(eload, salt.utils.event.tagify(prefix="key")) - except OSError: - pass -+ for keydir, key in invalid_keys: -+ matches[keydir].remove(key) -+ sys.stderr.write("Unable to accept invalid key for {}.\n".format(key)) - return self.name_match(match) if match is not None else self.dict_match(matches) - - def accept_all(self): -diff --git a/salt/transport/mixins/auth.py b/salt/transport/mixins/auth.py -index 102af568f3..29b38d3027 100644 ---- a/salt/transport/mixins/auth.py -+++ b/salt/transport/mixins/auth.py -@@ -174,11 +174,11 @@ class AESReqServerMixin: - tagged "auth" and returns a dict with information about the auth - event - -- # Verify that the key we are receiving matches the stored key -- # Store the key if it is not there -- # Make an RSA key with the pub key -- # Encrypt the AES key as an encrypted salt.payload -- # Package the return and return it -+ - Verify that the key we are receiving matches the stored key -+ - Store the key if it is not there -+ - Make an RSA key with the pub key -+ - Encrypt the AES key as an encrypted salt.payload -+ - Package the return and return it - """ - - if not salt.utils.verify.valid_id(self.opts, load["id"]): -@@ -450,7 +450,7 @@ class AESReqServerMixin: - # and an empty request comes in - try: - pub = salt.crypt.get_rsa_pub_key(pubfn) -- except (ValueError, IndexError, TypeError) as err: -+ except salt.crypt.InvalidKeyError as err: - log.error('Corrupt public key "%s": %s', pubfn, err) - return {"enc": "clear", "load": {"ret": False}} - -diff --git a/tests/pytests/integration/cli/test_salt_key.py b/tests/pytests/integration/cli/test_salt_key.py -index 3ec87fe580..8f29929747 100644 ---- a/tests/pytests/integration/cli/test_salt_key.py -+++ b/tests/pytests/integration/cli/test_salt_key.py -@@ -316,3 +316,31 @@ def test_keys_generation_keysize_max(salt_key_cli): - ) - assert ret.exitcode != 0 - assert "error: The maximum value for keysize is 32768" in ret.stderr -+ -+def test_keys_generation_keysize_max(salt_key_cli, tmp_path): -+ ret = salt_key_cli.run( -+ "--gen-keys", "minibar", "--gen-keys-dir", str(tmp_path), "--keysize", "32769" -+ ) -+ assert ret.exitcode != 0 -+ assert "error: The maximum value for keysize is 32768" in ret.stderr -+ -+ -+def test_accept_bad_key(salt_master, salt_key_cli): -+ """ -+ test salt-key -d usage -+ """ -+ min_name = random_string("minibar-") -+ pki_dir = salt_master.config["pki_dir"] -+ key = os.path.join(pki_dir, "minions_pre", min_name) -+ -+ with salt.utils.files.fopen(key, "w") as fp: -+ fp.write("") -+ -+ try: -+ # Check Key -+ ret = salt_key_cli.run("-y", "-a", min_name) -+ assert ret.exitcode == 0 -+ assert "invalid key for {}".format(min_name) in ret.stderr -+ finally: -+ if os.path.exists(key): -+ os.remove(key) -diff --git a/tests/pytests/unit/test_crypt.py b/tests/pytests/unit/test_crypt.py -new file mode 100644 -index 0000000000..aa8f439b8c ---- /dev/null -+++ b/tests/pytests/unit/test_crypt.py -@@ -0,0 +1,20 @@ -+""" -+tests.pytests.unit.test_crypt -+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+ -+Unit tests for salt's crypt module -+""" -+import pytest -+import salt.crypt -+import salt.utils.files -+ -+ -+def test_get_rsa_pub_key_bad_key(tmp_path): -+ """ -+ get_rsa_pub_key raises InvalidKeyError when encoutering a bad key -+ """ -+ key_path = str(tmp_path / "key") -+ with salt.utils.files.fopen(key_path, "w") as fp: -+ fp.write("") -+ with pytest.raises(salt.crypt.InvalidKeyError): -+ salt.crypt.get_rsa_pub_key(key_path) --- -2.33.0 - - diff --git a/debian-info_installed-compatibility-50453.patch b/debian-info_installed-compatibility-50453.patch index 925950e..2d1d26d 100644 --- a/debian-info_installed-compatibility-50453.patch +++ b/debian-info_installed-compatibility-50453.patch @@ -1,6 +1,6 @@ -From 1ccca51897eb7c7cf1bace7015a4307aa0be7215 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Tue, 20 Nov 2018 16:06:31 +0100 +From 7720401d74ed6eafe860aab297aee0c8e22bc00f Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 25 Jan 2022 17:08:57 +0100 Subject: [PATCH] Debian info_installed compatibility (#50453) Remove unused variable @@ -55,18 +55,16 @@ Reintroducing changes from commit e20362f6f053eaa4144583604e6aac3d62838419 that got partially reverted by this commit: https://github.com/openSUSE/salt/commit/d0ef24d113bdaaa29f180031b5da384cffe08c64#diff-820e6ce667fe3afddbc1b9cf1682fdef --- - salt/modules/aptpkg.py | 24 +- - salt/modules/dpkg_lowpkg.py | 108 ++- - tests/unit/modules/test_aptpkg.py | 981 +++++++++++++++++++++++++ - tests/unit/modules/test_dpkg_lowpkg.py | 189 +++-- - 4 files changed, 1203 insertions(+), 99 deletions(-) - create mode 100644 tests/unit/modules/test_aptpkg.py + salt/modules/aptpkg.py | 24 ++++- + salt/modules/dpkg_lowpkg.py | 108 ++++++++++++++++++---- + tests/pytests/unit/modules/test_aptpkg.py | 52 +++++++++++ + 3 files changed, 166 insertions(+), 18 deletions(-) diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py -index 86c85bb95c..06db908d3d 100644 +index 8d9f1b9f52..3c3fbf4970 100644 --- a/salt/modules/aptpkg.py +++ b/salt/modules/aptpkg.py -@@ -2920,6 +2920,15 @@ def info_installed(*names, **kwargs): +@@ -3035,6 +3035,15 @@ def info_installed(*names, **kwargs): .. versionadded:: 2016.11.3 @@ -82,7 +80,7 @@ index 86c85bb95c..06db908d3d 100644 CLI Example: .. code-block:: bash -@@ -2930,11 +2939,19 @@ def info_installed(*names, **kwargs): +@@ -3045,11 +3054,19 @@ def info_installed(*names, **kwargs): """ kwargs = salt.utils.args.clean_kwargs(**kwargs) failhard = kwargs.pop("failhard", True) @@ -103,7 +101,7 @@ index 86c85bb95c..06db908d3d 100644 t_nfo = dict() if pkg_nfo.get("status", "ii")[1] != "i": continue # return only packages that are really installed -@@ -2955,7 +2972,10 @@ def info_installed(*names, **kwargs): +@@ -3070,7 +3087,10 @@ def info_installed(*names, **kwargs): else: t_nfo[key] = value @@ -282,1216 +280,70 @@ index 6a88573a8f..afbd619490 100644 + ret[pkg_name] = pkg return ret -diff --git a/tests/unit/modules/test_aptpkg.py b/tests/unit/modules/test_aptpkg.py -new file mode 100644 -index 0000000000..3c9744e224 ---- /dev/null -+++ b/tests/unit/modules/test_aptpkg.py -@@ -0,0 +1,981 @@ -+""" -+ :synopsis: Unit Tests for Advanced Packaging Tool module 'module.aptpkg' -+ :platform: Linux -+ :maturity: develop -+ versionadded:: 2017.7.0 -+""" +diff --git a/tests/pytests/unit/modules/test_aptpkg.py b/tests/pytests/unit/modules/test_aptpkg.py +index 6c5ed29848..51b7ffbe4d 100644 +--- a/tests/pytests/unit/modules/test_aptpkg.py ++++ b/tests/pytests/unit/modules/test_aptpkg.py +@@ -336,6 +336,58 @@ def test_info_installed(lowpkg_info_var): + assert len(aptpkg.info_installed()) == 1 + + ++def test_info_installed_attr(lowpkg_info_var): ++ """ ++ Test info_installed 'attr'. ++ This doesn't test 'attr' behaviour per se, since the underlying function is in dpkg. ++ The test should simply not raise exceptions for invalid parameter. + -+ -+import copy -+import logging -+import textwrap -+ -+import pytest -+import salt.modules.aptpkg as aptpkg -+from salt.exceptions import CommandExecutionError, SaltInvocationError -+from salt.ext import six -+from tests.support.mixins import LoaderModuleMockMixin -+from tests.support.mock import MagicMock, Mock, call, patch -+from tests.support.unit import TestCase, skipIf -+ -+log = logging.getLogger(__name__) -+ -+ -+APT_KEY_LIST = r""" -+pub:-:1024:17:46181433FBB75451:1104433784:::-:::scSC: -+fpr:::::::::C5986B4F1257FFA86632CBA746181433FBB75451: -+uid:-::::1104433784::B4D41942D4B35FF44182C7F9D00C99AF27B93AD0::Ubuntu CD Image Automatic Signing Key : -+""" -+ -+REPO_KEYS = { -+ "46181433FBB75451": { -+ "algorithm": 17, -+ "bits": 1024, -+ "capability": "scSC", -+ "date_creation": 1104433784, -+ "date_expiration": None, -+ "fingerprint": "C5986B4F1257FFA86632CBA746181433FBB75451", -+ "keyid": "46181433FBB75451", -+ "uid": "Ubuntu CD Image Automatic Signing Key ", -+ "uid_hash": "B4D41942D4B35FF44182C7F9D00C99AF27B93AD0", -+ "validity": "-", -+ } -+} -+ -+PACKAGES = {"wget": "1.15-1ubuntu1.14.04.2"} -+ -+LOWPKG_FILES = { -+ "errors": {}, -+ "packages": { -+ "wget": [ -+ "/.", -+ "/etc", -+ "/etc/wgetrc", -+ "/usr", -+ "/usr/bin", -+ "/usr/bin/wget", -+ "/usr/share", -+ "/usr/share/info", -+ "/usr/share/info/wget.info.gz", -+ "/usr/share/doc", -+ "/usr/share/doc/wget", -+ "/usr/share/doc/wget/MAILING-LIST", -+ "/usr/share/doc/wget/NEWS.gz", -+ "/usr/share/doc/wget/AUTHORS", -+ "/usr/share/doc/wget/copyright", -+ "/usr/share/doc/wget/changelog.Debian.gz", -+ "/usr/share/doc/wget/README", -+ "/usr/share/man", -+ "/usr/share/man/man1", -+ "/usr/share/man/man1/wget.1.gz", -+ ] -+ }, -+} -+ -+LOWPKG_INFO = { -+ "wget": { -+ "architecture": "amd64", -+ "description": "retrieves files from the web", -+ "homepage": "http://www.gnu.org/software/wget/", -+ "install_date": "2016-08-30T22:20:15Z", -+ "maintainer": "Ubuntu Developers ", ++ :return: ++ """ ++ expected_pkg = { ++ "url": "http://www.gnu.org/software/wget/", ++ "packager": "Ubuntu Developers ", + "name": "wget", -+ "section": "web", -+ "source": "wget", -+ "version": "1.15-1ubuntu1.14.04.2", -+ "status": "ii", -+ }, -+ "apache2": { -+ "architecture": "amd64", -+ "description": """Apache HTTP Server -+ The Apache HTTP Server Project's goal is to build a secure, efficient and -+ extensible HTTP server as standards-compliant open source software. The -+ result has long been the number one web server on the Internet. -+ . -+ Installing this package results in a full installation, including the -+ configuration files, init scripts and support scripts.""", -+ "homepage": "http://httpd.apache.org/", + "install_date": "2016-08-30T22:20:15Z", -+ "maintainer": "Ubuntu Developers ", -+ "name": "apache2", -+ "section": "httpd", -+ "source": "apache2", -+ "version": "2.4.18-2ubuntu3.9", -+ "status": "rc", -+ }, -+} -+ -+APT_Q_UPDATE = """ -+Get:1 http://security.ubuntu.com trusty-security InRelease [65 kB] -+Get:2 http://security.ubuntu.com trusty-security/main Sources [120 kB] -+Get:3 http://security.ubuntu.com trusty-security/main amd64 Packages [548 kB] -+Get:4 http://security.ubuntu.com trusty-security/main i386 Packages [507 kB] -+Hit http://security.ubuntu.com trusty-security/main Translation-en -+Fetched 1240 kB in 10s (124 kB/s) -+Reading package lists... -+""" -+ -+APT_Q_UPDATE_ERROR = """ -+Err http://security.ubuntu.com trusty InRelease -+ -+Err http://security.ubuntu.com trusty Release.gpg -+Unable to connect to security.ubuntu.com:http: -+Reading package lists... -+W: Failed to fetch http://security.ubuntu.com/ubuntu/dists/trusty/InRelease -+ -+W: Failed to fetch http://security.ubuntu.com/ubuntu/dists/trusty/Release.gpg Unable to connect to security.ubuntu.com:http: -+ -+W: Some index files failed to download. They have been ignored, or old ones used instead. -+""" -+ -+AUTOREMOVE = """ -+Reading package lists... Done -+Building dependency tree -+Reading state information... Done -+0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. -+""" -+ -+UPGRADE = """ -+Reading package lists... -+Building dependency tree... -+Reading state information... -+0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. -+""" -+ -+UNINSTALL = {"tmux": {"new": "", "old": "1.8-5"}} -+INSTALL = {"tmux": {"new": "1.8-5", "old": ""}} -+ -+ -+def _get_uri(repo): -+ """ -+ Get the URI portion of the a string -+ """ -+ splits = repo.split() -+ for val in splits: -+ if any(val.startswith(x) for x in ("http://", "https://", "ftp://")): -+ return val -+ -+ -+class MockSourceEntry: -+ def __init__(self, uri, source_type, line, invalid, file=None): -+ self.uri = uri -+ self.type = source_type -+ self.line = line -+ self.invalid = invalid -+ self.file = file -+ self.disabled = False -+ self.dist = "" -+ -+ def mysplit(self, line): -+ return line.split() -+ -+ -+class MockSourceList: -+ def __init__(self): -+ self.list = [] -+ -+ -+class AptPkgTestCase(TestCase, LoaderModuleMockMixin): -+ """ -+ Test cases for salt.modules.aptpkg -+ """ -+ -+ def setup_loader_modules(self): -+ return {aptpkg: {"__grains__": {}}} -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ { -+ "pkg_resource.version": MagicMock( -+ return_value=LOWPKG_INFO["wget"]["version"] -+ ) -+ }, -+ ) -+ def test_version(self): -+ """ -+ Test - Returns a string representing the package version or an empty string if -+ not installed. -+ """ -+ assert aptpkg.version(*["wget"]) == aptpkg.__salt__["pkg_resource.version"]() -+ -+ @patch("salt.modules.aptpkg.latest_version", MagicMock(return_value="")) -+ def test_upgrade_available(self): -+ """ -+ Test - Check whether or not an upgrade is available for a given package. -+ """ -+ assert not aptpkg.upgrade_available("wget") -+ -+ @patch("salt.modules.aptpkg.get_repo_keys", MagicMock(return_value=REPO_KEYS)) -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"cmd.run_all": MagicMock(return_value={"retcode": 0, "stdout": "OK"})}, -+ ) -+ def test_add_repo_key(self): -+ """ -+ Test - Add a repo key. -+ """ -+ assert aptpkg.add_repo_key(keyserver="keyserver.ubuntu.com", keyid="FBB75451") -+ -+ @patch("salt.modules.aptpkg.get_repo_keys", MagicMock(return_value=REPO_KEYS)) -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"cmd.run_all": MagicMock(return_value={"retcode": 0, "stdout": "OK"})}, -+ ) -+ def test_add_repo_key_failed(self): -+ """ -+ Test - Add a repo key using incomplete input data. -+ """ -+ with pytest.raises(SaltInvocationError) as ex: -+ aptpkg.add_repo_key(keyserver="keyserver.ubuntu.com") -+ assert ( -+ " No keyid or keyid too short for keyserver: keyserver.ubuntu.com" -+ in str(ex) -+ ) -+ -+ def test_get_repo_keys(self): -+ """ -+ Test - List known repo key details. -+ """ -+ mock = MagicMock(return_value={"retcode": 0, "stdout": APT_KEY_LIST}) -+ with patch.dict(aptpkg.__salt__, {"cmd.run_all": mock}): -+ self.assertEqual(aptpkg.get_repo_keys(), REPO_KEYS) -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"lowpkg.file_dict": MagicMock(return_value=LOWPKG_FILES)}, -+ ) -+ def test_file_dict(self): -+ """ -+ Test - List the files that belong to a package, grouped by package. -+ """ -+ assert aptpkg.file_dict("wget") == LOWPKG_FILES -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ { -+ "lowpkg.file_list": MagicMock( -+ return_value={ -+ "errors": LOWPKG_FILES["errors"], -+ "files": LOWPKG_FILES["packages"]["wget"], -+ } -+ ) -+ }, -+ ) -+ def test_file_list(self): -+ """ -+ Test 'file_list' function, which is just an alias to the lowpkg 'file_list' -+ -+ """ -+ assert aptpkg.file_list("wget") == aptpkg.__salt__["lowpkg.file_list"]() -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"cmd.run_stdout": MagicMock(return_value="wget\t\t\t\t\t\tinstall")}, -+ ) -+ def test_get_selections(self): -+ """ -+ Test - View package state from the dpkg database. -+ """ -+ assert aptpkg.get_selections("wget") == {"install": ["wget"]} -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"lowpkg.info": MagicMock(return_value=LOWPKG_INFO)}, -+ ) -+ def test_info_installed(self): -+ """ -+ Test - Return the information of the named package(s) installed on the system. -+ """ -+ names = {"group": "section", "packager": "maintainer", "url": "homepage"} -+ -+ installed = copy.deepcopy({"wget": LOWPKG_INFO["wget"]}) -+ for name in names: -+ if installed["wget"].get(names[name], False): -+ installed["wget"][name] = installed["wget"].pop(names[name]) -+ -+ del installed["wget"]["status"] -+ self.assertEqual(aptpkg.info_installed("wget"), installed) -+ self.assertEqual(len(aptpkg.info_installed()), 1) -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"lowpkg.info": MagicMock(return_value=LOWPKG_INFO)}, -+ ) -+ def test_info_installed_attr(self): -+ """ -+ Test info_installed 'attr'. -+ This doesn't test 'attr' behaviour per se, since the underlying function is in dpkg. -+ The test should simply not raise exceptions for invalid parameter. -+ -+ :return: -+ """ -+ ret = aptpkg.info_installed("emacs", attr="foo,bar") -+ assert isinstance(ret, dict) -+ assert "wget" in ret -+ assert isinstance(ret["wget"], dict) -+ -+ wget_pkg = ret["wget"] -+ expected_pkg = { -+ "url": "http://www.gnu.org/software/wget/", -+ "packager": "Ubuntu Developers ", -+ "name": "wget", -+ "install_date": "2016-08-30T22:20:15Z", -+ "description": "retrieves files from the web", -+ "version": "1.15-1ubuntu1.14.04.2", -+ "architecture": "amd64", -+ "group": "web", -+ "source": "wget", -+ } -+ for k in wget_pkg: -+ assert k in expected_pkg -+ assert wget_pkg[k] == expected_pkg[k] -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"lowpkg.info": MagicMock(return_value=LOWPKG_INFO)}, -+ ) -+ def test_info_installed_all_versions(self): -+ """ -+ Test info_installed 'all_versions'. -+ Since Debian won't return same name packages with the different names, -+ this should just return different structure, backward compatible with -+ the RPM equivalents. -+ -+ :return: -+ """ -+ print() -+ ret = aptpkg.info_installed("emacs", all_versions=True) -+ assert isinstance(ret, dict) -+ assert "wget" in ret -+ assert isinstance(ret["wget"], list) -+ -+ pkgs = ret["wget"] -+ -+ assert len(pkgs) == 1 -+ assert isinstance(pkgs[0], dict) -+ -+ wget_pkg = pkgs[0] -+ expected_pkg = { -+ "url": "http://www.gnu.org/software/wget/", -+ "packager": "Ubuntu Developers ", -+ "name": "wget", -+ "install_date": "2016-08-30T22:20:15Z", -+ "description": "retrieves files from the web", -+ "version": "1.15-1ubuntu1.14.04.2", -+ "architecture": "amd64", -+ "group": "web", -+ "source": "wget", -+ } -+ for k in wget_pkg: -+ assert k in expected_pkg -+ assert wget_pkg[k] == expected_pkg[k] -+ -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ {"cmd.run_stdout": MagicMock(return_value="wget: /usr/bin/wget")}, -+ ) -+ def test_owner(self): -+ """ -+ Test - Return the name of the package that owns the file. -+ """ -+ assert aptpkg.owner("/usr/bin/wget") == "wget" -+ -+ @patch("salt.utils.pkg.clear_rtag", MagicMock()) -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ { -+ "cmd.run_all": MagicMock( -+ return_value={"retcode": 0, "stdout": APT_Q_UPDATE} -+ ), -+ "config.get": MagicMock(return_value=False), -+ }, -+ ) -+ def test_refresh_db(self): -+ """ -+ Test - Updates the APT database to latest packages based upon repositories. -+ """ -+ refresh_db = { -+ "http://security.ubuntu.com trusty-security InRelease": True, -+ "http://security.ubuntu.com trusty-security/main Sources": True, -+ "http://security.ubuntu.com trusty-security/main Translation-en": None, -+ "http://security.ubuntu.com trusty-security/main amd64 Packages": True, -+ "http://security.ubuntu.com trusty-security/main i386 Packages": True, -+ } -+ mock = MagicMock(return_value={"retcode": 0, "stdout": APT_Q_UPDATE}) -+ with patch("salt.utils.pkg.clear_rtag", MagicMock()): -+ with patch.dict( -+ aptpkg.__salt__, -+ {"cmd.run_all": mock, "config.get": MagicMock(return_value=False)}, -+ ): -+ self.assertEqual(aptpkg.refresh_db(), refresh_db) -+ -+ @patch("salt.utils.pkg.clear_rtag", MagicMock()) -+ @patch( -+ "salt.modules.aptpkg.__salt__", -+ { -+ "cmd.run_all": MagicMock( -+ return_value={"retcode": 0, "stdout": APT_Q_UPDATE_ERROR} -+ ), -+ "config.get": MagicMock(return_value=False), -+ }, -+ ) -+ def test_refresh_db_failed(self): -+ """ -+ Test - Update the APT database using unreachable repositories. -+ """ -+ kwargs = {"failhard": True} -+ mock = MagicMock(return_value={"retcode": 0, "stdout": APT_Q_UPDATE_ERROR}) -+ with patch("salt.utils.pkg.clear_rtag", MagicMock()): -+ with patch.dict( -+ aptpkg.__salt__, -+ {"cmd.run_all": mock, "config.get": MagicMock(return_value=False)}, -+ ): -+ self.assertRaises(CommandExecutionError, aptpkg.refresh_db, **kwargs) -+ -+ def test_autoremove(self): -+ """ -+ Test - Remove packages not required by another package. -+ """ -+ with patch("salt.modules.aptpkg.list_pkgs", MagicMock(return_value=PACKAGES)): -+ patch_kwargs = { -+ "__salt__": { -+ "config.get": MagicMock(return_value=True), -+ "cmd.run_all": MagicMock( -+ return_value=MagicMock(return_value=AUTOREMOVE) -+ ), -+ } -+ } -+ with patch.multiple(aptpkg, **patch_kwargs): -+ assert aptpkg.autoremove() == {} -+ assert aptpkg.autoremove(purge=True) == {} -+ assert aptpkg.autoremove(list_only=True) == [] -+ assert aptpkg.autoremove(list_only=True, purge=True) == [] -+ -+ @patch("salt.modules.aptpkg._uninstall", MagicMock(return_value=UNINSTALL)) -+ def test_remove(self): -+ """ -+ Test - Remove packages. -+ """ -+ assert aptpkg.remove(name="tmux") == UNINSTALL -+ -+ @patch("salt.modules.aptpkg._uninstall", MagicMock(return_value=UNINSTALL)) -+ def test_purge(self): -+ """ -+ Test - Remove packages along with all configuration files. -+ """ -+ assert aptpkg.purge(name="tmux") == UNINSTALL -+ -+ @patch("salt.utils.pkg.clear_rtag", MagicMock()) -+ @patch("salt.modules.aptpkg.list_pkgs", MagicMock(return_value=UNINSTALL)) -+ @patch.multiple( -+ aptpkg, -+ **{ -+ "__salt__": { -+ "config.get": MagicMock(return_value=True), -+ "cmd.run_all": MagicMock( -+ return_value={"retcode": 0, "stdout": UPGRADE} -+ ), -+ } -+ } -+ ) -+ def test_upgrade(self): -+ """ -+ Test - Upgrades all packages. -+ """ -+ with patch("salt.utils.pkg.clear_rtag", MagicMock()): -+ with patch( -+ "salt.modules.aptpkg.list_pkgs", MagicMock(return_value=UNINSTALL) -+ ): -+ mock_cmd = MagicMock(return_value={"retcode": 0, "stdout": UPGRADE}) -+ patch_kwargs = { -+ "__salt__": { -+ "config.get": MagicMock(return_value=True), -+ "cmd.run_all": mock_cmd, -+ } -+ } -+ with patch.multiple(aptpkg, **patch_kwargs): -+ self.assertEqual(aptpkg.upgrade(), dict()) -+ kwargs = {"force_conf_new": True} -+ self.assertEqual(aptpkg.upgrade(**kwargs), dict()) -+ -+ def test_upgrade_downloadonly(self): -+ """ -+ Tests the download-only options for upgrade. -+ """ -+ with patch("salt.utils.pkg.clear_rtag", MagicMock()): -+ with patch( -+ "salt.modules.aptpkg.list_pkgs", MagicMock(return_value=UNINSTALL) -+ ): -+ mock_cmd = MagicMock(return_value={"retcode": 0, "stdout": UPGRADE}) -+ patch_kwargs = { -+ "__salt__": { -+ "config.get": MagicMock(return_value=True), -+ "cmd.run_all": mock_cmd, -+ }, -+ } -+ with patch.multiple(aptpkg, **patch_kwargs): -+ aptpkg.upgrade() -+ args_matching = [ -+ True -+ for args in patch_kwargs["__salt__"]["cmd.run_all"].call_args[0] -+ if "--download-only" in args -+ ] -+ # Here we shouldn't see the parameter and args_matching should be empty. -+ self.assertFalse(any(args_matching)) -+ -+ aptpkg.upgrade(downloadonly=True) -+ args_matching = [ -+ True -+ for args in patch_kwargs["__salt__"]["cmd.run_all"].call_args[0] -+ if "--download-only" in args -+ ] -+ # --download-only should be in the args list and we should have at least on True in the list. -+ self.assertTrue(any(args_matching)) -+ -+ aptpkg.upgrade(download_only=True) -+ args_matching = [ -+ True -+ for args in patch_kwargs["__salt__"]["cmd.run_all"].call_args[0] -+ if "--download-only" in args -+ ] -+ # --download-only should be in the args list and we should have at least on True in the list. -+ self.assertTrue(any(args_matching)) -+ -+ def test_show(self): -+ """ -+ Test that the pkg.show function properly parses apt-cache show output. -+ This test uses an abridged output per package, for simplicity. -+ """ -+ show_mock_success = MagicMock( -+ return_value={ -+ "retcode": 0, -+ "pid": 12345, -+ "stderr": "", -+ "stdout": textwrap.dedent( -+ """\ -+ Package: foo1.0 -+ Architecture: amd64 -+ Version: 1.0.5-3ubuntu4 -+ Description: A silly package (1.0 release cycle) -+ Provides: foo -+ Suggests: foo-doc -+ -+ Package: foo1.0 -+ Architecture: amd64 -+ Version: 1.0.4-2ubuntu1 -+ Description: A silly package (1.0 release cycle) -+ Provides: foo -+ Suggests: foo-doc -+ -+ Package: foo-doc -+ Architecture: all -+ Version: 1.0.5-3ubuntu4 -+ Description: Silly documentation for a silly package (1.0 release cycle) -+ -+ Package: foo-doc -+ Architecture: all -+ Version: 1.0.4-2ubuntu1 -+ Description: Silly documentation for a silly package (1.0 release cycle) -+ -+ """ -+ ), -+ } -+ ) -+ -+ show_mock_failure = MagicMock( -+ return_value={ -+ "retcode": 1, -+ "pid": 12345, -+ "stderr": textwrap.dedent( -+ """\ -+ N: Unable to locate package foo* -+ N: Couldn't find any package by glob 'foo*' -+ N: Couldn't find any package by regex 'foo*' -+ E: No packages found -+ """ -+ ), -+ "stdout": "", -+ } -+ ) -+ -+ refresh_mock = Mock() -+ -+ expected = { -+ "foo1.0": { -+ "1.0.5-3ubuntu4": { -+ "Architecture": "amd64", -+ "Description": "A silly package (1.0 release cycle)", -+ "Provides": "foo", -+ "Suggests": "foo-doc", -+ }, -+ "1.0.4-2ubuntu1": { -+ "Architecture": "amd64", -+ "Description": "A silly package (1.0 release cycle)", -+ "Provides": "foo", -+ "Suggests": "foo-doc", -+ }, -+ }, -+ "foo-doc": { -+ "1.0.5-3ubuntu4": { -+ "Architecture": "all", -+ "Description": "Silly documentation for a silly package (1.0 release cycle)", -+ }, -+ "1.0.4-2ubuntu1": { -+ "Architecture": "all", -+ "Description": "Silly documentation for a silly package (1.0 release cycle)", -+ }, -+ }, -+ } -+ -+ # Make a copy of the above dict and strip out some keys to produce the -+ # expected filtered result. -+ filtered = copy.deepcopy(expected) -+ for k1 in filtered: -+ for k2 in filtered[k1]: -+ # Using list() because we will modify the dict during iteration -+ for k3 in list(filtered[k1][k2]): -+ if k3 not in ("Description", "Provides"): -+ filtered[k1][k2].pop(k3) -+ -+ with patch.dict( -+ aptpkg.__salt__, {"cmd.run_all": show_mock_success} -+ ), patch.object(aptpkg, "refresh_db", refresh_mock): -+ -+ # Test success (no refresh) -+ self.assertEqual(aptpkg.show("foo*"), expected) -+ refresh_mock.assert_not_called() -+ refresh_mock.reset_mock() -+ -+ # Test success (with refresh) -+ self.assertEqual(aptpkg.show("foo*", refresh=True), expected) -+ self.assert_called_once(refresh_mock) -+ refresh_mock.reset_mock() -+ -+ # Test filtered return -+ self.assertEqual( -+ aptpkg.show("foo*", filter="description,provides"), filtered -+ ) -+ refresh_mock.assert_not_called() -+ refresh_mock.reset_mock() -+ -+ with patch.dict( -+ aptpkg.__salt__, {"cmd.run_all": show_mock_failure} -+ ), patch.object(aptpkg, "refresh_db", refresh_mock): -+ -+ # Test failure (no refresh) -+ self.assertEqual(aptpkg.show("foo*"), {}) -+ refresh_mock.assert_not_called() -+ refresh_mock.reset_mock() -+ -+ # Test failure (with refresh) -+ self.assertEqual(aptpkg.show("foo*", refresh=True), {}) -+ self.assert_called_once(refresh_mock) -+ refresh_mock.reset_mock() -+ -+ def test_mod_repo_enabled(self): -+ """ -+ Checks if a repo is enabled or disabled depending on the passed kwargs. -+ """ -+ with patch.dict( -+ aptpkg.__salt__, -+ {"config.option": MagicMock(), "no_proxy": MagicMock(return_value=False)}, -+ ): -+ with patch("salt.modules.aptpkg._check_apt", MagicMock(return_value=True)): -+ with patch( -+ "salt.modules.aptpkg.refresh_db", MagicMock(return_value={}) -+ ): -+ with patch( -+ "salt.utils.data.is_true", MagicMock(return_value=True) -+ ) as data_is_true: -+ with patch( -+ "salt.modules.aptpkg.sourceslist", MagicMock(), create=True -+ ): -+ repo = aptpkg.mod_repo("foo", enabled=False) -+ data_is_true.assert_called_with(False) -+ # with disabled=True; should call salt.utils.data.is_true True -+ data_is_true.reset_mock() -+ repo = aptpkg.mod_repo("foo", disabled=True) -+ data_is_true.assert_called_with(True) -+ # with enabled=True; should call salt.utils.data.is_true with False -+ data_is_true.reset_mock() -+ repo = aptpkg.mod_repo("foo", enabled=True) -+ data_is_true.assert_called_with(True) -+ # with disabled=True; should call salt.utils.data.is_true False -+ data_is_true.reset_mock() -+ repo = aptpkg.mod_repo("foo", disabled=False) -+ data_is_true.assert_called_with(False) -+ -+ @patch( -+ "salt.utils.path.os_walk", MagicMock(return_value=[("test", "test", "test")]) -+ ) -+ @patch("os.path.getsize", MagicMock(return_value=123456)) -+ @patch("os.path.getctime", MagicMock(return_value=1234567890.123456)) -+ @patch( -+ "fnmatch.filter", -+ MagicMock(return_value=["/var/cache/apt/archive/test_package.rpm"]), -+ ) -+ def test_list_downloaded(self): -+ """ -+ Test downloaded packages listing. -+ :return: -+ """ -+ DOWNLOADED_RET = { -+ "test-package": { -+ "1.0": { -+ "path": "/var/cache/apt/archive/test_package.rpm", -+ "size": 123456, -+ "creation_date_time_t": 1234567890, -+ "creation_date_time": "2009-02-13T23:31:30", -+ } -+ } -+ } -+ -+ with patch.dict( -+ aptpkg.__salt__, -+ { -+ "lowpkg.bin_pkg_info": MagicMock( -+ return_value={"name": "test-package", "version": "1.0"} -+ ) -+ }, -+ ): -+ list_downloaded = aptpkg.list_downloaded() -+ self.assertEqual(len(list_downloaded), 1) -+ self.assertDictEqual(list_downloaded, DOWNLOADED_RET) -+ -+ def test__skip_source(self): -+ """ -+ Test __skip_source. -+ :return: -+ """ -+ # Valid source -+ source_type = "deb" -+ source_uri = "http://cdn-aws.deb.debian.org/debian" -+ source_line = "deb http://cdn-aws.deb.debian.org/debian stretch main\n" -+ -+ mock_source = MockSourceEntry(source_uri, source_type, source_line, False) -+ -+ ret = aptpkg._skip_source(mock_source) -+ self.assertFalse(ret) -+ -+ # Invalid source type -+ source_type = "ded" -+ source_uri = "http://cdn-aws.deb.debian.org/debian" -+ source_line = "deb http://cdn-aws.deb.debian.org/debian stretch main\n" -+ -+ mock_source = MockSourceEntry(source_uri, source_type, source_line, True) -+ -+ ret = aptpkg._skip_source(mock_source) -+ self.assertTrue(ret) -+ -+ # Invalid source type , not skipped -+ source_type = "deb" -+ source_uri = "http://cdn-aws.deb.debian.org/debian" -+ source_line = "deb [http://cdn-aws.deb.debian.org/debian] stretch main\n" -+ -+ mock_source = MockSourceEntry(source_uri, source_type, source_line, True) -+ -+ ret = aptpkg._skip_source(mock_source) -+ self.assertFalse(ret) -+ -+ def test_normalize_name(self): -+ """ -+ Test that package is normalized only when it should be -+ """ -+ with patch.dict(aptpkg.__grains__, {"osarch": "amd64"}): -+ result = aptpkg.normalize_name("foo") -+ assert result == "foo", result -+ result = aptpkg.normalize_name("foo:amd64") -+ assert result == "foo", result -+ result = aptpkg.normalize_name("foo:any") -+ assert result == "foo", result -+ result = aptpkg.normalize_name("foo:i386") -+ assert result == "foo:i386", result -+ -+ def test_list_repos(self): -+ """ -+ Checks results from list_repos -+ """ -+ # Valid source -+ source_type = "deb" -+ source_uri = "http://cdn-aws.deb.debian.org/debian/" -+ source_line = "deb http://cdn-aws.deb.debian.org/debian/ stretch main\n" -+ -+ mock_source = MockSourceEntry(source_uri, source_type, source_line, False) -+ mock_source_list = MockSourceList() -+ mock_source_list.list = [mock_source] -+ -+ with patch("salt.modules.aptpkg._check_apt", MagicMock(return_value=True)): -+ with patch("salt.modules.aptpkg.sourceslist", MagicMock(), create=True): -+ with patch( -+ "salt.modules.aptpkg.sourceslist.SourcesList", -+ MagicMock(return_value=mock_source_list), -+ create=True, -+ ): -+ repos = aptpkg.list_repos() -+ self.assertIn(source_uri, repos) -+ -+ assert isinstance(repos[source_uri], list) -+ assert len(repos[source_uri]) == 1 -+ -+ # Make sure last character in of the URI in line is still a / -+ self.assertIn("line", repos[source_uri][0]) -+ _uri = _get_uri(repos[source_uri][0]["line"]) -+ self.assertEqual(_uri[-1], "/") -+ -+ # Make sure last character in URI is still a / -+ self.assertIn("uri", repos[source_uri][0]) -+ self.assertEqual(repos[source_uri][0]["uri"][-1], "/") -+ -+ def test_expand_repo_def(self): -+ """ -+ Checks results from expand_repo_def -+ """ -+ source_type = "deb" -+ source_uri = "http://cdn-aws.deb.debian.org/debian/" -+ source_line = "deb http://cdn-aws.deb.debian.org/debian/ stretch main\n" -+ source_file = "/etc/apt/sources.list" -+ -+ mock_source = MockSourceEntry( -+ source_uri, source_type, source_line, False, file=source_file -+ ) -+ -+ # Valid source -+ with patch("salt.modules.aptpkg._check_apt", MagicMock(return_value=True)): -+ with patch("salt.modules.aptpkg.sourceslist", MagicMock(), create=True): -+ with patch( -+ "salt.modules.aptpkg.sourceslist.SourceEntry", -+ MagicMock(return_value=mock_source), -+ create=True, -+ ): -+ repo = "deb http://cdn-aws.deb.debian.org/debian/ stretch main\n" -+ sanitized = aptpkg.expand_repo_def(repo=repo, file=source_file) -+ -+ assert isinstance(sanitized, dict) -+ self.assertIn("uri", sanitized) -+ -+ # Make sure last character in of the URI is still a / -+ self.assertEqual(sanitized["uri"][-1], "/") -+ -+ -+@skipIf(pytest is None, "PyTest is missing") -+class AptUtilsTestCase(TestCase, LoaderModuleMockMixin): -+ """ -+ apt utils test case -+ """ -+ -+ def setup_loader_modules(self): -+ return {aptpkg: {}} -+ -+ def test_call_apt_default(self): -+ """ -+ Call default apt. -+ :return: -+ """ -+ with patch.dict( -+ aptpkg.__salt__, -+ {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=False)}, -+ ): -+ aptpkg._call_apt(["apt-get", "install", "emacs"]) # pylint: disable=W0106 -+ aptpkg.__salt__["cmd.run_all"].assert_called_once_with( -+ ["apt-get", "install", "emacs"], -+ env={}, -+ output_loglevel="trace", -+ python_shell=False, -+ ) -+ -+ @patch("salt.utils.systemd.has_scope", MagicMock(return_value=True)) -+ def test_call_apt_in_scope(self): -+ """ -+ Call apt within the scope. -+ :return: -+ """ -+ with patch.dict( -+ aptpkg.__salt__, -+ {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=True)}, -+ ): -+ aptpkg._call_apt(["apt-get", "purge", "vim"]) # pylint: disable=W0106 -+ aptpkg.__salt__["cmd.run_all"].assert_called_once_with( -+ [ -+ "systemd-run", -+ "--scope", -+ "--description", -+ '"salt.modules.aptpkg"', -+ "apt-get", -+ "purge", -+ "vim", -+ ], -+ env={}, -+ output_loglevel="trace", -+ python_shell=False, -+ ) -+ -+ def test_call_apt_with_kwargs(self): -+ """ -+ Call apt with the optinal keyword arguments. -+ :return: -+ """ -+ with patch.dict( -+ aptpkg.__salt__, -+ {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=False)}, -+ ): -+ aptpkg._call_apt( -+ ["dpkg", "-l", "python"], -+ python_shell=True, -+ output_loglevel="quiet", -+ ignore_retcode=False, -+ username="Darth Vader", -+ ) # pylint: disable=W0106 -+ aptpkg.__salt__["cmd.run_all"].assert_called_once_with( -+ ["dpkg", "-l", "python"], -+ env={}, -+ ignore_retcode=False, -+ output_loglevel="quiet", -+ python_shell=True, -+ username="Darth Vader", -+ ) -+ -+ def test_call_apt_dpkg_lock(self): -+ """ -+ Call apt and ensure the dpkg locking is handled -+ :return: -+ """ -+ cmd_side_effect = [ -+ {"stderr": "Could not get lock"}, -+ {"stderr": "Could not get lock"}, -+ {"stderr": "Could not get lock"}, -+ {"stderr": "Could not get lock"}, -+ {"stderr": "", "stdout": ""}, -+ ] -+ -+ cmd_mock = MagicMock(side_effect=cmd_side_effect) -+ cmd_call = ( -+ call( -+ ["dpkg", "-l", "python"], -+ env={}, -+ ignore_retcode=False, -+ output_loglevel="quiet", -+ python_shell=True, -+ username="Darth Vader", -+ ), -+ ) -+ expected_calls = [cmd_call * 5] -+ -+ with patch.dict( -+ aptpkg.__salt__, -+ {"cmd.run_all": cmd_mock, "config.get": MagicMock(return_value=False)}, -+ ): -+ with patch("salt.modules.aptpkg.time.sleep", MagicMock()) as sleep_mock: -+ aptpkg._call_apt( -+ ["dpkg", "-l", "python"], -+ python_shell=True, -+ output_loglevel="quiet", -+ ignore_retcode=False, -+ username="Darth Vader", -+ ) # pylint: disable=W0106 -+ -+ # We should have sleept at least 4 times -+ assert sleep_mock.call_count >= 4 -+ -+ # We should attempt to call the cmd 5 times -+ self.assertEqual(cmd_mock.call_count, 5) -+ cmd_mock.has_calls(expected_calls) -diff --git a/tests/unit/modules/test_dpkg_lowpkg.py b/tests/unit/modules/test_dpkg_lowpkg.py -index 071c0f0742..160bbcd5b1 100644 ---- a/tests/unit/modules/test_dpkg_lowpkg.py -+++ b/tests/unit/modules/test_dpkg_lowpkg.py -@@ -1,18 +1,12 @@ --# -*- coding: utf-8 -*- - """ - :codeauthor: Jayesh Kariya - """ - --# Import Python libs --from __future__ import absolute_import, print_function, unicode_literals - - import logging - import os - --# Import Salt Libs - import salt.modules.dpkg_lowpkg as dpkg -- --# Import Salt Testing Libs - from tests.support.mixins import LoaderModuleMockMixin - from tests.support.mock import MagicMock, patch - from tests.support.unit import TestCase -@@ -65,6 +59,51 @@ class DpkgTestCase(TestCase, LoaderModuleMockMixin): - package = cmd[2] - return DPKG_L_OUTPUT[package] - -+ dselect_pkg = { -+ "emacs": { -+ "priority": "optional", -+ "filename": "pool/main/e/emacs-defaults/emacs_46.1_all.deb", -+ "description": "GNU Emacs editor (metapackage)", -+ "md5sum": "766eb2cee55ba0122dac64c4cea04445", -+ "sha256": "d172289b9a1608820eddad85c7ffc15f346a6e755c3120de0f64739c4bbc44ce", -+ "description-md5": "21fb7da111336097a2378959f6d6e6a8", -+ "bugs": "https://bugs.launchpad.net/springfield/+filebug", -+ "depends": "emacs24 | emacs24-lucid | emacs24-nox", -+ "origin": "Simpsons", -+ "version": "46.1", -+ "task": "ubuntu-usb, edubuntu-usb", -+ "original-maintainer": "Homer Simpson ", -+ "package": "emacs", -+ "architecture": "all", -+ "size": "1692", -+ "sha1": "9271bcec53c1f7373902b1e594d9fc0359616407", -+ "source": "emacs-defaults", -+ "maintainer": "Simpsons Developers ", -+ "supported": "9m", -+ "section": "editors", -+ "installed-size": "25", -+ } ++ "description": "retrieves files from the web", ++ "version": "1.15-1ubuntu1.14.04.2", ++ "architecture": "amd64", ++ "group": "web", ++ "source": "wget", + } ++ mock = MagicMock(return_value=lowpkg_info_var) ++ with patch.dict(aptpkg.__salt__, {"lowpkg.info": mock}): ++ ret = aptpkg.info_installed("wget", attr="foo,bar") ++ assert ret["wget"] == expected_pkg + -+ pkgs_info = [ -+ { -+ "version": "46.1", -+ "arch": "all", -+ "build_date": "2014-08-07T16:51:48Z", -+ "install_date_time_t": 1481745778, -+ "section": "editors", -+ "description": "GNU Emacs editor (metapackage)\n GNU Emacs is the extensible " -+ "self-documenting text editor.\n This is a metapackage that will always " -+ "depend on the latest\n recommended Emacs release.\n", -+ "package": "emacs", -+ "source": "emacs-defaults", -+ "maintainer": "Simpsons Developers ", -+ "build_date_time_t": 1407430308, -+ "installed_size": "25", -+ "install_date": "2016-12-14T20:02:58Z", -+ } -+ ] + - def setup_loader_modules(self): - return {dpkg: {}} - -@@ -269,83 +308,71 @@ class DpkgTestCase(TestCase, LoaderModuleMockMixin): - dpkg.bin_pkg_info("package.deb")["name"], "package_name" - ) - -+ @patch("salt.modules.dpkg._get_pkg_ds_avail", MagicMock(return_value=dselect_pkg)) -+ @patch("salt.modules.dpkg._get_pkg_info", MagicMock(return_value=pkgs_info)) -+ @patch("salt.modules.dpkg._get_pkg_license", MagicMock(return_value="BSD v3")) - def test_info(self): - """ -- Test package info -+ Test info -+ :return: - """ -- mock = MagicMock( -- return_value={ -- "retcode": 0, -- "stderr": "", -- "stdout": os.linesep.join( -- [ -- "package:bash", -- "revision:", -- "architecture:amd64", -- "maintainer:Ubuntu Developers ", -- "summary:", -- "source:bash", -- "version:4.4.18-2ubuntu1", -- "section:shells", -- "installed_size:1588", -- "size:", -- "MD5:", -- "SHA1:", -- "SHA256:", -- "origin:", -- "homepage:http://tiswww.case.edu/php/chet/bash/bashtop.html", -- "status:ii ", -- "======", -- "description:GNU Bourne Again SHell", -- " Bash is an sh-compatible command language interpreter that executes", -- " commands read from the standard input or from a file. Bash also", -- " incorporates useful features from the Korn and C shells (ksh and csh).", -- " .", -- " Bash is ultimately intended to be a conformant implementation of the", -- " IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2).", -- " .", -- " The Programmable Completion Code, by Ian Macdonald, is now found in", -- " the bash-completion package.", -- "------", -- ] -- ), -- } -+ ret = dpkg.info("emacs") ++def test_info_installed_all_versions(lowpkg_info_var): ++ """ ++ Test info_installed 'all_versions'. ++ Since Debian won't return same name packages with the different names, ++ this should just return different structure, backward compatible with ++ the RPM equivalents. + ++ :return: ++ """ ++ expected_pkg = { ++ "url": "http://www.gnu.org/software/wget/", ++ "packager": "Ubuntu Developers ", ++ "name": "wget", ++ "install_date": "2016-08-30T22:20:15Z", ++ "description": "retrieves files from the web", ++ "version": "1.15-1ubuntu1.14.04.2", ++ "architecture": "amd64", ++ "group": "web", ++ "source": "wget", ++ } ++ mock = MagicMock(return_value=lowpkg_info_var) ++ with patch.dict(aptpkg.__salt__, {"lowpkg.info": mock}): ++ ret = aptpkg.info_installed("wget", all_versions=True) + assert isinstance(ret, dict) -+ assert len(ret.keys()) == 1 -+ assert "emacs" in ret ++ assert ret["wget"] == [expected_pkg] + -+ pkg_data = ret["emacs"] + -+ assert isinstance(pkg_data, dict) -+ for pkg_section in [ -+ "section", -+ "architecture", -+ "original-maintainer", -+ "maintainer", -+ "package", -+ "installed-size", -+ "build_date_time_t", -+ "sha256", -+ "origin", -+ "build_date", -+ "size", -+ "source", -+ "version", -+ "install_date_time_t", -+ "license", -+ "priority", -+ "description", -+ "md5sum", -+ "supported", -+ "filename", -+ "sha1", -+ "install_date", -+ "arch", -+ ]: -+ assert pkg_section in pkg_data -+ -+ assert pkg_data["section"] == "editors" -+ assert ( -+ pkg_data["maintainer"] -+ == "Simpsons Developers " - ) -+ assert pkg_data["license"] == "BSD v3" - -- with patch.dict(dpkg.__salt__, {"cmd.run_all": mock}), patch.dict( -- dpkg.__grains__, {"os": "Ubuntu", "osrelease_info": (18, 4)} -- ), patch("salt.utils.path.which", MagicMock(return_value=False)), patch( -- "os.path.exists", MagicMock(return_value=False) -- ), patch( -- "os.path.getmtime", MagicMock(return_value=1560199259.0) -- ): -- self.assertDictEqual( -- dpkg.info("bash"), -- { -- "bash": { -- "architecture": "amd64", -- "description": os.linesep.join( -- [ -- "GNU Bourne Again SHell", -- " Bash is an sh-compatible command language interpreter that executes", -- " commands read from the standard input or from a file. Bash also", -- " incorporates useful features from the Korn and C shells (ksh and csh).", -- " .", -- " Bash is ultimately intended to be a conformant implementation of the", -- " IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2).", -- " .", -- " The Programmable Completion Code, by Ian Macdonald, is now found in", -- " the bash-completion package." + os.linesep, -- ] -- ), -- "homepage": "http://tiswww.case.edu/php/chet/bash/bashtop.html", -- "maintainer": "Ubuntu Developers " -- "", -- "package": "bash", -- "section": "shells", -- "source": "bash", -- "status": "ii", -- "version": "4.4.18-2ubuntu1", -- } -- }, -- ) -+ @patch("salt.modules.dpkg._get_pkg_ds_avail", MagicMock(return_value=dselect_pkg)) -+ @patch("salt.modules.dpkg._get_pkg_info", MagicMock(return_value=pkgs_info)) -+ @patch("salt.modules.dpkg._get_pkg_license", MagicMock(return_value="BSD v3")) -+ def test_info_attr(self): -+ """ -+ Test info with 'attr' parameter -+ :return: -+ """ -+ ret = dpkg.info("emacs", attr="arch,license,version") -+ assert isinstance(ret, dict) -+ assert "emacs" in ret -+ for attr in ["arch", "license", "version"]: -+ assert attr in ret["emacs"] -+ -+ assert ret["emacs"]["arch"] == "all" -+ assert ret["emacs"]["license"] == "BSD v3" -+ assert ret["emacs"]["version"] == "46.1" + def test_owner(): + """ + Test - Return the name of the package that owns the file. -- -2.33.0 +2.34.1 diff --git a/do-not-break-master_tops-for-minion-with-version-low.patch b/do-not-break-master_tops-for-minion-with-version-low.patch deleted file mode 100644 index 37093c3..0000000 --- a/do-not-break-master_tops-for-minion-with-version-low.patch +++ /dev/null @@ -1,37 +0,0 @@ -From a4412799453bf967aa14b93660bfc70f94e11a85 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Thu, 30 Sep 2021 11:06:09 +0100 -Subject: [PATCH] Do not break master_tops for minion with version lower - to 3003 - ---- - salt/master.py | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/salt/master.py b/salt/master.py -index 1bfd278b31..2c5739cc90 100644 ---- a/salt/master.py -+++ b/salt/master.py -@@ -1234,6 +1234,7 @@ class AESFuncs(TransportMethods): - "_dir_list", - "_symlink_list", - "_file_envs", -+ "_ext_nodes", - ) - - def __init__(self, opts): -@@ -1433,6 +1434,9 @@ class AESFuncs(TransportMethods): - return {} - return self.masterapi._master_tops(load, skip_verify=True) - -+ # Needed so older minions can request master_tops -+ _ext_nodes = _master_tops -+ - def _master_opts(self, load): - """ - Return the master options to the minion --- -2.33.0 - - diff --git a/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch b/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch index 9134653..b75a5dc 100644 --- a/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch +++ b/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch @@ -1,60 +1,19 @@ -From f9a66dbf39345b2b371b18e8bf9d89835d6381b7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Mon, 25 Jan 2021 12:15:59 +0000 +From b151f2c1c6b6599b6387ec6e2d32a56e031e3d48 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 19:12:25 +0100 Subject: [PATCH] Do not crash when unexpected cmd output at listing patches (bsc#1181290) Add unit tests to cover unexpected output when listing patches --- - salt/modules/yumpkg.py | 20 +++++++-- tests/pytests/unit/modules/test_yumpkg.py | 53 +++++++++++++++++++++++ - 2 files changed, 70 insertions(+), 3 deletions(-) + 1 file changed, 53 insertions(+) -diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py -index fd79109e40..c800dafa82 100644 ---- a/salt/modules/yumpkg.py -+++ b/salt/modules/yumpkg.py -@@ -3325,10 +3325,17 @@ def _get_patches(installed_only=False): - - cmd = [_yum(), "--quiet", "updateinfo", "list", "all"] - ret = __salt__["cmd.run_stdout"](cmd, python_shell=False, env={"SALT_RUNNING": "1"}) -+ parsing_errors = False -+ - for line in salt.utils.itertools.split(ret, os.linesep): -- inst, advisory_id, sev, pkg = re.match( -- r"([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)", line -- ).groups() -+ try: -+ inst, advisory_id, sev, pkg = re.match( -+ r"([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)", line -+ ).groups() -+ except Exception: # pylint: disable=broad-except -+ parsing_errors = True -+ continue -+ - if advisory_id not in patches: - patches[advisory_id] = { - "installed": True if inst == "i" else False, -@@ -3339,6 +3346,13 @@ def _get_patches(installed_only=False): - if inst != "i": - patches[advisory_id]["installed"] = False - -+ if parsing_errors: -+ log.warning( -+ "Skipped some unexpected output while running '{}' to list patches. Please check output".format( -+ " ".join(cmd) -+ ) -+ ) -+ - if installed_only: - patches = {k: v for k, v in patches.items() if v["installed"]} - return patches diff --git a/tests/pytests/unit/modules/test_yumpkg.py b/tests/pytests/unit/modules/test_yumpkg.py -index ef7100fe9d..df01853927 100644 +index 475e1d6094..3b35272550 100644 --- a/tests/pytests/unit/modules/test_yumpkg.py +++ b/tests/pytests/unit/modules/test_yumpkg.py -@@ -420,6 +420,59 @@ def test_list_patches(): +@@ -433,6 +433,59 @@ def test_list_patches(): assert _patch in patches["my-fake-patch-installed-1234"]["summary"] @@ -115,6 +74,6 @@ index ef7100fe9d..df01853927 100644 with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})): -- -2.33.0 +2.34.1 diff --git a/do-not-monkey-patch-yaml-bsc-1177474.patch b/do-not-monkey-patch-yaml-bsc-1177474.patch deleted file mode 100644 index 34a72b9..0000000 --- a/do-not-monkey-patch-yaml-bsc-1177474.patch +++ /dev/null @@ -1,153 +0,0 @@ -From e82c8832aed9ef46f5021558758ef9d944d89214 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Mon, 8 Mar 2021 12:35:14 +0000 -Subject: [PATCH] Do not monkey patch yaml (bsc#1177474) - -Add changelog file - -Add suggestions by pre-commit - -Add unit test to check for monkey patching ---- - changelog/57995.fixed | 1 + - salt/utils/yamlloader.py | 28 ++++++++++------------------ - tests/unit/utils/test_yamlloader.py | 6 +++++- - 3 files changed, 16 insertions(+), 19 deletions(-) - create mode 100644 changelog/57995.fixed - -diff --git a/changelog/57995.fixed b/changelog/57995.fixed -new file mode 100644 -index 0000000000..78f2cd1fa4 ---- /dev/null -+++ b/changelog/57995.fixed -@@ -0,0 +1 @@ -+Do not monkey patch yaml loaders: Prevent breaking Ansible filter modules -diff --git a/salt/utils/yamlloader.py b/salt/utils/yamlloader.py -index e9d80fc4ad..f98fdcb0e9 100644 ---- a/salt/utils/yamlloader.py -+++ b/salt/utils/yamlloader.py -@@ -1,10 +1,7 @@ --# -*- coding: utf-8 -*- - """ - Custom YAML loading in Salt - """ - --# Import python libs --from __future__ import absolute_import, print_function, unicode_literals - - import warnings - -@@ -13,13 +10,8 @@ import yaml # pylint: disable=blacklisted-import - from yaml.constructor import ConstructorError - from yaml.nodes import MappingNode, SequenceNode - --try: -- yaml.Loader = yaml.CLoader -- yaml.Dumper = yaml.CDumper -- yaml.SafeLoader = yaml.CSafeLoader -- yaml.SafeDumper = yaml.CSafeDumper --except Exception: # pylint: disable=broad-except -- pass -+# prefer C bindings over python when available -+BaseLoader = getattr(yaml, "CSafeLoader", yaml.SafeLoader) - - - __all__ = ["SaltYamlSafeLoader", "load", "safe_load"] -@@ -35,7 +27,7 @@ warnings.simplefilter("always", category=DuplicateKeyWarning) - - - # with code integrated from https://gist.github.com/844388 --class SaltYamlSafeLoader(yaml.SafeLoader): -+class SaltYamlSafeLoader(BaseLoader): - """ - Create a custom YAML loader that uses the custom constructor. This allows - for the YAML loading defaults to be manipulated based on needs within salt -@@ -43,7 +35,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - """ - - def __init__(self, stream, dictclass=dict): -- super(SaltYamlSafeLoader, self).__init__(stream) -+ super().__init__(stream) - if dictclass is not dict: - # then assume ordered dict and use it for both !map and !omap - self.add_constructor("tag:yaml.org,2002:map", type(self).construct_yaml_map) -@@ -74,7 +66,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - raise ConstructorError( - None, - None, -- "expected a mapping node, but found {0}".format(node.id), -+ "expected a mapping node, but found {}".format(node.id), - node.start_mark, - ) - -@@ -90,7 +82,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - raise ConstructorError( - context, - node.start_mark, -- "found unacceptable key {0}".format(key_node.value), -+ "found unacceptable key {}".format(key_node.value), - key_node.start_mark, - ) - value = self.construct_object(value_node, deep=deep) -@@ -98,7 +90,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - raise ConstructorError( - context, - node.start_mark, -- "found conflicting ID '{0}'".format(key), -+ "found conflicting ID '{}'".format(key), - key_node.start_mark, - ) - mapping[key] = value -@@ -118,7 +110,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - # an empty string. Change it to '0'. - if node.value == "": - node.value = "0" -- return super(SaltYamlSafeLoader, self).construct_scalar(node) -+ return super().construct_scalar(node) - - def construct_yaml_str(self, node): - value = self.construct_scalar(node) -@@ -142,7 +134,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - raise ConstructorError( - "while constructing a mapping", - node.start_mark, -- "expected a mapping for merging, but found {0}".format( -+ "expected a mapping for merging, but found {}".format( - subnode.id - ), - subnode.start_mark, -@@ -156,7 +148,7 @@ class SaltYamlSafeLoader(yaml.SafeLoader): - raise ConstructorError( - "while constructing a mapping", - node.start_mark, -- "expected a mapping or list of mappings for merging, but found {0}".format( -+ "expected a mapping or list of mappings for merging, but found {}".format( - value_node.id - ), - value_node.start_mark, -diff --git a/tests/unit/utils/test_yamlloader.py b/tests/unit/utils/test_yamlloader.py -index e1d60aeed0..a5de963817 100644 ---- a/tests/unit/utils/test_yamlloader.py -+++ b/tests/unit/utils/test_yamlloader.py -@@ -5,7 +5,7 @@ - import textwrap - - import salt.utils.files --from salt.utils.yamlloader import SaltYamlSafeLoader -+from salt.utils.yamlloader import SaltYamlSafeLoader, yaml - from tests.support.mock import mock_open, patch - from tests.support.unit import TestCase - from yaml.constructor import ConstructorError -@@ -133,3 +133,7 @@ class YamlLoaderTestCase(TestCase): - ), - {"foo": {"b": {"foo": "bar", "one": 1, "list": [1, "two", 3]}}}, - ) -+ -+ def test_not_yaml_monkey_patching(self): -+ if hasattr(yaml, "CSafeLoader"): -+ assert yaml.SafeLoader != yaml.CSafeLoader --- -2.33.0 - - diff --git a/don-t-call-zypper-with-more-than-one-no-refresh.patch b/don-t-call-zypper-with-more-than-one-no-refresh.patch deleted file mode 100644 index 819bd79..0000000 --- a/don-t-call-zypper-with-more-than-one-no-refresh.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 421988aea296ced1f8c63cfa4b517b25eedfb00c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= -Date: Tue, 29 Jan 2019 09:44:03 +0100 -Subject: [PATCH] Don't call zypper with more than one --no-refresh - -Now zypper started being picky and errors out when --no-refresh is -passed twice. Make sure we won't hit this. ---- - salt/modules/zypperpkg.py | 2 +- - tests/unit/modules/test_zypperpkg.py | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py -index 6fa6e3e0a1..dfaaf420a1 100644 ---- a/salt/modules/zypperpkg.py -+++ b/salt/modules/zypperpkg.py -@@ -300,7 +300,7 @@ class _Zypper: - self.__called = True - if self.__xml: - self.__cmd.append("--xmlout") -- if not self.__refresh: -+ if not self.__refresh and "--no-refresh" not in args: - self.__cmd.append("--no-refresh") - if self.__root: - self.__cmd.extend(["--root", self.__root]) -diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py -index 7bff7065c6..b07f9a3af7 100644 ---- a/tests/unit/modules/test_zypperpkg.py -+++ b/tests/unit/modules/test_zypperpkg.py -@@ -136,7 +136,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): - self.assertEqual(zypper.__zypper__.call("foo"), stdout_xml_snippet) - self.assertEqual(len(sniffer.calls), 1) - -- zypper.__zypper__.call("bar") -+ zypper.__zypper__.call("--no-refresh", "bar") - self.assertEqual(len(sniffer.calls), 2) - self.assertEqual( - sniffer.calls[0]["args"][0], --- -2.29.2 - - diff --git a/early-feature-support-config.patch b/early-feature-support-config.patch index acdcae5..f1d422b 100644 --- a/early-feature-support-config.patch +++ b/early-feature-support-config.patch @@ -1,6 +1,6 @@ -From a20d99db6de68bd4e7a4d2eda4c1c590aea449e7 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Tue, 10 Jul 2018 12:06:33 +0200 +From f24c61d3c1ede64c0ef5c11efeb7d2293e714550 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 16:40:45 +0100 Subject: [PATCH] early feature: support-config Add support script function @@ -527,19 +527,19 @@ Check last function by full name salt/cli/support/profiles/postgres.yml | 11 + salt/cli/support/profiles/salt.yml | 9 + salt/cli/support/profiles/users.yml | 22 + - salt/loader.py | 6 +- + salt/loader/lazy.py | 6 +- salt/modules/saltsupport.py | 405 ++++++++++++++++ salt/scripts.py | 15 + salt/state.py | 38 +- salt/states/saltsupport.py | 225 +++++++++ salt/utils/args.py | 3 +- - salt/utils/decorators/__init__.py | 68 +-- + salt/utils/decorators/__init__.py | 24 + salt/utils/parsers.py | 114 +++++ scripts/salt-support | 11 + setup.py | 2 + tests/unit/cli/test_support.py | 553 +++++++++++++++++++++ tests/unit/modules/test_saltsupport.py | 496 +++++++++++++++++++ - 28 files changed, 2973 insertions(+), 36 deletions(-) + 28 files changed, 2958 insertions(+), 7 deletions(-) create mode 100644 salt/cli/support/__init__.py create mode 100644 salt/cli/support/collector.py create mode 100644 salt/cli/support/console.py @@ -561,10 +561,10 @@ Check last function by full name create mode 100644 tests/unit/modules/test_saltsupport.py diff --git a/doc/ref/modules/all/index.rst b/doc/ref/modules/all/index.rst -index d9501ae4d9..3fff7ad636 100644 +index 43dfb700f9..73958181dd 100644 --- a/doc/ref/modules/all/index.rst +++ b/doc/ref/modules/all/index.rst -@@ -414,6 +414,7 @@ execution modules +@@ -415,6 +415,7 @@ execution modules salt_version saltcheck saltcloudmod @@ -1739,12 +1739,12 @@ index 0000000000..391acdb606 + - group.getent: + info: List of all available groups + output: table -diff --git a/salt/loader.py b/salt/loader.py -index 7a32f4c90d..491768a028 100644 ---- a/salt/loader.py -+++ b/salt/loader.py -@@ -2019,8 +2019,10 @@ class LazyLoader(salt.utils.lazy.LazyDict): - } +diff --git a/salt/loader/lazy.py b/salt/loader/lazy.py +index 48c70d01c0..220641059c 100644 +--- a/salt/loader/lazy.py ++++ b/salt/loader/lazy.py +@@ -950,8 +950,10 @@ class LazyLoader(salt.utils.lazy.LazyDict): + mod_names = [module_name] + list(virtual_aliases) for attr in getattr(mod, "__load__", dir(mod)): - if attr.startswith("_"): @@ -2168,10 +2168,10 @@ index 0000000000..e800e3bf1f + + return __virtualname__ diff --git a/salt/scripts.py b/salt/scripts.py -index 8f3cde8477..e5c248f011 100644 +index 93eab0f702..b1fea566a9 100644 --- a/salt/scripts.py +++ b/salt/scripts.py -@@ -592,3 +592,18 @@ def salt_unity(): +@@ -574,3 +574,18 @@ def salt_unity(): sys.argv.pop(1) s_fun = getattr(sys.modules[__name__], "salt_{}".format(cmd)) s_fun() @@ -2191,10 +2191,10 @@ index 8f3cde8477..e5c248f011 100644 + _install_signal_handlers(client) + client.run() diff --git a/salt/state.py b/salt/state.py -index f999191e6d..f5ac3199c7 100644 +index 91927d9ec6..fa5a578dc6 100644 --- a/salt/state.py +++ b/salt/state.py -@@ -1565,7 +1565,9 @@ class State: +@@ -1577,7 +1577,9 @@ class State: names = [] if state.startswith("__"): continue @@ -2205,7 +2205,7 @@ index f999191e6d..f5ac3199c7 100644 if orchestration_jid is not None: chunk["__orchestration_jid__"] = orchestration_jid if "__sls__" in body: -@@ -2168,9 +2170,16 @@ class State: +@@ -2176,9 +2178,16 @@ class State: ret = self.call_parallel(cdata, low) else: self.format_slots(cdata) @@ -2225,7 +2225,7 @@ index f999191e6d..f5ac3199c7 100644 self.states.inject_globals = {} if ( "check_cmd" in low -@@ -3242,10 +3251,31 @@ class State: +@@ -3252,10 +3261,31 @@ class State: running.update(errors) return running @@ -2510,153 +2510,11 @@ index ba50aff126..4e5ca0eedf 100644 aspec = get_function_argspec(fun, is_class_method=is_class_method) diff --git a/salt/utils/decorators/__init__.py b/salt/utils/decorators/__init__.py -index 940d0a90f2..b06cf0abc8 100644 +index 20803771ed..0aba77e194 100644 --- a/salt/utils/decorators/__init__.py +++ b/salt/utils/decorators/__init__.py -@@ -1,10 +1,7 @@ --# -*- coding: utf-8 -*- - """ - Helpful decorators for module writing - """ - --# Import python libs --from __future__ import absolute_import, print_function, unicode_literals - - import errno - import inspect -@@ -15,13 +12,10 @@ import time - from collections import defaultdict - from functools import wraps - --# Import salt libs - import salt.utils.args - import salt.utils.data - import salt.utils.versions - from salt.exceptions import CommandExecutionError, SaltConfigurationError -- --# Import 3rd-party libs - from salt.ext import six - from salt.log import LOG_LEVELS - -@@ -32,7 +26,7 @@ if getattr(sys, "getwindowsversion", False): - log = logging.getLogger(__name__) - - --class Depends(object): -+class Depends: - """ - This decorator will check the module when it is loaded and check that the - dependencies passed in are in the globals of the module. If not, it will -@@ -121,7 +115,7 @@ class Depends(object): - - @staticmethod - def run_command(dependency, mod_name, func_name): -- full_name = "{0}.{1}".format(mod_name, func_name) -+ full_name = "{}.{}".format(mod_name, func_name) - log.trace("Running '%s' for '%s'", dependency, full_name) - if IS_WINDOWS: - args = salt.utils.args.shlex_split(dependency, posix=False) -@@ -145,8 +139,8 @@ class Depends(object): - It will modify the "functions" dict and remove/replace modules that - are missing dependencies. - """ -- for dependency, dependent_dict in six.iteritems(cls.dependency_dict[kind]): -- for (mod_name, func_name), (frame, params) in six.iteritems(dependent_dict): -+ for dependency, dependent_dict in cls.dependency_dict[kind].items(): -+ for (mod_name, func_name), (frame, params) in dependent_dict.items(): - if mod_name != tgt_mod: - continue - # Imports from local context take presedence over those from the global context. -@@ -232,7 +226,7 @@ class Depends(object): - except (AttributeError, KeyError): - pass - -- mod_key = "{0}.{1}".format(mod_name, func_name) -+ mod_key = "{}.{}".format(mod_name, func_name) - - # if we don't have this module loaded, skip it! - if mod_key not in functions: -@@ -267,9 +261,7 @@ def timing(function): - mod_name = function.__module__[16:] - else: - mod_name = function.__module__ -- fstr = "Function %s.%s took %.{0}f seconds to execute".format( -- sys.float_info.dig -- ) -+ fstr = "Function %s.%s took %.{}f seconds to execute".format(sys.float_info.dig) - log.profile(fstr, mod_name, function.__name__, end_time - start_time) - return ret - -@@ -291,13 +283,13 @@ def memoize(func): - def _memoize(*args, **kwargs): - str_args = [] - for arg in args: -- if not isinstance(arg, six.string_types): -- str_args.append(six.text_type(arg)) -+ if not isinstance(arg, str): -+ str_args.append(str(arg)) - else: - str_args.append(arg) - - args_ = ",".join( -- list(str_args) + ["{0}={1}".format(k, kwargs[k]) for k in sorted(kwargs)] -+ list(str_args) + ["{}={}".format(k, kwargs[k]) for k in sorted(kwargs)] - ) - if args_ not in cache: - cache[args_] = func(*args, **kwargs) -@@ -306,7 +298,7 @@ def memoize(func): - return _memoize - - --class _DeprecationDecorator(object): -+class _DeprecationDecorator: - """ - Base mix-in class for the deprecation decorator. - Takes care of a common functionality, used in its derivatives. -@@ -359,7 +351,7 @@ class _DeprecationDecorator(object): - try: - return self._function(*args, **kwargs) - except TypeError as error: -- error = six.text_type(error).replace( -+ error = str(error).replace( - self._function, self._orig_f_name - ) # Hide hidden functions - log.error( -@@ -374,7 +366,7 @@ class _DeprecationDecorator(object): - self._function.__name__, - error, - ) -- six.reraise(*sys.exc_info()) -+ raise - else: - raise CommandExecutionError( - "Function is deprecated, but the successor function was not found." -@@ -626,11 +618,11 @@ class _WithDeprecated(_DeprecationDecorator): - - if use_deprecated and use_superseded: - raise SaltConfigurationError( -- "Function '{0}' is mentioned both in deprecated " -+ "Function '{}' is mentioned both in deprecated " - "and superseded sections. Please remove any of that.".format(full_name) - ) - old_function = self._globals.get( -- self._with_name or "_{0}".format(function.__name__) -+ self._with_name or "_{}".format(function.__name__) - ) - if self._policy == self.OPT_IN: - self._function = function if use_superseded else old_function -@@ -782,12 +774,30 @@ def ensure_unicode_args(function): - - @wraps(function) - def wrapped(*args, **kwargs): -- if six.PY2: -- return function( -- *salt.utils.data.decode_list(args), -- **salt.utils.data.decode_dict(kwargs) -- ) -- else: -- return function(*args, **kwargs) -+ return function(*args, **kwargs) +@@ -867,3 +867,27 @@ def ensure_unicode_args(function): + return function(*args, **kwargs) return wrapped + @@ -2684,7 +2542,7 @@ index 940d0a90f2..b06cf0abc8 100644 + + return f diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py -index 2a5abb25d5..31a2bf0b64 100644 +index 28660397d4..c0820e5df0 100644 --- a/salt/utils/parsers.py +++ b/salt/utils/parsers.py @@ -17,6 +17,7 @@ import optparse @@ -2703,7 +2561,7 @@ index 2a5abb25d5..31a2bf0b64 100644 import salt.utils.platform import salt.utils.process import salt.utils.stringutils -@@ -2066,6 +2068,118 @@ class SyndicOptionParser( +@@ -2088,6 +2090,118 @@ class SyndicOptionParser( return opts @@ -2840,10 +2698,10 @@ index 0000000000..4e0e79f3ea +if __name__ == "__main__": + salt_support() diff --git a/setup.py b/setup.py -index 031f2bc492..6f3c1abd76 100755 +index af8e448007..2f6dfd6064 100755 --- a/setup.py +++ b/setup.py -@@ -1251,6 +1251,7 @@ class SaltDistribution(distutils.dist.Distribution): +@@ -1253,6 +1253,7 @@ class SaltDistribution(distutils.dist.Distribution): "scripts/salt-master", "scripts/salt-minion", "scripts/salt-proxy", @@ -2851,7 +2709,7 @@ index 031f2bc492..6f3c1abd76 100755 "scripts/salt-ssh", "scripts/salt-syndic", "scripts/salt-unity", -@@ -1297,6 +1298,7 @@ class SaltDistribution(distutils.dist.Distribution): +@@ -1299,6 +1300,7 @@ class SaltDistribution(distutils.dist.Distribution): "salt-key = salt.scripts:salt_key", "salt-master = salt.scripts:salt_master", "salt-minion = salt.scripts:salt_minion", @@ -3921,6 +3779,6 @@ index 0000000000..f9ce7be29a + "00:00:00.000 - The real TTYs became " "pseudo TTYs and vice versa" + ] -- -2.33.0 +2.34.1 diff --git a/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch b/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch index 519ef13..f05d98d 100644 --- a/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch +++ b/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch @@ -1,4 +1,4 @@ -From fec7f65b4debede8cf0eef335182fce2206e200d Mon Sep 17 00:00:00 2001 +From c8f4092f117bd93293e0957422555d3ae7bae999 Mon Sep 17 00:00:00 2001 From: Maximilian Meister Date: Thu, 3 May 2018 15:52:23 +0200 Subject: [PATCH] enable passing a unix_socket for mysql returners @@ -15,19 +15,14 @@ the refactor is done upstream Signed-off-by: Maximilian Meister --- - salt/returners/mysql.py | 63 ++++++++++++++++++++--------------------- - 1 file changed, 30 insertions(+), 33 deletions(-) + salt/returners/mysql.py | 5 +++++ + 1 file changed, 5 insertions(+) diff --git a/salt/returners/mysql.py b/salt/returners/mysql.py -index b7bb05164f..4aa8aeddfa 100644 +index 6fd4fdef2c..7a7e9a3284 100644 --- a/salt/returners/mysql.py +++ b/salt/returners/mysql.py -@@ -1,4 +1,3 @@ --# -*- coding: utf-8 -*- - """ - Return data to a mysql server - -@@ -18,6 +17,7 @@ config. These are the defaults: +@@ -17,6 +17,7 @@ config. These are the defaults: mysql.pass: 'salt' mysql.db: 'salt' mysql.port: 3306 @@ -35,7 +30,7 @@ index b7bb05164f..4aa8aeddfa 100644 SSL is optional. The defaults are set to None. If you do not want to use SSL, either exclude these options or set them to None. -@@ -43,6 +43,7 @@ optional. The following ssl options are simply for illustration purposes: +@@ -42,6 +43,7 @@ optional. The following ssl options are simply for illustration purposes: alternative.mysql.ssl_ca: '/etc/pki/mysql/certs/localhost.pem' alternative.mysql.ssl_cert: '/etc/pki/mysql/certs/localhost.crt' alternative.mysql.ssl_key: '/etc/pki/mysql/certs/localhost.key' @@ -43,30 +38,7 @@ index b7bb05164f..4aa8aeddfa 100644 Should you wish the returner data to be cleaned out every so often, set `keep_jobs` to the number of hours for the jobs to live in the tables. -@@ -138,22 +139,15 @@ To override individual configuration items, append --return_kwargs '{"key:": "va - salt '*' test.ping --return mysql --return_kwargs '{"db": "another-salt"}' - - """ --from __future__ import absolute_import, print_function, unicode_literals - - import logging - import sys -- --# Import python libs - from contextlib import contextmanager - - import salt.exceptions -- --# Import salt libs - import salt.returners - import salt.utils.jid - import salt.utils.json -- --# Import 3rd-party libs - from salt.ext import six - - # Let's not allow PyLint complain about string substitution -@@ -205,6 +199,7 @@ def _get_options(ret=None): +@@ -196,6 +198,7 @@ def _get_options(ret=None): "ssl_ca": None, "ssl_cert": None, "ssl_key": None, @@ -74,7 +46,7 @@ index b7bb05164f..4aa8aeddfa 100644 } attrs = { -@@ -216,6 +211,7 @@ def _get_options(ret=None): +@@ -207,6 +210,7 @@ def _get_options(ret=None): "ssl_ca": "ssl_ca", "ssl_cert": "ssl_cert", "ssl_key": "ssl_key", @@ -82,18 +54,7 @@ index b7bb05164f..4aa8aeddfa 100644 } _options = salt.returners.get_returner_options( -@@ -227,8 +223,8 @@ def _get_options(ret=None): - defaults=defaults, - ) - # post processing -- for k, v in six.iteritems(_options): -- if isinstance(v, six.string_types) and v.lower() == "none": -+ for k, v in _options.items(): -+ if isinstance(v, str) and v.lower() == "none": - # Ensure 'None' is rendered as None - _options[k] = None - if k == "port": -@@ -274,6 +270,7 @@ def _get_serv(ret=None, commit=False): +@@ -265,6 +269,7 @@ def _get_serv(ret=None, commit=False): db=_options.get("db"), port=_options.get("port"), ssl=ssl_options, @@ -101,137 +62,7 @@ index b7bb05164f..4aa8aeddfa 100644 ) try: -@@ -291,9 +288,9 @@ def _get_serv(ret=None, commit=False): - yield cursor - except MySQLdb.DatabaseError as err: - error = err.args -- sys.stderr.write(six.text_type(error)) -+ sys.stderr.write(str(error)) - cursor.execute("ROLLBACK") -- six.reraise(*sys.exc_info()) -+ raise - else: - if commit: - cursor.execute("COMMIT") -@@ -515,8 +512,8 @@ def _purge_jobs(timestamp): - log.error( - "mysql returner archiver was unable to delete contents of table 'jids'" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - - try: - sql = "delete from `salt_returns` where alter_time < %s" -@@ -526,8 +523,8 @@ def _purge_jobs(timestamp): - log.error( - "mysql returner archiver was unable to delete contents of table 'salt_returns'" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - - try: - sql = "delete from `salt_events` where alter_time < %s" -@@ -537,8 +534,8 @@ def _purge_jobs(timestamp): - log.error( - "mysql returner archiver was unable to delete contents of table 'salt_events'" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - - return True - -@@ -556,7 +553,7 @@ def _archive_jobs(timestamp): - for table_name in source_tables: - try: - tmp_table_name = table_name + "_archive" -- sql = "create table if not exists {0} like {1}".format( -+ sql = "create table if not exists {} like {}".format( - tmp_table_name, table_name - ) - cur.execute(sql) -@@ -566,11 +563,11 @@ def _archive_jobs(timestamp): - log.error( - "mysql returner archiver was unable to create the archive tables." - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - - try: -- sql = "insert into `{0}` select * from `{1}` where jid in (select distinct jid from salt_returns where alter_time < %s)".format( -+ sql = "insert into `{}` select * from `{}` where jid in (select distinct jid from salt_returns where alter_time < %s)".format( - target_tables["jids"], "jids" - ) - cur.execute(sql, (timestamp,)) -@@ -579,14 +576,14 @@ def _archive_jobs(timestamp): - log.error( - "mysql returner archiver was unable to copy contents of table 'jids'" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - except Exception as e: # pylint: disable=broad-except - log.error(e) - raise - - try: -- sql = "insert into `{0}` select * from `{1}` where alter_time < %s".format( -+ sql = "insert into `{}` select * from `{}` where alter_time < %s".format( - target_tables["salt_returns"], "salt_returns" - ) - cur.execute(sql, (timestamp,)) -@@ -595,11 +592,11 @@ def _archive_jobs(timestamp): - log.error( - "mysql returner archiver was unable to copy contents of table 'salt_returns'" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - - try: -- sql = "insert into `{0}` select * from `{1}` where alter_time < %s".format( -+ sql = "insert into `{}` select * from `{}` where alter_time < %s".format( - target_tables["salt_events"], "salt_events" - ) - cur.execute(sql, (timestamp,)) -@@ -608,8 +605,8 @@ def _archive_jobs(timestamp): - log.error( - "mysql returner archiver was unable to copy contents of table 'salt_events'" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) - - return _purge_jobs(timestamp) - -@@ -623,7 +620,7 @@ def clean_old_jobs(): - if __opts__.get("keep_jobs", False) and int(__opts__.get("keep_jobs", 0)) > 0: - try: - with _get_serv() as cur: -- sql = "select date_sub(now(), interval {0} hour) as stamp;".format( -+ sql = "select date_sub(now(), interval {} hour) as stamp;".format( - __opts__["keep_jobs"] - ) - cur.execute(sql) -@@ -638,5 +635,5 @@ def clean_old_jobs(): - log.error( - "Mysql returner was unable to get timestamp for purge/archive of jobs" - ) -- log.error(six.text_type(e)) -- raise salt.exceptions.SaltRunnerError(six.text_type(e)) -+ log.error(str(e)) -+ raise salt.exceptions.SaltRunnerError(str(e)) -- -2.29.2 +2.34.1 diff --git a/enhance-openscap-module-add-xccdf_eval-call-386.patch b/enhance-openscap-module-add-xccdf_eval-call-386.patch index fcf4f08..b899270 100644 --- a/enhance-openscap-module-add-xccdf_eval-call-386.patch +++ b/enhance-openscap-module-add-xccdf_eval-call-386.patch @@ -1,4 +1,4 @@ -From 9071189b7395284f0328b59c999c18919e12ae32 Mon Sep 17 00:00:00 2001 +From 933345d049a0207e730ca518dc5f016b0c05d761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Wed, 7 Jul 2021 15:41:48 +0100 @@ -17,9 +17,9 @@ Co-authored-by: Michael Calmer Fix error handling in openscap module (bsc#1188647) (#409) --- changelog/59756.added | 1 + - salt/modules/openscap.py | 126 +++++++++++++-- + salt/modules/openscap.py | 116 +++++++++++++- tests/unit/modules/test_openscap.py | 234 ++++++++++++++++++++++++++++ - 3 files changed, 352 insertions(+), 9 deletions(-) + 3 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 changelog/59756.added diff --git a/changelog/59756.added b/changelog/59756.added @@ -30,41 +30,18 @@ index 0000000000..a59fb21eef @@ -0,0 +1 @@ +adding new call for openscap xccdf eval supporting new parameters diff --git a/salt/modules/openscap.py b/salt/modules/openscap.py -index 6f8ff4a76d..216fd89eef 100644 +index 770c8e7c04..216fd89eef 100644 --- a/salt/modules/openscap.py +++ b/salt/modules/openscap.py -@@ -1,20 +1,15 @@ --# -*- coding: utf-8 -*- - """ - Module for OpenSCAP Management - +@@ -4,6 +4,7 @@ Module for OpenSCAP Management """ --# Import Python libs --from __future__ import absolute_import, print_function, unicode_literals +import os.path import shlex import shutil import tempfile - from subprocess import PIPE, Popen - --# Import Salt libs --from salt.ext import six -- - ArgumentParser = object - - try: -@@ -44,7 +39,7 @@ def __virtual__(): - - class _ArgumentParser(ArgumentParser): - def __init__(self, action=None, *args, **kwargs): -- super(_ArgumentParser, self).__init__(*args, prog="oscap", **kwargs) -+ super().__init__(*args, prog="oscap", **kwargs) - self.add_argument("action", choices=["eval"]) - add_arg = None - for params, kwparams in _XCCDF_MAP["eval"]["parser_arguments"]: -@@ -61,6 +56,117 @@ _OSCAP_EXIT_CODES_MAP = { +@@ -55,6 +56,117 @@ _OSCAP_EXIT_CODES_MAP = { } @@ -182,15 +159,7 @@ index 6f8ff4a76d..216fd89eef 100644 def xccdf(params): """ Run ``oscap xccdf`` commands on minions. -@@ -91,14 +197,16 @@ def xccdf(params): - args, argv = _ArgumentParser(action=action).parse_known_args(args=params) - except Exception as err: # pylint: disable=broad-except - success = False -- error = six.text_type(err) -+ error = str(err) - - if success: - cmd = _XCCDF_MAP[action]["cmd_pattern"].format(args.profile, policy) +@@ -92,7 +204,9 @@ def xccdf(params): tempdir = tempfile.mkdtemp() proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE, cwd=tempdir) (stdoutdata, error) = proc.communicate() @@ -451,6 +420,6 @@ index 045c37f7c9..301c1869ec 100644 + }, + ) -- -2.33.0 +2.34.1 diff --git a/figure-out-python-interpreter-to-use-inside-containe.patch b/figure-out-python-interpreter-to-use-inside-containe.patch deleted file mode 100644 index 3126396..0000000 --- a/figure-out-python-interpreter-to-use-inside-containe.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 1b54843abe5fad0bac844d6d5d9707df3e501aae Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Wed, 19 May 2021 16:24:27 +0100 -Subject: [PATCH] Figure out Python interpreter to use inside containers - -Fix unit test for dockermod.call function ---- - salt/modules/dockermod.py | 28 +++++++++++++++++++++++--- - tests/unit/modules/test_dockermod.py | 30 +++++++++++++++------------- - 2 files changed, 41 insertions(+), 17 deletions(-) - -diff --git a/salt/modules/dockermod.py b/salt/modules/dockermod.py -index ab2296a945..6d60a9a5aa 100644 ---- a/salt/modules/dockermod.py -+++ b/salt/modules/dockermod.py -@@ -209,7 +209,6 @@ import re - import shutil - import string - import subprocess --import sys - import time - import uuid - -@@ -6728,9 +6727,32 @@ def call(name, function, *args, **kwargs): - name, thin_path, os.path.join(thin_dest_path, os.path.basename(thin_path)) - ) - -+ # figure out available python interpreter inside the container -+ pycmds = ( -+ "python3", -+ "/usr/libexec/platform-python", -+ "python27", -+ "python2.7", -+ "python26", -+ "python2.6", -+ "python2", -+ "python", -+ ) -+ container_python_bin = None -+ for py_cmd in pycmds: -+ cmd = [py_cmd] + ["--version"] -+ ret = run_all(name, subprocess.list2cmdline(cmd)) -+ if ret["retcode"] == 0: -+ container_python_bin = py_cmd -+ break -+ if not container_python_bin: -+ raise CommandExecutionError( -+ "Python interpreter cannot be found inside the container. Make sure Python is installed in the container" -+ ) -+ - # untar archive - untar_cmd = [ -- "python", -+ container_python_bin, - "-c", - "import tarfile; " - 'tarfile.open("{0}/{1}").extractall(path="{0}")'.format( -@@ -6744,7 +6766,7 @@ def call(name, function, *args, **kwargs): - try: - salt_argv = ( - [ -- "python{}".format(sys.version_info[0]), -+ container_python_bin, - os.path.join(thin_dest_path, "salt-call"), - "--metadata", - "--local", -diff --git a/tests/unit/modules/test_dockermod.py b/tests/unit/modules/test_dockermod.py -index 2c3665de85..fcedaf9272 100644 ---- a/tests/unit/modules/test_dockermod.py -+++ b/tests/unit/modules/test_dockermod.py -@@ -987,33 +987,35 @@ class DockerTestCase(TestCase, LoaderModuleMockMixin): - # [ call(name, [args]), ... - self.maxDiff = None - self.assertIn("mkdir", docker_run_all_mock.mock_calls[0][1][1]) -- self.assertIn("mkdir", docker_run_all_mock.mock_calls[4][1][1]) -+ self.assertIn("mkdir", docker_run_all_mock.mock_calls[5][1][1]) - self.assertNotEqual( - docker_run_all_mock.mock_calls[0][1][1], -- docker_run_all_mock.mock_calls[4][1][1], -+ docker_run_all_mock.mock_calls[5][1][1], - ) - -- self.assertIn("salt-call", docker_run_all_mock.mock_calls[2][1][1]) -- self.assertIn("salt-call", docker_run_all_mock.mock_calls[6][1][1]) -+ self.assertEqual("python3 --version", docker_run_all_mock.mock_calls[1][1][1]) -+ -+ self.assertIn("salt-call", docker_run_all_mock.mock_calls[3][1][1]) -+ self.assertIn("salt-call", docker_run_all_mock.mock_calls[8][1][1]) - self.assertNotEqual( -- docker_run_all_mock.mock_calls[2][1][1], -- docker_run_all_mock.mock_calls[6][1][1], -+ docker_run_all_mock.mock_calls[3][1][1], -+ docker_run_all_mock.mock_calls[8][1][1], - ) - - # check thin untar -- self.assertIn("tarfile", docker_run_all_mock.mock_calls[1][1][1]) -- self.assertIn("tarfile", docker_run_all_mock.mock_calls[5][1][1]) -+ self.assertIn("tarfile", docker_run_all_mock.mock_calls[2][1][1]) -+ self.assertIn("tarfile", docker_run_all_mock.mock_calls[7][1][1]) - self.assertNotEqual( -- docker_run_all_mock.mock_calls[1][1][1], -- docker_run_all_mock.mock_calls[5][1][1], -+ docker_run_all_mock.mock_calls[2][1][1], -+ docker_run_all_mock.mock_calls[7][1][1], - ) - - # check directory cleanup -- self.assertIn("rm -rf", docker_run_all_mock.mock_calls[3][1][1]) -- self.assertIn("rm -rf", docker_run_all_mock.mock_calls[7][1][1]) -+ self.assertIn("rm -rf", docker_run_all_mock.mock_calls[4][1][1]) -+ self.assertIn("rm -rf", docker_run_all_mock.mock_calls[9][1][1]) - self.assertNotEqual( -- docker_run_all_mock.mock_calls[3][1][1], -- docker_run_all_mock.mock_calls[7][1][1], -+ docker_run_all_mock.mock_calls[4][1][1], -+ docker_run_all_mock.mock_calls[9][1][1], - ) - - self.assertEqual({"retcode": 0, "comment": "container cmd"}, ret) --- -2.33.0 - - diff --git a/fix-a-test-and-some-variable-names-229.patch b/fix-a-test-and-some-variable-names-229.patch deleted file mode 100644 index 74670da..0000000 --- a/fix-a-test-and-some-variable-names-229.patch +++ /dev/null @@ -1,29 +0,0 @@ -From daf29460408a5e0eb042b3c234c7e21a6b994cf1 Mon Sep 17 00:00:00 2001 -From: Alberto Planas -Date: Tue, 12 May 2020 14:16:23 +0200 -Subject: [PATCH] Fix a test and some variable names (#229) - -* loop: fix variable names for until_no_eval - -* Fix test_core tests for fqdns errors ---- - tests/unit/grains/test_core.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py -index 196dbcf83d..918a9155cb 100644 ---- a/tests/unit/grains/test_core.py -+++ b/tests/unit/grains/test_core.py -@@ -1416,7 +1416,7 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - with patch("salt.modules.network.log", mock_log): - self.assertEqual(core.fqdns(), {"fqdns": []}) - mock_log.debug.assert_called_once() -- mock_log.error.assert_called() -+ mock_log.error.assert_called_once() - - @patch.object(salt.utils.platform, "is_windows", MagicMock(return_value=False)) - @patch( --- -2.29.2 - - diff --git a/fix-aptpkg.normalize_name-when-package-arch-is-all.patch b/fix-aptpkg.normalize_name-when-package-arch-is-all.patch deleted file mode 100644 index 85d98de..0000000 --- a/fix-aptpkg.normalize_name-when-package-arch-is-all.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 763d63b72b9a20f22555b665033899e10f091b60 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Mon, 11 Jan 2021 15:45:28 +0000 -Subject: [PATCH] Fix aptpkg.normalize_name when package arch is 'all' - -Add test case of DEB package where arch is 'all' ---- - salt/modules/aptpkg.py | 2 +- - tests/unit/modules/test_aptpkg.py | 2 ++ - 2 files changed, 3 insertions(+), 1 deletion(-) - -diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py -index e001d2f11c..03e99af733 100644 ---- a/salt/modules/aptpkg.py -+++ b/salt/modules/aptpkg.py -@@ -208,7 +208,7 @@ def normalize_name(name): - pkgname = name - pkgarch = __grains__["osarch"] - -- return pkgname if pkgarch in (__grains__["osarch"], "any") else name -+ return pkgname if pkgarch in (__grains__["osarch"], "all", "any") else name - - - def parse_arch(name): -diff --git a/tests/unit/modules/test_aptpkg.py b/tests/unit/modules/test_aptpkg.py -index 51dfce29eb..eb3f9e2da7 100644 ---- a/tests/unit/modules/test_aptpkg.py -+++ b/tests/unit/modules/test_aptpkg.py -@@ -808,6 +808,8 @@ class AptPkgTestCase(TestCase, LoaderModuleMockMixin): - assert result == "foo", result - result = aptpkg.normalize_name("foo:any") - assert result == "foo", result -+ result = aptpkg.normalize_name("foo:all") -+ assert result == "foo", result - result = aptpkg.normalize_name("foo:i386") - assert result == "foo:i386", result - --- -2.29.2 - - diff --git a/fix-exception-in-yumpkg.remove-for-not-installed-pac.patch b/fix-exception-in-yumpkg.remove-for-not-installed-pac.patch index 4108f23..89d783c 100644 --- a/fix-exception-in-yumpkg.remove-for-not-installed-pac.patch +++ b/fix-exception-in-yumpkg.remove-for-not-installed-pac.patch @@ -1,4 +1,4 @@ -From 9413059223107924c6594e6c72e50fcbcc441e60 Mon Sep 17 00:00:00 2001 +From 40d9cde9b90965e60520f36dbe189fb64d15559d Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> Date: Thu, 24 Jun 2021 13:17:13 +0300 Subject: [PATCH] Fix exception in yumpkg.remove for not installed @@ -10,23 +10,23 @@ Subject: [PATCH] Fix exception in yumpkg.remove for not installed 2 files changed, 39 insertions(+) diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py -index dd81c6f1e9..273f0fb370 100644 +index 9737508377..9f8f548e5f 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py -@@ -2087,6 +2087,8 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 - old = list_pkgs() - targets = [] +@@ -2123,6 +2123,8 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 + pkg_params.update(pkg_matches) + for target in pkg_params: + if target not in old: + continue version_to_remove = pkg_params[target] - installed_versions = old[target].split(",") + # Check if package version set to be removed is actually installed: diff --git a/tests/pytests/unit/modules/test_yumpkg.py b/tests/pytests/unit/modules/test_yumpkg.py -index 7e3ed517ea..b5572db123 100644 +index 3b35272550..c3456f7e29 100644 --- a/tests/pytests/unit/modules/test_yumpkg.py +++ b/tests/pytests/unit/modules/test_yumpkg.py -@@ -1219,6 +1219,43 @@ def test_install_error_reporting(): +@@ -1284,6 +1284,43 @@ def test_install_error_reporting(): assert exc_info.value.info == expected, exc_info.value.info @@ -71,6 +71,6 @@ index 7e3ed517ea..b5572db123 100644 with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})), patch( "salt.utils.systemd.has_scope", MagicMock(return_value=False) -- -2.33.0 +2.34.1 diff --git a/fix-issues-with-salt-ssh-s-extra-filerefs.patch b/fix-issues-with-salt-ssh-s-extra-filerefs.patch index 5f476a7..7573a38 100644 --- a/fix-issues-with-salt-ssh-s-extra-filerefs.patch +++ b/fix-issues-with-salt-ssh-s-extra-filerefs.patch @@ -1,4 +1,4 @@ -From ef433d6f02af87d45363ae07fe438a1d7747df13 Mon Sep 17 00:00:00 2001 +From a268bfee70fabffc6d8fb6c297cd255fb3483ae1 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Thu, 7 Oct 2021 17:22:37 -0700 Subject: [PATCH] Fix issues with salt-ssh's extra-filerefs @@ -7,12 +7,9 @@ Verify salt-ssh can import from map files in states Add changelog for 60003.fixed --- - changelog/60003.fixed | 1 + - salt/client/ssh/__init__.py | 1 + - tests/pytests/integration/ssh/test_state.py | 94 +++++++++++++++++++++ - 3 files changed, 96 insertions(+) + changelog/60003.fixed | 1 + + 1 file changed, 1 insertion(+) create mode 100644 changelog/60003.fixed - create mode 100644 tests/pytests/integration/ssh/test_state.py diff --git a/changelog/60003.fixed b/changelog/60003.fixed new file mode 100644 @@ -21,119 +18,7 @@ index 0000000000..6fafbf5108 +++ b/changelog/60003.fixed @@ -0,0 +1 @@ +Validate we can import map files in states -diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py -index 409d6e740e..76c57996d9 100644 ---- a/salt/client/ssh/__init__.py -+++ b/salt/client/ssh/__init__.py -@@ -1161,6 +1161,7 @@ class Single: - opts_pkg["_ssh_version"] = self.opts["_ssh_version"] - opts_pkg["thin_dir"] = self.opts["thin_dir"] - opts_pkg["master_tops"] = self.opts["master_tops"] -+ opts_pkg["extra_filerefs"] = self.opts.get("extra_filerefs", "") - opts_pkg["__master_opts__"] = self.context["master_opts"] - if "known_hosts_file" in self.opts: - opts_pkg["known_hosts_file"] = self.opts["known_hosts_file"] -diff --git a/tests/pytests/integration/ssh/test_state.py b/tests/pytests/integration/ssh/test_state.py -new file mode 100644 -index 0000000000..58330a5dd8 ---- /dev/null -+++ b/tests/pytests/integration/ssh/test_state.py -@@ -0,0 +1,94 @@ -+import pytest -+ -+pytestmark = [ -+ pytest.mark.skip_on_windows(reason="salt-ssh not available on Windows"), -+] -+ -+ -+@pytest.fixture(scope="module") -+def state_tree(base_env_state_tree_root_dir): -+ top_file = """ -+ base: -+ 'localhost': -+ - basic -+ '127.0.0.1': -+ - basic -+ """ -+ map_file = """ -+ {%- set abc = "def" %} -+ """ -+ state_file = """ -+ {%- from "map.jinja" import abc with context %} -+ -+ Ok with {{ abc }}: -+ test.succeed_without_changes -+ """ -+ top_tempfile = pytest.helpers.temp_file( -+ "top.sls", top_file, base_env_state_tree_root_dir -+ ) -+ map_tempfile = pytest.helpers.temp_file( -+ "map.jinja", map_file, base_env_state_tree_root_dir -+ ) -+ state_tempfile = pytest.helpers.temp_file( -+ "test.sls", state_file, base_env_state_tree_root_dir -+ ) -+ -+ with top_tempfile, map_tempfile, state_tempfile: -+ yield -+ -+ -+@pytest.mark.slow_test -+def test_state_with_import(salt_ssh_cli, state_tree): -+ """ -+ verify salt-ssh can use imported map files in states -+ """ -+ ret = salt_ssh_cli.run("state.sls", "test") -+ assert ret.exitcode == 0 -+ assert ret.json -+ -+ -+@pytest.fixture -+def nested_state_tree(base_env_state_tree_root_dir, tmpdir): -+ top_file = """ -+ base: -+ 'localhost': -+ - basic -+ '127.0.0.1': -+ - basic -+ """ -+ state_file = """ -+ /{}/file.txt: -+ file.managed: -+ - source: salt://foo/file.jinja -+ - template: jinja -+ """.format( -+ tmpdir -+ ) -+ file_jinja = """ -+ {% from 'foo/map.jinja' import comment %}{{ comment }} -+ """ -+ map_file = """ -+ {% set comment = "blah blah" %} -+ """ -+ statedir = base_env_state_tree_root_dir / "foo" -+ top_tempfile = pytest.helpers.temp_file( -+ "top.sls", top_file, base_env_state_tree_root_dir -+ ) -+ map_tempfile = pytest.helpers.temp_file("map.jinja", map_file, statedir) -+ file_tempfile = pytest.helpers.temp_file("file.jinja", file_jinja, statedir) -+ state_tempfile = pytest.helpers.temp_file("init.sls", state_file, statedir) -+ -+ with top_tempfile, map_tempfile, state_tempfile, file_tempfile: -+ yield -+ -+ -+@pytest.mark.slow_test -+def test_state_with_import_from_dir(salt_ssh_cli, nested_state_tree): -+ """ -+ verify salt-ssh can use imported map files in states -+ """ -+ ret = salt_ssh_cli.run( -+ "--extra-filerefs=salt://foo/map.jinja", "state.apply", "foo" -+ ) -+ assert ret.exitcode == 0 -+ assert ret.json -- -2.33.0 +2.34.1 diff --git a/fix-save-for-iptables-state-module-bsc-1185131-372.patch b/fix-save-for-iptables-state-module-bsc-1185131-372.patch deleted file mode 100644 index 0df6e4c..0000000 --- a/fix-save-for-iptables-state-module-bsc-1185131-372.patch +++ /dev/null @@ -1,433 +0,0 @@ -From 944f2a8e4db522ad32f547cf350a1268caa6de5a Mon Sep 17 00:00:00 2001 -From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> -Date: Thu, 24 Jun 2021 13:18:51 +0300 -Subject: [PATCH] Fix save for iptables state module (bsc#1185131) - (#372) - ---- - salt/states/iptables.py | 86 ++++++++------ - tests/unit/states/test_iptables.py | 184 ++++++++++++++++++++++++++++- - 2 files changed, 227 insertions(+), 43 deletions(-) - -diff --git a/salt/states/iptables.py b/salt/states/iptables.py -index 61dfc7e665..2e81477f18 100644 ---- a/salt/states/iptables.py -+++ b/salt/states/iptables.py -@@ -401,7 +401,7 @@ def append(name, table="filter", family="ipv4", **kwargs): - if save: - if save_file is True: - save_file = None -- __salt__["iptables.save"](save_file, family=family) -+ __salt__["iptables.save"](filename=save_file, family=family) - if not ret["changes"]["locale"]: - del ret["changes"]["locale"] - ret["comment"] = "\n".join(comments) -@@ -426,7 +426,9 @@ def append(name, table="filter", family="ipv4", **kwargs): - filename = kwargs["save"] - else: - filename = None -- saved_rules = __salt__["iptables.get_saved_rules"](family=family) -+ saved_rules = __salt__["iptables.get_saved_rules"]( -+ conf_file=filename, family=family -+ ) - _rules = __salt__["iptables.get_rules"](family=family) - __rules = [] - for table in _rules: -@@ -438,7 +440,7 @@ def append(name, table="filter", family="ipv4", **kwargs): - __saved_rules.append(saved_rules[table][chain].get("rules")) - # Only save if rules in memory are different than saved rules - if __rules != __saved_rules: -- out = __salt__["iptables.save"](filename, family=family) -+ out = __salt__["iptables.save"](filename=filename, family=family) - ret["comment"] += ("\nSaved iptables rule {} for {}\n" "{}\n{}").format( - name, family, command.strip(), out - ) -@@ -454,16 +456,15 @@ def append(name, table="filter", family="ipv4", **kwargs): - ret["comment"] = "Set iptables rule for {} to: {} for {}".format( - name, command.strip(), family - ) -- if "save" in kwargs: -- if kwargs["save"]: -- if kwargs["save"] is not True: -- filename = kwargs["save"] -- else: -- filename = None -- out = __salt__["iptables.save"](filename, family=family) -- ret["comment"] = ( -- "Set and saved iptables rule {} for {}\n" "{}\n{}" -- ).format(name, family, command.strip(), out) -+ if "save" in kwargs and kwargs["save"]: -+ if kwargs["save"] is not True: -+ filename = kwargs["save"] -+ else: -+ filename = None -+ out = __salt__["iptables.save"](filename=filename, family=family) -+ ret["comment"] = ( -+ "Set and saved iptables rule {} for {}\n" "{}\n{}" -+ ).format(name, family, command.strip(), out) - return ret - else: - ret["result"] = False -@@ -527,7 +528,7 @@ def insert(name, table="filter", family="ipv4", **kwargs): - if save: - if save_file is True: - save_file = None -- __salt__["iptables.save"](save_file, family=family) -+ __salt__["iptables.save"](filename=save_file, family=family) - if not ret["changes"]["locale"]: - del ret["changes"]["locale"] - ret["comment"] = "\n".join(comments) -@@ -552,7 +553,9 @@ def insert(name, table="filter", family="ipv4", **kwargs): - filename = kwargs["save"] - else: - filename = None -- saved_rules = __salt__["iptables.get_saved_rules"](family=family) -+ saved_rules = __salt__["iptables.get_saved_rules"]( -+ conf_file=filename, family=family -+ ) - _rules = __salt__["iptables.get_rules"](family=family) - __rules = [] - for table in _rules: -@@ -564,7 +567,7 @@ def insert(name, table="filter", family="ipv4", **kwargs): - __saved_rules.append(saved_rules[table][chain].get("rules")) - # Only save if rules in memory are different than saved rules - if __rules != __saved_rules: -- out = __salt__["iptables.save"](filename, family=family) -+ out = __salt__["iptables.save"](filename=filename, family=family) - ret["comment"] += ("\nSaved iptables rule {} for {}\n" "{}\n{}").format( - name, family, command.strip(), out - ) -@@ -582,12 +585,15 @@ def insert(name, table="filter", family="ipv4", **kwargs): - ret["comment"] = "Set iptables rule for {} to: {} for {}".format( - name, command.strip(), family - ) -- if "save" in kwargs: -- if kwargs["save"]: -- out = __salt__["iptables.save"](filename=None, family=family) -- ret["comment"] = ( -- "Set and saved iptables rule {} for {}\n" "{}\n{}" -- ).format(name, family, command.strip(), out) -+ if "save" in kwargs and kwargs["save"]: -+ if kwargs["save"] is not True: -+ filename = kwargs["save"] -+ else: -+ filename = None -+ out = __salt__["iptables.save"](filename=filename, family=family) -+ ret["comment"] = ( -+ "Set and saved iptables rule {} for {}\n" "{}\n{}" -+ ).format(name, family, command.strip(), out) - return ret - else: - ret["result"] = False -@@ -646,7 +652,7 @@ def delete(name, table="filter", family="ipv4", **kwargs): - if save: - if save_file is True: - save_file = None -- __salt__["iptables.save"](save_file, family=family) -+ __salt__["iptables.save"](filename=save_file, family=family) - if not ret["changes"]["locale"]: - del ret["changes"]["locale"] - ret["comment"] = "\n".join(comments) -@@ -688,12 +694,15 @@ def delete(name, table="filter", family="ipv4", **kwargs): - ret["changes"] = {"locale": name} - ret["result"] = True - ret["comment"] = "Delete iptables rule for {} {}".format(name, command.strip()) -- if "save" in kwargs: -- if kwargs["save"]: -- out = __salt__["iptables.save"](filename=None, family=family) -- ret["comment"] = ( -- "Deleted and saved iptables rule {} for {}\n" "{}\n{}" -- ).format(name, family, command.strip(), out) -+ if "save" in kwargs and kwargs["save"]: -+ if kwargs["save"] is not True: -+ filename = kwargs["save"] -+ else: -+ filename = None -+ out = __salt__["iptables.save"](filename=filename, family=family) -+ ret["comment"] = ( -+ "Deleted and saved iptables rule {} for {}\n" "{}\n{}" -+ ).format(name, family, command.strip(), out) - return ret - else: - ret["result"] = False -@@ -751,14 +760,17 @@ def set_policy(name, table="filter", family="ipv4", **kwargs): - ret["comment"] = "Set default policy for {} to {} family {}".format( - kwargs["chain"], kwargs["policy"], family - ) -- if "save" in kwargs: -- if kwargs["save"]: -- __salt__["iptables.save"](filename=None, family=family) -- ret[ -- "comment" -- ] = "Set and saved default policy for {} to {} family {}".format( -- kwargs["chain"], kwargs["policy"], family -- ) -+ if "save" in kwargs and kwargs["save"]: -+ if kwargs["save"] is not True: -+ filename = kwargs["save"] -+ else: -+ filename = None -+ __salt__["iptables.save"](filename=filename, family=family) -+ ret[ -+ "comment" -+ ] = "Set and saved default policy for {} to {} family {}".format( -+ kwargs["chain"], kwargs["policy"], family -+ ) - return ret - else: - ret["result"] = False -diff --git a/tests/unit/states/test_iptables.py b/tests/unit/states/test_iptables.py -index c49022c962..975ae49c3e 100644 ---- a/tests/unit/states/test_iptables.py -+++ b/tests/unit/states/test_iptables.py -@@ -135,7 +135,7 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - with patch.object(iptables, "_STATE_INTERNAL_KEYWORDS", mock): - mock = MagicMock(return_value="a") - with patch.dict(iptables.__salt__, {"iptables.build_rule": mock}): -- mock = MagicMock(side_effect=[True, False, False, False]) -+ mock = MagicMock(side_effect=[True, False, False, False, False, True]) - with patch.dict(iptables.__salt__, {"iptables.check": mock}): - ret.update( - { -@@ -161,7 +161,7 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - ) - - with patch.dict(iptables.__opts__, {"test": False}): -- mock = MagicMock(side_effect=[True, False]) -+ mock = MagicMock(side_effect=[True, False, True, True]) - with patch.dict(iptables.__salt__, {"iptables.append": mock}): - ret.update( - { -@@ -188,6 +188,65 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - iptables.append("salt", table="", chain=""), ret - ) - -+ mock_save = MagicMock( -+ side_effect=['Wrote 1 lines to "/tmp/iptables"', ""] -+ ) -+ with patch.dict( -+ iptables.__salt__, {"iptables.save": mock_save} -+ ): -+ mock_get_saved_rules = MagicMock(side_effect=[""]) -+ with patch.dict( -+ iptables.__salt__, -+ {"iptables.get_saved_rules": mock_get_saved_rules}, -+ ): -+ mock = MagicMock(side_effect=[""]) -+ with patch.dict( -+ iptables.__salt__, {"iptables.get_rules": mock} -+ ): -+ ret.update( -+ { -+ "changes": {"locale": "salt"}, -+ "result": True, -+ "comment": "Set and saved iptables rule" -+ ' salt for ipv4\na\nWrote 1 lines to "/tmp/iptables"', -+ } -+ ) -+ self.assertDictEqual( -+ iptables.append( -+ "salt", -+ table="", -+ chain="", -+ save="/tmp/iptables", -+ ), -+ ret, -+ ) -+ ret.update( -+ { -+ "changes": {}, -+ "result": True, -+ "comment": "iptables rule for salt already set (a) for ipv4", -+ } -+ ) -+ self.assertDictEqual( -+ iptables.append( -+ "salt", -+ table="", -+ chain="", -+ save="/tmp/iptables", -+ ), -+ ret, -+ ) -+ self.assertEqual( -+ mock_get_saved_rules.mock_calls[0][2][ -+ "conf_file" -+ ], -+ "/tmp/iptables", -+ ) -+ self.assertEqual( -+ mock_save.mock_calls[0][2]["filename"], -+ "/tmp/iptables", -+ ) -+ - def test_insert(self): - """ - Test to insert a rule into a chain -@@ -200,7 +259,7 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - with patch.object(iptables, "_STATE_INTERNAL_KEYWORDS", mock): - mock = MagicMock(return_value="a") - with patch.dict(iptables.__salt__, {"iptables.build_rule": mock}): -- mock = MagicMock(side_effect=[True, False, False, False]) -+ mock = MagicMock(side_effect=[True, False, False, False, False, True]) - with patch.dict(iptables.__salt__, {"iptables.check": mock}): - ret.update( - { -@@ -226,7 +285,7 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - ) - - with patch.dict(iptables.__opts__, {"test": False}): -- mock = MagicMock(side_effect=[False, True]) -+ mock = MagicMock(side_effect=[False, True, False, True]) - with patch.dict(iptables.__salt__, {"iptables.insert": mock}): - ret.update( - { -@@ -258,6 +317,67 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - ret, - ) - -+ mock_save = MagicMock( -+ side_effect=['Wrote 1 lines to "/tmp/iptables"', ""] -+ ) -+ with patch.dict( -+ iptables.__salt__, {"iptables.save": mock_save} -+ ): -+ mock_get_saved_rules = MagicMock(side_effect=[""]) -+ with patch.dict( -+ iptables.__salt__, -+ {"iptables.get_saved_rules": mock_get_saved_rules}, -+ ): -+ mock = MagicMock(side_effect=[""]) -+ with patch.dict( -+ iptables.__salt__, {"iptables.get_rules": mock} -+ ): -+ ret.update( -+ { -+ "changes": {"locale": "salt"}, -+ "result": True, -+ "comment": "Set and saved iptables rule" -+ ' salt for ipv4\na\nWrote 1 lines to "/tmp/iptables"', -+ } -+ ) -+ self.assertDictEqual( -+ iptables.insert( -+ "salt", -+ table="", -+ chain="", -+ position="", -+ save="/tmp/iptables", -+ ), -+ ret, -+ ) -+ ret.update( -+ { -+ "changes": {}, -+ "result": True, -+ "comment": "iptables rule for salt already set for ipv4 (a)", -+ } -+ ) -+ self.assertDictEqual( -+ iptables.insert( -+ "salt", -+ table="", -+ chain="", -+ position="", -+ save="/tmp/iptables", -+ ), -+ ret, -+ ) -+ self.assertEqual( -+ mock_get_saved_rules.mock_calls[0][2][ -+ "conf_file" -+ ], -+ "/tmp/iptables", -+ ) -+ self.assertEqual( -+ mock_save.mock_calls[0][2]["filename"], -+ "/tmp/iptables", -+ ) -+ - def test_delete(self): - """ - Test to delete a rule to a chain -@@ -270,7 +390,7 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - with patch.object(iptables, "_STATE_INTERNAL_KEYWORDS", mock): - mock = MagicMock(return_value="a") - with patch.dict(iptables.__salt__, {"iptables.build_rule": mock}): -- mock = MagicMock(side_effect=[False, True, True, True]) -+ mock = MagicMock(side_effect=[False, True, True, True, True, False]) - with patch.dict(iptables.__salt__, {"iptables.check": mock}): - ret.update( - { -@@ -296,7 +416,7 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - ) - - with patch.dict(iptables.__opts__, {"test": False}): -- mock = MagicMock(side_effect=[False, True]) -+ mock = MagicMock(side_effect=[False, True, False, False]) - with patch.dict(iptables.__salt__, {"iptables.delete": mock}): - ret.update( - { -@@ -327,6 +447,58 @@ class IptablesTestCase(TestCase, LoaderModuleMockMixin): - ret, - ) - -+ mock_save = MagicMock( -+ side_effect=['Wrote 1 lines to "/tmp/iptables"', ""] -+ ) -+ with patch.dict( -+ iptables.__salt__, {"iptables.save": mock_save} -+ ): -+ mock = MagicMock(side_effect=[True, False]) -+ with patch.dict( -+ iptables.__salt__, {"iptables.check": mock} -+ ): -+ mock = MagicMock(side_effect=[""]) -+ with patch.dict( -+ iptables.__salt__, {"iptables.get_rules": mock} -+ ): -+ ret.update( -+ { -+ "changes": {"locale": "salt"}, -+ "result": True, -+ "comment": "Deleted and saved iptables rule" -+ ' salt for ipv4\na\nWrote 1 lines to "/tmp/iptables"', -+ } -+ ) -+ self.assertDictEqual( -+ iptables.delete( -+ "salt", -+ table="", -+ chain="", -+ save="/tmp/iptables", -+ ), -+ ret, -+ ) -+ ret.update( -+ { -+ "changes": {}, -+ "result": True, -+ "comment": "iptables rule for salt already absent for ipv4 (a)", -+ } -+ ) -+ self.assertDictEqual( -+ iptables.delete( -+ "salt", -+ table="", -+ chain="", -+ save="/tmp/iptables", -+ ), -+ ret, -+ ) -+ self.assertEqual( -+ mock_save.mock_calls[0][2]["filename"], -+ "/tmp/iptables", -+ ) -+ - def test_set_policy(self): - """ - Test to sets the default policy for iptables firewall tables --- -2.32.0 - - diff --git a/fix-traceback.print_exc-calls-for-test_pip_state-432.patch b/fix-traceback.print_exc-calls-for-test_pip_state-432.patch index 46a4e35..9bcf3f5 100644 --- a/fix-traceback.print_exc-calls-for-test_pip_state-432.patch +++ b/fix-traceback.print_exc-calls-for-test_pip_state-432.patch @@ -1,27 +1,17 @@ -From fba844fbaeb6203350944241a4ad0d7127a79bd5 Mon Sep 17 00:00:00 2001 +From 929942b15f377df21ae076ef9e25cf83639b1850 Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> Date: Mon, 8 Nov 2021 17:43:02 +0300 Subject: [PATCH] Fix traceback.print_exc calls for test_pip_state (#432) --- - tests/unit/states/test_pip_state.py | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) + tests/unit/states/test_pip_state.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/states/test_pip_state.py b/tests/unit/states/test_pip_state.py -index 914f62ff23..9e827dbf8a 100644 +index 9f5be295be..1074a1989f 100644 --- a/tests/unit/states/test_pip_state.py +++ b/tests/unit/states/test_pip_state.py -@@ -439,15 +439,15 @@ class PipStateInstallationErrorTest(TestCase): - import salt.states.pip_state - salt.states.pip_state.InstallationError - except ImportError as exc: -- traceback.print_exc(exc, file=sys.stdout) -+ traceback.print_exc(file=sys.stdout) - sys.stdout.flush() - sys.exit(1) - except AttributeError as exc: -- traceback.print_exc(exc, file=sys.stdout) -+ traceback.print_exc(file=sys.stdout) +@@ -445,7 +445,7 @@ class PipStateInstallationErrorTest(TestCase): sys.stdout.flush() sys.exit(2) except Exception as exc: @@ -31,6 +21,6 @@ index 914f62ff23..9e827dbf8a 100644 sys.exit(3) sys.exit(0) -- -2.33.1 +2.34.1 diff --git a/implementation-of-held-unheld-functions-for-state-pk.patch b/implementation-of-held-unheld-functions-for-state-pk.patch index 83680e0..0b834d5 100644 --- a/implementation-of-held-unheld-functions-for-state-pk.patch +++ b/implementation-of-held-unheld-functions-for-state-pk.patch @@ -1,22 +1,22 @@ -From f6e5a6bd16fa49cceadde9a9f46fefd12d92316b Mon Sep 17 00:00:00 2001 -From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> -Date: Mon, 5 Jul 2021 18:39:26 +0300 +From 8e5295ef9047a9afdd2323508c633ab0356ef603 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Wed, 19 Jan 2022 15:34:24 +0100 Subject: [PATCH] Implementation of held/unheld functions for state pkg (#387) * Implementation of held/unheld functions for state pkg --- - salt/modules/zypperpkg.py | 117 ++++++- + salt/modules/zypperpkg.py | 119 ++++++- salt/states/pkg.py | 310 +++++++++++++++++++ tests/pytests/unit/modules/test_zypperpkg.py | 133 ++++++++ tests/pytests/unit/states/test_pkg.py | 137 ++++++++ - 4 files changed, 684 insertions(+), 13 deletions(-) + 4 files changed, 686 insertions(+), 13 deletions(-) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py -index 863be3c894..9c7ffcf5da 100644 +index 4fc045c313..ac6c36a09f 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py -@@ -2092,6 +2092,76 @@ def purge( +@@ -2103,6 +2103,76 @@ def purge( return _uninstall(inclusion_detection, name=name, pkgs=pkgs, root=root) @@ -93,7 +93,7 @@ index 863be3c894..9c7ffcf5da 100644 def list_locks(root=None): """ List current package locks. -@@ -2162,7 +2232,7 @@ def clean_locks(root=None): +@@ -2173,7 +2243,7 @@ def clean_locks(root=None): return out @@ -102,16 +102,17 @@ index 863be3c894..9c7ffcf5da 100644 """ .. versionadded:: 3003 -@@ -2176,6 +2246,8 @@ def unhold(name=None, pkgs=None, **kwargs): +@@ -2187,6 +2257,9 @@ def unhold(name=None, pkgs=None, **kwargs): A list of packages to unhold. The ``name`` parameter will be ignored if this option is passed. + root + Operate on a different root directory. - ++ CLI Example: -@@ -2191,24 +2263,38 @@ def unhold(name=None, pkgs=None, **kwargs): + .. code-block:: bash +@@ -2201,24 +2274,38 @@ def unhold(name=None, pkgs=None, **kwargs): targets = [] if pkgs: @@ -158,7 +159,7 @@ index 863be3c894..9c7ffcf5da 100644 ret[target]["comment"] = "Package {} was already unheld.".format(target) if removed: -@@ -2261,7 +2347,7 @@ def remove_lock(name, root=None, **kwargs): +@@ -2271,7 +2358,7 @@ def remove_lock(name, root=None, **kwargs): return {"removed": len(removed), "not_found": missing} @@ -167,17 +168,18 @@ index 863be3c894..9c7ffcf5da 100644 """ .. versionadded:: 3003 -@@ -2275,6 +2361,9 @@ def hold(name=None, pkgs=None, **kwargs): +@@ -2285,6 +2372,10 @@ def hold(name=None, pkgs=None, **kwargs): A list of packages to hold. The ``name`` parameter will be ignored if this option is passed. + root + Operate on a different root directory. + - ++ CLI Example: -@@ -2290,8 +2379,7 @@ def hold(name=None, pkgs=None, **kwargs): + .. code-block:: bash +@@ -2299,8 +2390,7 @@ def hold(name=None, pkgs=None, **kwargs): targets = [] if pkgs: @@ -187,7 +189,7 @@ index 863be3c894..9c7ffcf5da 100644 else: targets.append(name) -@@ -2299,9 +2387,12 @@ def hold(name=None, pkgs=None, **kwargs): +@@ -2308,9 +2398,12 @@ def hold(name=None, pkgs=None, **kwargs): added = [] for target in targets: @@ -202,10 +204,10 @@ index 863be3c894..9c7ffcf5da 100644 ret[target]["changes"]["old"] = "" ret[target]["comment"] = "Package {} is now being held.".format(target) diff --git a/salt/states/pkg.py b/salt/states/pkg.py -index fd6808a2dc..a8ffe25a77 100644 +index f71f61e720..0d601e1aaf 100644 --- a/salt/states/pkg.py +++ b/salt/states/pkg.py -@@ -3607,3 +3607,313 @@ def mod_beacon(name, **kwargs): +@@ -3644,3 +3644,313 @@ def mod_beacon(name, **kwargs): ), "result": False, } @@ -520,10 +522,10 @@ index fd6808a2dc..a8ffe25a77 100644 + + return ret diff --git a/tests/pytests/unit/modules/test_zypperpkg.py b/tests/pytests/unit/modules/test_zypperpkg.py -index 37bbef87b7..dbe09976b2 100644 +index eb1e63f6d7..bfc1558c9a 100644 --- a/tests/pytests/unit/modules/test_zypperpkg.py +++ b/tests/pytests/unit/modules/test_zypperpkg.py -@@ -119,3 +119,136 @@ def test_del_repo_key(): +@@ -121,3 +121,136 @@ def test_del_repo_key(): with patch.dict(zypper.__salt__, salt_mock): assert zypper.del_repo_key(keyid="keyid", root="/mnt") salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") @@ -661,13 +663,13 @@ index 37bbef87b7..dbe09976b2 100644 + assert len(ret) == 1 + assert "bar-2:2.3.4-2.1.*" in ret diff --git a/tests/pytests/unit/states/test_pkg.py b/tests/pytests/unit/states/test_pkg.py -index 8e5ae42ed8..a2e63d5157 100644 +index 7e667d36fd..17b91bcb39 100644 --- a/tests/pytests/unit/states/test_pkg.py +++ b/tests/pytests/unit/states/test_pkg.py -@@ -352,3 +352,140 @@ def test_mod_beacon(): - } - - assert ret == expected +@@ -578,3 +578,140 @@ def test_removed_purged_with_changes_test_true(list_pkgs, action): + ret = pkg_actions[action]("pkga", test=True) + assert ret["result"] is None + assert ret["changes"] == expected + + +@pytest.mark.parametrize( @@ -806,6 +808,6 @@ index 8e5ae42ed8..a2e63d5157 100644 + unhold_mock.assert_any_call(name="held-test", pkgs=["baz"]) + unhold_mock.assert_any_call(name="held-test", pkgs=["bar"]) -- -2.33.0 +2.34.1 diff --git a/improvements-on-ansiblegate-module-354.patch b/improvements-on-ansiblegate-module-354.patch index e9cce7e..af82f26 100644 --- a/improvements-on-ansiblegate-module-354.patch +++ b/improvements-on-ansiblegate-module-354.patch @@ -1,7 +1,6 @@ -From 90cc5349ed085729db43966bf290c76db5c7f6b9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Tue, 20 Apr 2021 11:01:26 +0100 +From b58056da2f5a12e3d614650904039c0655ce1221 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 19:41:03 +0100 Subject: [PATCH] Improvements on "ansiblegate" module (#354) * Allow collecting Ansible Inventory from a minion @@ -36,26 +35,34 @@ Subject: [PATCH] Improvements on "ansiblegate" module (#354) Fix issue parsing errors in ansiblegate state module --- - salt/modules/ansiblegate.py | 166 +++++++++++++++++- - salt/roster/ansible.py | 18 +- + salt/modules/ansiblegate.py | 167 +++++++++++++++++- + salt/roster/ansible.py | 17 +- salt/states/ansiblegate.py | 12 +- - salt/utils/ansible.py | 44 +++++ - .../pytests/unit/modules/test_ansiblegate.py | 94 +++++++++- + salt/utils/ansible.py | 41 +++++ + .../pytests/unit/modules/test_ansiblegate.py | 99 ++++++++++- .../example_playbooks/example-playbook2/hosts | 7 + .../example-playbook2/site.yml | 28 +++ .../playbooks/example_playbooks/playbook1.yml | 5 + tests/unit/roster/test_ansible.py | 2 +- - 9 files changed, 364 insertions(+), 12 deletions(-) + 9 files changed, 367 insertions(+), 11 deletions(-) create mode 100644 salt/utils/ansible.py create mode 100644 tests/unit/files/playbooks/example_playbooks/example-playbook2/hosts create mode 100644 tests/unit/files/playbooks/example_playbooks/example-playbook2/site.yml create mode 100644 tests/unit/files/playbooks/example_playbooks/playbook1.yml diff --git a/salt/modules/ansiblegate.py b/salt/modules/ansiblegate.py -index 0279a26017..2b4d4b0bdf 100644 +index 328d9b7b0a..f33be6a00e 100644 --- a/salt/modules/ansiblegate.py +++ b/salt/modules/ansiblegate.py -@@ -425,7 +425,171 @@ def playbooks( +@@ -17,6 +17,7 @@ any Ansible module to respond. + import fnmatch + import json + import logging ++import os + import subprocess + import sys + from tempfile import NamedTemporaryFile +@@ -365,7 +366,171 @@ def playbooks( } ret = __salt__["cmd.run_all"](**cmd_kwargs) log.debug("Ansible Playbook Return: %s", ret) @@ -229,23 +236,15 @@ index 0279a26017..2b4d4b0bdf 100644 + raise CommandExecutionError("There was an exception while checking syntax of playbooks: {}".format(exc)) + return ret diff --git a/salt/roster/ansible.py b/salt/roster/ansible.py -index f17316bdd7..cc61f6fb7d 100644 +index 7beaaf2075..d3b352de27 100644 --- a/salt/roster/ansible.py +++ b/salt/roster/ansible.py -@@ -89,7 +89,6 @@ Any of the [groups] or direct hostnames will return. The 'all' is special, and - """ - # Import Python libs - from __future__ import absolute_import, print_function, unicode_literals -- - import copy - import fnmatch - -@@ -121,27 +120,32 @@ def targets(tgt, tgt_type="glob", **kwargs): +@@ -117,27 +117,32 @@ def targets(tgt, tgt_type="glob", **kwargs): Return the targets from the ansible inventory_file Default: /etc/salt/roster """ - inventory = __runner__["salt.cmd"]( -- "cmd.run", "ansible-inventory -i {0} --list".format(get_roster_file(__opts__)) +- "cmd.run", "ansible-inventory -i {} --list".format(get_roster_file(__opts__)) - ) - __context__["inventory"] = __utils__["json.loads"]( - __utils__["stringutils.to_str"](inventory) @@ -280,10 +279,10 @@ index f17316bdd7..cc61f6fb7d 100644 return hosts diff --git a/salt/states/ansiblegate.py b/salt/states/ansiblegate.py -index 5daba0f37f..bd00653928 100644 +index 4afe6a020d..af5cb0f0e5 100644 --- a/salt/states/ansiblegate.py +++ b/salt/states/ansiblegate.py -@@ -183,7 +183,11 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= +@@ -184,7 +184,11 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= checks = __salt__["ansible.playbooks"]( name, rundir=rundir, check=True, diff=True, **ansible_kwargs ) @@ -296,7 +295,7 @@ index 5daba0f37f..bd00653928 100644 not check["changed"] and not check["failures"] and not check["unreachable"] -@@ -212,7 +216,11 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= +@@ -213,7 +217,11 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= results = __salt__["ansible.playbooks"]( name, rundir=rundir, diff=True, **ansible_kwargs ) @@ -311,13 +310,10 @@ index 5daba0f37f..bd00653928 100644 and not check["unreachable"] diff --git a/salt/utils/ansible.py b/salt/utils/ansible.py new file mode 100644 -index 0000000000..ee85cb656c +index 0000000000..1e14037fd3 --- /dev/null +++ b/salt/utils/ansible.py -@@ -0,0 +1,44 @@ -+# -*- coding: utf-8 -*- -+# Import Python libs -+from __future__ import absolute_import, print_function, unicode_literals +@@ -0,0 +1,41 @@ +import logging +import os + @@ -360,33 +356,37 @@ index 0000000000..ee85cb656c + else: + return salt.utils.json.loads(salt.utils.stringutils.to_str(inv)) diff --git a/tests/pytests/unit/modules/test_ansiblegate.py b/tests/pytests/unit/modules/test_ansiblegate.py -index 42c0968a6e..24c7e5e6b3 100644 +index 44c9b12acb..f357133000 100644 --- a/tests/pytests/unit/modules/test_ansiblegate.py +++ b/tests/pytests/unit/modules/test_ansiblegate.py -@@ -8,8 +8,11 @@ import pytest - import salt.modules.ansiblegate as ansible - import salt.utils.path - import salt.utils.platform +@@ -1,9 +1,15 @@ + # Author: Bo Maryniuk ++import os + + import pytest ++ +import salt.config +import salt.loader - from salt.exceptions import LoaderError - from tests.support.mock import MagicMock, MockTimedProc, patch + import salt.modules.ansiblegate as ansiblegate + import salt.utils.json ++import salt.utils.path + from tests.support.mock import ANY, MagicMock, patch +from tests.support.runtests import RUNTIME_VARS - pytestmark = pytest.mark.skipif( - salt.utils.platform.is_windows(), reason="Not supported on Windows" -@@ -18,7 +21,7 @@ pytestmark = pytest.mark.skipif( + pytestmark = [ + pytest.mark.skip_on_windows(reason="Not supported on Windows"), +@@ -12,7 +18,7 @@ pytestmark = [ @pytest.fixture def configure_loader_modules(): -- return {ansible: {}} -+ return {ansible: {"__utils__": {}}} +- return {ansiblegate: {}} ++ return {ansiblegate: {"__utils__": {}}} - @pytest.fixture -@@ -201,3 +204,92 @@ def test_ansible_playbooks_return_retcode(resolver): + def test_ansible_module_help(): +@@ -133,3 +139,94 @@ def test_ansible_playbooks_return_retcode(): ): - ret = ansible.playbooks("fake-playbook.yml") + ret = ansiblegate.playbooks("fake-playbook.yml") assert "retcode" in ret + + @@ -425,14 +425,16 @@ index 42c0968a6e..24c7e5e6b3 100644 + opts = salt.config.DEFAULT_MINION_OPTS.copy() + utils = salt.loader.utils(opts, whitelist=["ansible"]) + with patch("salt.modules.cmdmod.run", ansible_inventory_mock), patch.dict( -+ ansible.__utils__, utils), patch( -+ "os.path.isfile", MagicMock(return_value=True) -+ ): -+ ret = ansible.targets() ++ ansiblegate.__utils__, utils ++ ), patch("os.path.isfile", MagicMock(return_value=True)): ++ ret = ansiblegate.targets() + assert ansible_inventory_mock.call_args + assert "_meta" in ret + assert "uyuni-stable-ansible-centos7-1.tf.local" in ret["_meta"]["hostvars"] -+ assert "ansible_ssh_private_key_file" in ret["_meta"]["hostvars"]["uyuni-stable-ansible-centos7-1.tf.local"] ++ assert ( ++ "ansible_ssh_private_key_file" ++ in ret["_meta"]["hostvars"]["uyuni-stable-ansible-centos7-1.tf.local"] ++ ) + assert "all" in ret + assert len(ret["ungrouped"]["hosts"]) == 2 + @@ -441,7 +443,7 @@ index 42c0968a6e..24c7e5e6b3 100644 + playbooks_dir = os.path.join( + RUNTIME_VARS.TESTS_DIR, "unit/files/playbooks/example_playbooks/" + ) -+ ret = ansible.discover_playbooks(playbooks_dir) ++ ret = ansiblegate.discover_playbooks(playbooks_dir) + assert playbooks_dir in ret + assert ret[playbooks_dir]["playbook1.yml"] == { + "fullpath": os.path.join(playbooks_dir, "playbook1.yml") @@ -456,7 +458,7 @@ index 42c0968a6e..24c7e5e6b3 100644 + playbooks_dir = os.path.join( + RUNTIME_VARS.TESTS_DIR, "unit/files/playbooks/example_playbooks/" + ) -+ ret = ansible.discover_playbooks( ++ ret = ansiblegate.discover_playbooks( + playbooks_dir, playbook_extension="foobar", hosts_filename="deadbeaf" + ) + assert playbooks_dir in ret @@ -467,7 +469,7 @@ index 42c0968a6e..24c7e5e6b3 100644 + playbooks_dir = os.path.join( + RUNTIME_VARS.TESTS_DIR, "unit/files/playbooks/example_playbooks/" + ) -+ ret = ansible.discover_playbooks(locations=[playbooks_dir, "/tmp/foobar"]) ++ ret = ansiblegate.discover_playbooks(locations=[playbooks_dir, "/tmp/foobar"]) + assert playbooks_dir in ret + assert "/tmp/foobar" in ret + assert ret[playbooks_dir]["playbook1.yml"] == { @@ -536,10 +538,10 @@ index 0000000000..e258a101e1 + tasks: + - ping: diff --git a/tests/unit/roster/test_ansible.py b/tests/unit/roster/test_ansible.py -index a5cdcbbdbc..8bc9c1c6f7 100644 +index 7f1144454b..c4ab8b7639 100644 --- a/tests/unit/roster/test_ansible.py +++ b/tests/unit/roster/test_ansible.py -@@ -71,7 +71,7 @@ class AnsibleRosterTestCase(TestCase, mixins.LoaderModuleMockMixin): +@@ -63,7 +63,7 @@ class AnsibleRosterTestCase(TestCase, mixins.LoaderModuleMockMixin): opts = salt.config.master_config( os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "master") ) @@ -549,6 +551,6 @@ index a5cdcbbdbc..8bc9c1c6f7 100644 return {ansible: {"__utils__": utils, "__opts__": {}, "__runner__": runner}} -- -2.33.0 +2.34.1 diff --git a/include-aliases-in-the-fqdns-grains.patch b/include-aliases-in-the-fqdns-grains.patch index 3aa9efd..90ca818 100644 --- a/include-aliases-in-the-fqdns-grains.patch +++ b/include-aliases-in-the-fqdns-grains.patch @@ -1,6 +1,6 @@ -From 0c0f470f0bc082316cf854c8c4f6f6500f80f3f0 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Tue, 29 Jan 2019 11:11:38 +0100 +From 834defc8e38c4495ed51bb549d86727dd8b812b3 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 17:10:37 +0100 Subject: [PATCH] Include aliases in the fqdns grains Add UT for "is_fqdn" @@ -24,17 +24,16 @@ Implement network.fqdns module function (bsc#1134860) (#172) Co-authored-by: Eric Siebigteroth --- - salt/modules/network.py | 5 ++- - salt/utils/network.py | 16 +++++++++ - tests/unit/grains/test_core.py | 60 +++++++++++++++++++++----------- - tests/unit/utils/test_network.py | 37 ++++++++++++++++++++ - 4 files changed, 97 insertions(+), 21 deletions(-) + salt/modules/network.py | 5 ++++- + salt/utils/network.py | 16 ++++++++++++++ + tests/unit/utils/test_network.py | 37 ++++++++++++++++++++++++++++++++ + 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/salt/modules/network.py b/salt/modules/network.py -index 9280a0f854..d8ff251271 100644 +index 08c20b99f9..53ebfe4bc7 100644 --- a/salt/modules/network.py +++ b/salt/modules/network.py -@@ -2073,7 +2073,10 @@ def fqdns(): +@@ -2089,7 +2089,10 @@ def fqdns(): def _lookup_fqdn(ip): try: @@ -47,10 +46,10 @@ index 9280a0f854..d8ff251271 100644 if err.errno in (0, HOST_NOT_FOUND, NO_DATA): # No FQDN for this IP address, so we don't need to know this all the time. diff --git a/salt/utils/network.py b/salt/utils/network.py -index 144f9dc850..5fc9a34ca4 100644 +index 22075066fd..8867041e0e 100644 --- a/salt/utils/network.py +++ b/salt/utils/network.py -@@ -2286,3 +2286,19 @@ def filter_by_networks(values, networks): +@@ -2302,3 +2302,19 @@ def filter_by_networks(values, networks): raise ValueError("Do not know how to filter a {}".format(type(values))) else: return values @@ -70,111 +69,6 @@ index 144f9dc850..5fc9a34ca4 100644 + and len(hostname) < 0xFF + and all(compliant.match(x) for x in hostname.rstrip(".").split(".")) + ) -diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py -index 914be531ed..7173f04979 100644 ---- a/tests/unit/grains/test_core.py -+++ b/tests/unit/grains/test_core.py -@@ -18,6 +18,7 @@ import salt.utils.network - import salt.utils.path - import salt.utils.platform - from salt._compat import ipaddress -+from salt.ext import six - from tests.support.mixins import LoaderModuleMockMixin - from tests.support.mock import MagicMock, Mock, mock_open, patch - from tests.support.unit import TestCase, skipIf -@@ -1428,7 +1429,7 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - with patch.dict("salt.grains.core.__opts__", {"enable_fqdns_grains": False}): - assert core.fqdns() == {"fqdns": []} - -- def test_enable_fqdns_true(self): -+ def test_enablefqdnsTrue(self): - """ - testing that grains uses network.fqdns module - """ -@@ -1439,14 +1440,14 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - with patch.dict("salt.grains.core.__opts__", {"enable_fqdns_grains": True}): - assert core.fqdns() == "my.fake.domain" - -- def test_enable_fqdns_none(self): -+ def test_enablefqdnsNone(self): - """ - testing default fqdns grains is returned when enable_fqdns_grains is None - """ - with patch.dict("salt.grains.core.__opts__", {"enable_fqdns_grains": None}): - assert core.fqdns() == {"fqdns": []} - -- def test_enable_fqdns_without_patching(self): -+ def test_enablefqdnswithoutpaching(self): - """ - testing fqdns grains is enabled by default - """ -@@ -1454,23 +1455,7 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - "salt.grains.core.__salt__", - {"network.fqdns": MagicMock(return_value="my.fake.domain")}, - ): -- # fqdns is disabled by default on Windows -- if salt.utils.platform.is_windows(): -- assert core.fqdns() == {"fqdns": []} -- else: -- assert core.fqdns() == "my.fake.domain" -- -- def test_enable_fqdns_false_is_proxy(self): -- """ -- testing fqdns grains is disabled by default for proxy minions -- """ -- with patch("salt.utils.platform.is_proxy", return_value=True, autospec=True): -- with patch.dict( -- "salt.grains.core.__salt__", -- {"network.fqdns": MagicMock(return_value="my.fake.domain")}, -- ): -- # fqdns is disabled by default on proxy minions -- assert core.fqdns() == {"fqdns": []} -+ assert core.fqdns() == "my.fake.domain" - - def test_enable_fqdns_false_is_aix(self): - """ -@@ -1577,6 +1562,41 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): - mock_log.debug.assert_called_once() - mock_log.error.assert_called() - -+ @patch.object(salt.utils.platform, "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_aliases(self): -+ """ -+ FQDNs aliases -+ """ -+ reverse_resolv_mock = [ -+ ("foo.bar.baz", ["throwmeaway", "this.is.valid.alias"], ["1.2.3.4"]), -+ ("rinzler.evil-corp.com", ["false-hostname", "badaliass"], ["5.6.7.8"]), -+ ("foo.bar.baz", [], ["fe80::a8b2:93ff:fe00:0"]), -+ ( -+ "bluesniff.foo.bar", -+ ["alias.bluesniff.foo.bar"], -+ ["fe80::a8b2:93ff:dead:beef"], -+ ), -+ ] -+ with patch.dict(core.__salt__, {"network.fqdns": salt.modules.network.fqdns}): -+ with patch.object(socket, "gethostbyaddr", side_effect=reverse_resolv_mock): -+ fqdns = core.fqdns() -+ assert "fqdns" in fqdns -+ for alias in ["this.is.valid.alias", "alias.bluesniff.foo.bar"]: -+ assert alias in fqdns["fqdns"] -+ -+ for alias in ["throwmeaway", "false-hostname", "badaliass"]: -+ assert alias not in fqdns["fqdns"] -+ - def test_core_virtual(self): - """ - test virtual grain with cmd virt-what diff --git a/tests/unit/utils/test_network.py b/tests/unit/utils/test_network.py index 6863ccd0c9..637d5e9811 100644 --- a/tests/unit/utils/test_network.py @@ -221,6 +115,6 @@ index 6863ccd0c9..637d5e9811 100644 + ]: + assert not network.is_fqdn(fqdn) -- -2.33.0 +2.34.1 diff --git a/info_installed-works-without-status-attr-now.patch b/info_installed-works-without-status-attr-now.patch index 6a0af04..cf4872c 100644 --- a/info_installed-works-without-status-attr-now.patch +++ b/info_installed-works-without-status-attr-now.patch @@ -1,21 +1,21 @@ -From 0ef6eed4f5e120a584843c33272066ba477feb3f Mon Sep 17 00:00:00 2001 -From: Jochen Breuer -Date: Tue, 19 May 2020 10:34:35 +0200 +From 3f8e937d938f19dd40fde527497f7775bbffe353 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 25 Jan 2022 17:12:47 +0100 Subject: [PATCH] info_installed works without status attr now If 'status' was excluded via attr, info_installed was no longer able to detect if a package was installed or not. Now info_installed adds the 'status' for the 'lowpkg.info' request again. --- - salt/modules/aptpkg.py | 9 +++++++++ - tests/unit/modules/test_aptpkg.py | 20 ++++++++++++++++++++ - 2 files changed, 29 insertions(+) + salt/modules/aptpkg.py | 9 +++++++++ + tests/pytests/unit/modules/test_aptpkg.py | 18 ++++++++++++++++++ + 2 files changed, 27 insertions(+) diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py -index db0480b45d..e4a9872aad 100644 +index 3c3fbf4970..0d378355ab 100644 --- a/salt/modules/aptpkg.py +++ b/salt/modules/aptpkg.py -@@ -2923,6 +2923,15 @@ def info_installed(*names, **kwargs): +@@ -3056,6 +3056,15 @@ def info_installed(*names, **kwargs): failhard = kwargs.pop("failhard", True) kwargs.pop("errors", None) # Only for compatibility with RPM attr = kwargs.pop("attr", None) # Package attributes to return @@ -31,38 +31,36 @@ index db0480b45d..e4a9872aad 100644 all_versions = kwargs.pop( "all_versions", False ) # This is for backward compatible structure only -diff --git a/tests/unit/modules/test_aptpkg.py b/tests/unit/modules/test_aptpkg.py -index 3c9744e224..51dfce29eb 100644 ---- a/tests/unit/modules/test_aptpkg.py -+++ b/tests/unit/modules/test_aptpkg.py -@@ -297,6 +297,26 @@ class AptPkgTestCase(TestCase, LoaderModuleMockMixin): - self.assertEqual(aptpkg.info_installed("wget"), installed) - self.assertEqual(len(aptpkg.info_installed()), 1) +diff --git a/tests/pytests/unit/modules/test_aptpkg.py b/tests/pytests/unit/modules/test_aptpkg.py +index 51b7ffbe4d..8c64c8c9c1 100644 +--- a/tests/pytests/unit/modules/test_aptpkg.py ++++ b/tests/pytests/unit/modules/test_aptpkg.py +@@ -361,6 +361,24 @@ def test_info_installed_attr(lowpkg_info_var): + assert ret["wget"] == expected_pkg -+ def test_info_installed_attr_without_status(self): -+ """ -+ Test info_installed 'attr' for inclusion of 'status' attribute. + ++def test_info_installed_attr_without_status(lowpkg_info_var): ++ """ ++ Test info_installed 'attr' for inclusion of 'status' attribute. + -+ Since info_installed should only return installed packages, we need to -+ call __salt__['lowpkg.info'] with the 'status' attribute even if the user -+ is not asking for it in 'attr'. Otherwise info_installed would not be able -+ to check if the package is installed and would return everything. ++ Since info_installed should only return installed packages, we need to ++ call __salt__['lowpkg.info'] with the 'status' attribute even if the user ++ is not asking for it in 'attr'. Otherwise info_installed would not be able ++ to check if the package is installed and would return everything. + -+ :return: -+ """ -+ with patch( -+ "salt.modules.aptpkg.__salt__", -+ {"lowpkg.info": MagicMock(return_value=LOWPKG_INFO)}, -+ ) as wget_lowpkg: -+ ret = aptpkg.info_installed("wget", attr="version") -+ calls = wget_lowpkg["lowpkg.info"].call_args_list.pop() -+ self.assertIn("status", calls.kwargs["attr"]) -+ self.assertIn("version", calls.kwargs["attr"]) ++ :return: ++ """ ++ mock = MagicMock(return_value=lowpkg_info_var) ++ with patch.dict(aptpkg.__salt__, {"lowpkg.info": mock}): ++ aptpkg.info_installed("wget", attr="version") ++ assert "status" in mock.call_args.kwargs["attr"] ++ assert "version" in mock.call_args.kwargs["attr"] + - @patch( - "salt.modules.aptpkg.__salt__", - {"lowpkg.info": MagicMock(return_value=LOWPKG_INFO)}, ++ + def test_info_installed_all_versions(lowpkg_info_var): + """ + Test info_installed 'all_versions'. -- -2.29.2 +2.34.1 diff --git a/mock-ip_addrs-in-utils-minions.py-unit-test-443.patch b/mock-ip_addrs-in-utils-minions.py-unit-test-443.patch index a5067c0..d27ad17 100644 --- a/mock-ip_addrs-in-utils-minions.py-unit-test-443.patch +++ b/mock-ip_addrs-in-utils-minions.py-unit-test-443.patch @@ -1,6 +1,6 @@ -From 2ea56dd17378fe2f41de04a9c1786d27fec9a266 Mon Sep 17 00:00:00 2001 -From: Alexander Graul -Date: Mon, 25 Oct 2021 10:31:10 +0200 +From a363596e5e02307680859432da9935905b749846 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Wed, 19 Jan 2022 17:33:01 +0100 Subject: [PATCH] Mock ip_addrs() in utils/minions.py unit test (#443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -14,23 +14,25 @@ Since this is a unit test, it should not depend on the environment, it should just work™, even if there are no real IP addresses assigned to the system (or container) that runs the test. -Co-authored-by: Pablo Suárez Hernández - Co-authored-by: Pablo Suárez Hernández --- - tests/pytests/unit/utils/test_minions.py | 17 +++++++++-------- - 1 file changed, 9 insertions(+), 8 deletions(-) + tests/pytests/unit/utils/test_minions.py | 36 +++++++++++++----------- + 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/pytests/unit/utils/test_minions.py b/tests/pytests/unit/utils/test_minions.py -index 0b7a7d3928..5b0cd77216 100644 +index a9eee20ea1..6bc6c80bbd 100644 --- a/tests/pytests/unit/utils/test_minions.py +++ b/tests/pytests/unit/utils/test_minions.py -@@ -8,15 +8,16 @@ def test_connected_ids(): +@@ -8,18 +8,22 @@ def test_connected_ids(): test ckminion connected_ids when local_port_tcp returns 127.0.0.1 """ -- opts = {"publish_port": 4505} -+ opts = {"publish_port": 4505, "minion_data_cache": True} +- opts = {"publish_port": 4505, "detect_remote_minions": False} ++ opts = { ++ "publish_port": 4505, ++ "detect_remote_minions": False, ++ "minion_data_cache": True, ++ } minion = "minion" - ip = salt.utils.network.ip_addrs() - mdata = {"grains": {"ipv4": ip, "ipv6": []}} @@ -45,12 +47,44 @@ index 0b7a7d3928..5b0cd77216 100644 - with patch_net, patch_list, patch_fetch: - ret = ckminions.connected_ids() - assert ret == {minion} -+ + ckminions = salt.utils.minions.CkMinions(opts) + with patch_net, patch_ip_addrs, patch_list, patch_fetch: + ret = ckminions.connected_ids() + assert ret == {minion} + + + def test_connected_ids_remote_minions(): +@@ -31,21 +35,21 @@ def test_connected_ids_remote_minions(): + "publish_port": 4505, + "detect_remote_minions": True, + "remote_minions_port": 22, ++ "minion_data_cache": True, + } + minion = "minion" + minion2 = "minion2" + minion2_ip = "192.168.2.10" +- ip = salt.utils.network.ip_addrs() +- mdata = {"grains": {"ipv4": ip, "ipv6": []}} ++ minion_ips = {"203.0.113.1", "203.0.113.2", "127.0.0.1"} ++ mdata = {"grains": {"ipv4": minion_ips, "ipv6": []}} + mdata2 = {"grains": {"ipv4": [minion2_ip], "ipv6": []}} +- ckminions = salt.utils.minions.CkMinions({"minion_data_cache": True}) +- patch_net = patch("salt.utils.network.local_port_tcp", return_value={"127.0.0.1"}) ++ patch_net = patch("salt.utils.network.local_port_tcp", return_value=minion_ips) + patch_remote_net = patch( + "salt.utils.network.remote_port_tcp", return_value={minion2_ip} + ) + patch_list = patch("salt.cache.Cache.list", return_value=[minion, minion2]) + patch_fetch = patch("salt.cache.Cache.fetch", side_effect=[mdata, mdata2]) +- with patch.dict(ckminions.opts, opts): +- with patch_net, patch_list, patch_fetch, patch_remote_net: +- ret = ckminions.connected_ids() +- assert ret == {minion2, minion} ++ ckminions = salt.utils.minions.CkMinions(opts) ++ with patch_net, patch_list, patch_fetch, patch_remote_net: ++ ret = ckminions.connected_ids() ++ assert ret == {minion2, minion} -- -2.33.1 +2.34.1 diff --git a/parsing-epoch-out-of-version-provided-during-pkg-rem.patch b/parsing-epoch-out-of-version-provided-during-pkg-rem.patch deleted file mode 100644 index b77c87e..0000000 --- a/parsing-epoch-out-of-version-provided-during-pkg-rem.patch +++ /dev/null @@ -1,159 +0,0 @@ -From 3973f51a948c0d4a81ca5992c9b06e71ebae443c Mon Sep 17 00:00:00 2001 -From: Jochen Breuer -Date: Mon, 3 May 2021 17:20:54 +0200 -Subject: [PATCH] Parsing Epoch out of version provided during pkg remove - (bsc#1173692) - -yum doesn't seem to like the epoch information provided within the -version. Therefore it's removed before passing it to yum. - -* Introducing `ignore_epoch` to pkg.remove - Just like pkg.install pkg.remove now also has ignore_epoch. With - this it is possible to ignore the epoch information completely - during version comparison. -* No epoch regardless of arch -* Added tests for cases with and without arch. -* Epoch information is now skipped in all cases. -* Removes ignore_epoch from pkg state ---- - changelog/57881.changed | 1 + - salt/modules/yumpkg.py | 14 ++-- - tests/pytests/unit/modules/test_yumpkg.py | 79 +++++++++++++++++++++++ - 3 files changed, 90 insertions(+), 4 deletions(-) - create mode 100644 changelog/57881.changed - -diff --git a/changelog/57881.changed b/changelog/57881.changed -new file mode 100644 -index 0000000000..e2ae2f4653 ---- /dev/null -+++ b/changelog/57881.changed -@@ -0,0 +1 @@ -+Parsing Epoch out of version during pkg remove, since yum can't handle that in all of the cases. -diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py -index c800dafa82..dd81c6f1e9 100644 ---- a/salt/modules/yumpkg.py -+++ b/salt/modules/yumpkg.py -@@ -2087,11 +2087,13 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 - old = list_pkgs() - targets = [] - for target in pkg_params: -+ version_to_remove = pkg_params[target] -+ installed_versions = old[target].split(",") -+ - # Check if package version set to be removed is actually installed: -- # old[target] contains a comma-separated list of installed versions -- if target in old and not pkg_params[target]: -+ if target in old and not version_to_remove: - targets.append(target) -- elif target in old and pkg_params[target] in old[target].split(","): -+ elif target in old and version_to_remove in installed_versions: - arch = "" - pkgname = target - try: -@@ -2102,7 +2104,11 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 - if archpart in salt.utils.pkg.rpm.ARCHES: - arch = "." + archpart - pkgname = namepart -- targets.append("{}-{}{}".format(pkgname, pkg_params[target], arch)) -+ # Since we don't always have the arch info, epoch information has to parsed out. But -+ # a version check was already performed, so we are removing the right version. -+ targets.append( -+ "{}-{}{}".format(pkgname, version_to_remove.split(":", 1)[-1], arch) -+ ) - if not targets: - return {} - -diff --git a/tests/pytests/unit/modules/test_yumpkg.py b/tests/pytests/unit/modules/test_yumpkg.py -index cae47788ff..7e3ed517ea 100644 ---- a/tests/pytests/unit/modules/test_yumpkg.py -+++ b/tests/pytests/unit/modules/test_yumpkg.py -@@ -1046,6 +1046,85 @@ def test_install_with_options(): - ) - - -+def test_remove_with_epoch(): -+ """ -+ Tests that we properly identify a version containing an epoch for -+ deinstallation. -+ You can deinstall pkgs only without the epoch if no arch is provided: -+ .. code-block:: bash -+ yum remove PackageKit-yum-1.1.10-2.el7.centos -+ """ -+ name = "foo" -+ installed = "8:3.8.12-4.n.el7" -+ list_pkgs_mock = MagicMock( -+ side_effect=lambda **kwargs: { -+ name: [installed] if kwargs.get("versions_as_list", False) else installed -+ } -+ ) -+ cmd_mock = MagicMock( -+ return_value={"pid": 12345, "retcode": 0, "stdout": "", "stderr": ""} -+ ) -+ salt_mock = { -+ "cmd.run_all": cmd_mock, -+ "lowpkg.version_cmp": rpm.version_cmp, -+ "pkg_resource.parse_targets": MagicMock( -+ return_value=({name: installed}, "repository") -+ ), -+ } -+ full_pkg_string = "-".join((name, installed[2:])) -+ with patch.object(yumpkg, "list_pkgs", list_pkgs_mock), patch( -+ "salt.utils.systemd.has_scope", MagicMock(return_value=False) -+ ), patch.dict(yumpkg.__salt__, salt_mock): -+ -+ with patch.dict(yumpkg.__grains__, {"os": "CentOS", "osrelease": 7}): -+ expected = ["yum", "-y", "remove", full_pkg_string] -+ yumpkg.remove(name) -+ call = cmd_mock.mock_calls[0][1][0] -+ assert call == expected, call -+ -+ -+def test_remove_with_epoch_and_arch_info(): -+ """ -+ Tests that we properly identify a version containing an epoch and arch -+ deinstallation. -+ You can deinstall pkgs with or without epoch in combination with the arch. -+ Here we test for the absence of the epoch, but the presence for the arch: -+ .. code-block:: bash -+ yum remove PackageKit-yum-1.1.10-2.el7.centos.x86_64 -+ """ -+ arch = "x86_64" -+ name = "foo" -+ name_and_arch = name + "." + arch -+ installed = "8:3.8.12-4.n.el7" -+ list_pkgs_mock = MagicMock( -+ side_effect=lambda **kwargs: { -+ name_and_arch: [installed] -+ if kwargs.get("versions_as_list", False) -+ else installed -+ } -+ ) -+ cmd_mock = MagicMock( -+ return_value={"pid": 12345, "retcode": 0, "stdout": "", "stderr": ""} -+ ) -+ salt_mock = { -+ "cmd.run_all": cmd_mock, -+ "lowpkg.version_cmp": rpm.version_cmp, -+ "pkg_resource.parse_targets": MagicMock( -+ return_value=({name_and_arch: installed}, "repository") -+ ), -+ } -+ full_pkg_string = "-".join((name, installed[2:])) -+ with patch.object(yumpkg, "list_pkgs", list_pkgs_mock), patch( -+ "salt.utils.systemd.has_scope", MagicMock(return_value=False) -+ ), patch.dict(yumpkg.__salt__, salt_mock): -+ -+ with patch.dict(yumpkg.__grains__, {"os": "CentOS", "osrelease": 7}): -+ expected = ["yum", "-y", "remove", full_pkg_string + "." + arch] -+ yumpkg.remove(name) -+ call = cmd_mock.mock_calls[0][1][0] -+ assert call == expected, call -+ -+ - def test_install_with_epoch(): - """ - Tests that we properly identify a version containing an epoch as an --- -2.33.0 - - diff --git a/prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch b/prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch deleted file mode 100644 index d45702f..0000000 --- a/prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch +++ /dev/null @@ -1,318 +0,0 @@ -From eb15f772aa7aa6c9553bb4f24c709e941e6f3413 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Wed, 22 Jan 2020 08:19:55 +0000 -Subject: [PATCH] Prevent logging deadlock on salt-api subprocesses - (bsc#1159284) - ---- - salt/_logging/impl.py | 80 +++++++++++++++++++---------------- - salt/client/ssh/__init__.py | 12 +++++- - salt/client/ssh/client.py | 9 +++- - salt/client/ssh/wrapper/cp.py | 2 +- - salt/loader.py | 6 +-- - salt/utils/lazy.py | 10 ++--- - 6 files changed, 71 insertions(+), 48 deletions(-) - -diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py -index afb0027eeb..ad46c10e01 100644 ---- a/salt/_logging/impl.py -+++ b/salt/_logging/impl.py -@@ -15,6 +15,7 @@ PROFILE = logging.PROFILE = 15 - TRACE = logging.TRACE = 5 - GARBAGE = logging.GARBAGE = 1 - QUIET = logging.QUIET = 1000 -+DEBUG = logging.DEBUG = 10 - - from salt._logging.handlers import StreamHandler # isort:skip - -@@ -182,11 +183,9 @@ class SaltLoggingClass( - """ - instance = super().__new__(cls) - -- try: -- max_logger_length = len( -- max(list(logging.Logger.manager.loggerDict), key=len) -- ) -- for handler in logging.root.handlers: -+ max_logger_length = len(max(list(logging.Logger.manager.loggerDict), key=len)) -+ for handler in logging.root.handlers: -+ try: - if handler in ( - LOGGING_NULL_HANDLER, - LOGGING_STORE_HANDLER, -@@ -207,18 +206,15 @@ class SaltLoggingClass( - match = MODNAME_PATTERN.search(fmt) - if not match: - # Not matched. Release handler and return. -- handler.release() - return instance - - if "digits" not in match.groupdict(): - # No digits group. Release handler and return. -- handler.release() - return instance - - digits = match.group("digits") - if not digits or not (digits and digits.isdigit()): - # No valid digits. Release handler and return. -- handler.release() - return instance - - if int(digits) < max_logger_length: -@@ -229,9 +225,14 @@ class SaltLoggingClass( - ) - handler.setFormatter(formatter) - handler.release() -- except ValueError: -- # There are no registered loggers yet -- pass -+ except ValueError: -+ # There are no registered loggers yet -+ pass -+ finally: -+ try: -+ handler.release() -+ except: -+ pass - return instance - - def _log( -@@ -281,31 +282,37 @@ class SaltLoggingClass( - else: - extra["exc_info_on_loglevel"] = exc_info_on_loglevel - -- if sys.version_info < (3,): -- LOGGING_LOGGER_CLASS._log( -- self, level, msg, args, exc_info=exc_info, extra=extra -- ) -- elif sys.version_info < (3, 8): -- LOGGING_LOGGER_CLASS._log( -- self, -- level, -- msg, -- args, -- exc_info=exc_info, -- extra=extra, -- stack_info=stack_info, -- ) -- else: -- LOGGING_LOGGER_CLASS._log( -- self, -- level, -- msg, -- args, -- exc_info=exc_info, -- extra=extra, -- stack_info=stack_info, -- stacklevel=stacklevel, -- ) -+ try: -+ logging._acquireLock() -+ if sys.version_info < (3,): -+ LOGGING_LOGGER_CLASS._log( -+ self, level, msg, args, exc_info=exc_info, extra=extra -+ ) -+ elif sys.version_info < (3, 8): -+ LOGGING_LOGGER_CLASS._log( -+ self, -+ level, -+ msg, -+ args, -+ exc_info=exc_info, -+ extra=extra, -+ stack_info=stack_info, -+ ) -+ else: -+ LOGGING_LOGGER_CLASS._log( -+ self, -+ level, -+ msg, -+ args, -+ exc_info=exc_info, -+ extra=extra, -+ stack_info=stack_info, -+ stacklevel=stacklevel, -+ ) -+ except: -+ pass -+ finally: -+ logging._releaseLock() - - def makeRecord( - self, -@@ -395,6 +402,7 @@ if logging.getLoggerClass() is not SaltLoggingClass: - logging.addLevelName(PROFILE, "PROFILE") - logging.addLevelName(TRACE, "TRACE") - logging.addLevelName(GARBAGE, "GARBAGE") -+ logging.addLevelName(DEBUG, "DEBUG") - if pip_log_module is not None: - # Let's make newer versions of pip work by patching SaltLoggingClass to - # add a verbose method which is what pip expects -diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py -index 01fb5e9a30..a4a74aa2c2 100644 ---- a/salt/client/ssh/__init__.py -+++ b/salt/client/ssh/__init__.py -@@ -532,7 +532,9 @@ class SSH: - **target - ) - ret = {"id": single.id} -+ logging._acquireLock() - stdout, stderr, retcode = single.run() -+ logging._releaseLock() - # This job is done, yield - try: - data = salt.utils.json.find_json(stdout) -@@ -600,8 +602,14 @@ class SSH: - self.targets[host], - mine, - ) -- routine = Process(target=self.handle_routine, args=args) -- routine.start() -+ try: -+ logging._acquireLock() -+ routine = Process(target=self.handle_routine, args=args) -+ routine.start() -+ except: -+ pass -+ finally: -+ logging._releaseLock() - running[host] = {"thread": routine} - continue - ret = {} -diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py -index ef4287463b..037aea779c 100644 ---- a/salt/client/ssh/client.py -+++ b/salt/client/ssh/client.py -@@ -1,7 +1,9 @@ - import copy - import logging -+import multiprocessing - import os - import random -+import time - - import salt.config - import salt.syspaths -@@ -10,6 +12,7 @@ from salt.exceptions import SaltClientError - - log = logging.getLogger(__name__) - -+_LOCK = multiprocessing.Lock() - - class SSHClient: - """ -@@ -124,7 +127,11 @@ class SSHClient: - opts["selected_target_option"] = tgt_type - opts["tgt"] = tgt - opts["arg"] = arg -- return salt.client.ssh.SSH(opts) -+ _LOCK.acquire() -+ ret = salt.client.ssh.SSH(opts) -+ time.sleep(0.01) -+ _LOCK.release() -+ return ret - - def cmd_iter( - self, -diff --git a/salt/client/ssh/wrapper/cp.py b/salt/client/ssh/wrapper/cp.py -index 020dadfa35..af7d4f73f4 100644 ---- a/salt/client/ssh/wrapper/cp.py -+++ b/salt/client/ssh/wrapper/cp.py -@@ -2,10 +2,10 @@ - Wrap the cp module allowing for managed ssh file transfers - """ - --import logging - import os - - import salt.client.ssh -+import salt.log.setup as logging - import salt.utils.files - import salt.utils.stringutils - import salt.utils.templates -diff --git a/salt/loader.py b/salt/loader.py -index 491768a028..cdcec70262 100644 ---- a/salt/loader.py -+++ b/salt/loader.py -@@ -10,7 +10,6 @@ import importlib - import importlib.machinery # pylint: disable=no-name-in-module,import-error - import importlib.util # pylint: disable=no-name-in-module,import-error - import inspect --import logging - import os - import re - import sys -@@ -27,6 +26,7 @@ import salt.config - import salt.defaults.events - import salt.defaults.exitcodes - import salt.loader_context -+import salt.log.setup as logging - import salt.syspaths - import salt.utils.args - import salt.utils.context -@@ -2195,7 +2195,7 @@ class LazyLoader(salt.utils.lazy.LazyDict): - mod.__name__, exc - ) - ) -- log.error(error_reason, exc_info_on_loglevel=logging.DEBUG) -+ log.error(error_reason, exc_info_on_loglevel=logging.logging.DEBUG) - virtual = None - # Get the module's virtual name - virtualname = getattr(mod, "__virtualname__", virtual) -@@ -2341,5 +2341,5 @@ def catch_entry_points_exception(entry_point): - entry_point_details.name, - entry_point_details.version, - exc, -- exc_info_on_loglevel=logging.DEBUG, -+ exc_info_on_loglevel=logging.logging.DEBUG, - ) -diff --git a/salt/utils/lazy.py b/salt/utils/lazy.py -index 8fc538164a..c828dd2c32 100644 ---- a/salt/utils/lazy.py -+++ b/salt/utils/lazy.py -@@ -1,15 +1,13 @@ --# -*- coding: utf-8 -*- - """ - Lazily-evaluated data structures, primarily used by Salt's loader - """ - --# Import Python Libs --from __future__ import absolute_import, unicode_literals - --import logging -+import time - from collections.abc import MutableMapping - - import salt.exceptions -+import salt.log.setup as logging - - log = logging.getLogger(__name__) - -@@ -81,7 +79,7 @@ class LazyDict(MutableMapping): - - Override this to return a more meaningfull error message if possible - """ -- return "'{0}' is not available.".format(function_name) -+ return "'{}' is not available.".format(function_name) - - def __setitem__(self, key, val): - self._dict[key] = val -@@ -100,11 +98,13 @@ class LazyDict(MutableMapping): - # load the item - if self._load(key): - log.debug("LazyLoaded %s", key) -+ time.sleep(0.0001) - return self._dict[key] - else: - log.debug( - "Could not LazyLoad %s: %s", key, self.missing_fun_string(key) - ) -+ time.sleep(0.0001) - raise KeyError(key) - else: - return self._dict[key] --- -2.33.0 - - diff --git a/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch b/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch index dcfbfe3..b3a3efe 100644 --- a/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch +++ b/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch @@ -1,4 +1,4 @@ -From ad5baab333cb80ce47e65605c47c8ca6fc6d4514 Mon Sep 17 00:00:00 2001 +From 27db7d49c4b3348d5dcfe229f0d5823c0e770179 Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> Date: Mon, 8 Nov 2021 17:42:36 +0300 Subject: [PATCH] Prevent pkg plugins errors on missing cookie path @@ -12,11 +12,11 @@ Subject: [PATCH] Prevent pkg plugins errors on missing cookie path * Fix yumnotify --- - scripts/suse/dpkg/dpkgnotify | 18 ++++++++++++++--- + scripts/suse/dpkg/dpkgnotify | 18 +++++++++++++++--- scripts/suse/yum/plugins/README.md | 2 +- - scripts/suse/yum/plugins/yumnotify.py | 17 ++++++++++++---- - scripts/suse/zypper/plugins/commit/zyppnotify | 20 ++++++++++++------- - 4 files changed, 42 insertions(+), 15 deletions(-) + scripts/suse/yum/plugins/yumnotify.py | 17 +++++++++++++---- + scripts/suse/zypper/plugins/commit/zyppnotify | 18 ++++++++++++------ + 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/scripts/suse/dpkg/dpkgnotify b/scripts/suse/dpkg/dpkgnotify index d3ad3d2ba9..3d6d038a98 100644 @@ -104,7 +104,7 @@ index 4e137191a0..0d117e8946 100644 + except OSError as e: + print("Unable to save the cookie file: %s" % (e), file=sys.stderr) diff --git a/scripts/suse/zypper/plugins/commit/zyppnotify b/scripts/suse/zypper/plugins/commit/zyppnotify -index d6a1bef42b..e3528e87a9 100755 +index bacbc8b97e..e3528e87a9 100755 --- a/scripts/suse/zypper/plugins/commit/zyppnotify +++ b/scripts/suse/zypper/plugins/commit/zyppnotify @@ -1,4 +1,4 @@ @@ -113,12 +113,8 @@ index d6a1bef42b..e3528e87a9 100755 # # Copyright (c) 2016 SUSE Linux LLC # All Rights Reserved. -@@ -52,15 +52,21 @@ class DriftDetector(Plugin): - - def PLUGINEND(self, headers, body): - """ -- Hook when plugin closes Zypper's transaction. -+ Hook when plugin closes Zypper's transaction. +@@ -55,12 +55,18 @@ class DriftDetector(Plugin): + Hook when plugin closes Zypper's transaction. """ if "SALT_RUNNING" not in os.environ: - with open(self.ck_path, "w") as ck_fh: @@ -142,6 +138,6 @@ index d6a1bef42b..e3528e87a9 100755 self.ack() -- -2.33.1 +2.34.1 diff --git a/refactor-and-improvements-for-transactional-updates-.patch b/refactor-and-improvements-for-transactional-updates-.patch index f4f0022..35964da 100644 --- a/refactor-and-improvements-for-transactional-updates-.patch +++ b/refactor-and-improvements-for-transactional-updates-.patch @@ -1,7 +1,6 @@ -From b458f53eaa4930abab230ca734f5930b95b8def0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Thu, 4 Nov 2021 16:07:01 +0000 +From e28a67ec69b4781fc9c667a9cdec66192e4cca45 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Wed, 19 Jan 2022 17:45:01 +0100 Subject: [PATCH] Refactor and improvements for "transactional-updates" module @@ -26,8 +25,8 @@ Remove hack about tukit issue that has been already fixed salt/modules/state.py | 12 +- salt/modules/transactional_update.py | 235 ++--------- salt/utils/parsers.py | 6 + + .../pytests/unit/modules/state/test_state.py | 2 +- .../unit/modules/test_transactional_update.py | 389 ++---------------- - tests/unit/modules/test_state.py | 2 +- 7 files changed, 81 insertions(+), 568 deletions(-) create mode 100644 changelog/61188.fixed @@ -41,10 +40,10 @@ index 0000000000..102a8982a6 +Make "state.highstate" to acts on concurrent flag. +Simplify "transactional_update" module to not use SSH wrapper and allow more flexible execution diff --git a/salt/cli/caller.py b/salt/cli/caller.py -index 0e1fa9f90b..af8cc84a29 100644 +index 795d32e4c9..10eb5e397a 100644 --- a/salt/cli/caller.py +++ b/salt/cli/caller.py -@@ -294,7 +294,7 @@ class BaseCaller: +@@ -293,7 +293,7 @@ class BaseCaller: pass # return the job infos back up to the respective minion's master @@ -54,10 +53,10 @@ index 0e1fa9f90b..af8cc84a29 100644 mret = ret.copy() mret["jid"] = "req" diff --git a/salt/modules/state.py b/salt/modules/state.py -index ff6998a0e3..0636b7d894 100644 +index c78072131b..0c3dfc3317 100644 --- a/salt/modules/state.py +++ b/salt/modules/state.py -@@ -1052,9 +1052,15 @@ def highstate(test=None, queue=False, **kwargs): +@@ -1053,9 +1053,15 @@ def highstate(test=None, queue=False, **kwargs): } return ret @@ -394,12 +393,12 @@ index 799fe08e4d..28b02f8fec 100644 + **kwargs ) diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py -index 31a2bf0b64..6cfb34a550 100644 +index c0820e5df0..5ff3c964be 100644 --- a/salt/utils/parsers.py +++ b/salt/utils/parsers.py -@@ -3079,6 +3079,12 @@ class SaltCallOptionParser( +@@ -3108,6 +3108,12 @@ class SaltCallOptionParser( action="store_true", - help=("Force a refresh of the grains cache."), + help="Force a refresh of the grains cache.", ) + self.add_option( + "--no-return-event", @@ -410,15 +409,28 @@ index 31a2bf0b64..6cfb34a550 100644 self.add_option( "-t", "--timeout", +diff --git a/tests/pytests/unit/modules/state/test_state.py b/tests/pytests/unit/modules/state/test_state.py +index 3fa663edeb..02fd2dd307 100644 +--- a/tests/pytests/unit/modules/state/test_state.py ++++ b/tests/pytests/unit/modules/state/test_state.py +@@ -777,7 +777,7 @@ def test_highstate(): + } + + mock = MagicMock(side_effect=["A", None, None]) +- with patch.object(state, "_check_queue", mock): ++ with patch.object(state, "running", mock): + assert state.highstate("whitelist=sls1.sls") == "A" + + with patch.dict(state.__opts__, {"test": "A"}): diff --git a/tests/pytests/unit/modules/test_transactional_update.py b/tests/pytests/unit/modules/test_transactional_update.py -index e7293cf3e2..64c06c1693 100644 +index 032ca0c9e8..40dab0e2f6 100644 --- a/tests/pytests/unit/modules/test_transactional_update.py +++ b/tests/pytests/unit/modules/test_transactional_update.py @@ -1,5 +1,3 @@ -import sys - import pytest - import salt.loader_context + import salt.loader.context import salt.modules.state as statemod @@ -353,114 +351,23 @@ def test_call_fails_input_validation(): tu.call("") @@ -1045,20 +1057,7 @@ index e7293cf3e2..64c06c1693 100644 ): assert tu.single("pkg.installed", name="emacs", queue=True) == "result" - _create_and_execute_salt_state_mock.assert_called_once() -diff --git a/tests/unit/modules/test_state.py b/tests/unit/modules/test_state.py -index ffa5428873..03f434dbcc 100644 ---- a/tests/unit/modules/test_state.py -+++ b/tests/unit/modules/test_state.py -@@ -801,7 +801,7 @@ class StateTestCase(TestCase, LoaderModuleMockMixin): - ) - - mock = MagicMock(side_effect=["A", None, None]) -- with patch.object(state, "_check_queue", mock): -+ with patch.object(state, "running", mock): - self.assertEqual(state.highstate("whitelist=sls1.sls"), "A") - - with patch.dict(state.__opts__, {"test": "A"}): -- -2.33.1 +2.34.1 diff --git a/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch b/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch index ca2252c..434424d 100644 --- a/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch +++ b/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch @@ -1,7 +1,6 @@ -From 93a38f70953f48d10e21ba8601e1a562a4b739fb Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Tue, 5 Jan 2021 12:31:26 +0000 +From a82b6316d8a780a7a8cbfbabeb52fa50b3fb1032 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 19:07:34 +0100 Subject: [PATCH] Revert "Fixing a use case when multiple inotify beacons are defined but when notifications are fired the configuration fron the first beacon are used." Revert "Adding a util function to remove hidden (options @@ -12,20 +11,20 @@ Subject: [PATCH] Revert "Fixing a use case when multiple inotify beacons This reverts commit 68a891ab2fe53ebf329b9c83b875f3575e87e266. This reverts commit 66c58dedf8c364eaeb35c5adce8bcc8fe5c1219a. --- - salt/beacons/__init__.py | 1 - - salt/beacons/diskusage.py | 3 --- - salt/beacons/inotify.py | 25 +++++++------------ - salt/beacons/napalm_beacon.py | 6 ++--- - salt/beacons/status.py | 4 --- - tests/unit/beacons/test_inotify.py | 39 ------------------------------ - tests/unit/test_beacons.py | 25 +++---------------- - 7 files changed, 15 insertions(+), 88 deletions(-) + salt/beacons/__init__.py | 1 - + salt/beacons/diskusage.py | 3 --- + salt/beacons/inotify.py | 24 ++++++++-------------- + salt/beacons/napalm_beacon.py | 6 ++---- + salt/beacons/status.py | 4 ---- + tests/pytests/unit/beacons/test_inotify.py | 5 +---- + tests/pytests/unit/test_beacons.py | 16 --------------- + 7 files changed, 11 insertions(+), 48 deletions(-) diff --git a/salt/beacons/__init__.py b/salt/beacons/__init__.py -index cd00b15275..967f9ff64b 100644 +index 414142c262..13e2ae78db 100644 --- a/salt/beacons/__init__.py +++ b/salt/beacons/__init__.py -@@ -70,7 +70,6 @@ class Beacon: +@@ -71,7 +71,6 @@ class Beacon: beacon_name = current_beacon_config["beacon_module"] else: beacon_name = mod @@ -34,7 +33,7 @@ index cd00b15275..967f9ff64b 100644 validate_str = "{}.validate".format(beacon_name) if fun_str in self.beacons: diff --git a/salt/beacons/diskusage.py b/salt/beacons/diskusage.py -index 5624ebaffd..a015eebdc7 100644 +index 1216abf79b..897bc90a6a 100644 --- a/salt/beacons/diskusage.py +++ b/salt/beacons/diskusage.py @@ -8,7 +8,6 @@ Beacon to monitor disk usage. @@ -55,18 +54,10 @@ index 5624ebaffd..a015eebdc7 100644 ret = [] for mounts in config: diff --git a/salt/beacons/inotify.py b/salt/beacons/inotify.py -index 4d9ffbeb87..031da181f0 100644 +index b6e7334eee..c44bd49fb0 100644 --- a/salt/beacons/inotify.py +++ b/salt/beacons/inotify.py -@@ -21,6 +21,7 @@ import os - import re - - import salt.utils.beacons -+import salt.ext.six - - try: - import pyinotify -@@ -65,19 +66,17 @@ def _get_notifier(config): +@@ -65,19 +65,17 @@ def _get_notifier(config): """ Check the context for the notifier and construct it if not present """ @@ -90,30 +81,30 @@ index 4d9ffbeb87..031da181f0 100644 def validate(config): -@@ -253,10 +252,6 @@ def beacon(config): - affects all paths that are being watched. This is due to this option +@@ -237,9 +235,6 @@ def beacon(config): being at the Notifier level in pyinotify. """ -- + - whitelist = ["_beacon_name"] - config = salt.utils.beacons.remove_hidden_options(config, whitelist) - - _config = {} - list(map(_config.update, config)) + config = salt.utils.beacons.list_to_dict(config) -@@ -280,7 +275,7 @@ def beacon(config): + ret = [] +@@ -262,7 +257,7 @@ def beacon(config): break path = os.path.dirname(path) -- excludes = _config["files"].get(path, {}).get("exclude", "") -+ excludes = _config["files"][path].get("exclude", "") +- excludes = config["files"].get(path, {}).get("exclude", "") ++ excludes = config["files"][path].get("exclude", "") if excludes and isinstance(excludes, list): for exclude in excludes: -@@ -367,8 +362,6 @@ def beacon(config): +@@ -349,9 +344,6 @@ def beacon(config): def close(config): +- config = salt.utils.beacons.list_to_dict(config) - beacon_name = config.get("_beacon_name", "inotify") - notifier = "{}.notifier".format(beacon_name) - if notifier in __context__: @@ -123,7 +114,7 @@ index 4d9ffbeb87..031da181f0 100644 + __context__["inotify.notifier"].stop() + del __context__["inotify.notifier"] diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py -index 3ca4d10512..d1bddccb8e 100644 +index ec8cf63fca..164b29cdf8 100644 --- a/salt/beacons/napalm_beacon.py +++ b/salt/beacons/napalm_beacon.py @@ -168,9 +168,10 @@ with a NTP server at a stratum level greater than 5. @@ -138,7 +129,7 @@ index 3ca4d10512..d1bddccb8e 100644 import salt.utils.napalm log = logging.getLogger(__name__) -@@ -302,9 +303,6 @@ def beacon(config): +@@ -301,9 +302,6 @@ def beacon(config): """ Watch napalm function and fire events. """ @@ -149,7 +140,7 @@ index 3ca4d10512..d1bddccb8e 100644 log.debug(config) ret = [] diff --git a/salt/beacons/status.py b/salt/beacons/status.py -index bd7046aaa1..a30bf5f4e6 100644 +index aa5aa13b47..e2c3177ea8 100644 --- a/salt/beacons/status.py +++ b/salt/beacons/status.py @@ -91,7 +91,6 @@ import datetime @@ -170,101 +161,55 @@ index bd7046aaa1..a30bf5f4e6 100644 if not config: config = [ { -diff --git a/tests/unit/beacons/test_inotify.py b/tests/unit/beacons/test_inotify.py -index 665e334fbc..d91a2daebf 100644 ---- a/tests/unit/beacons/test_inotify.py -+++ b/tests/unit/beacons/test_inotify.py -@@ -273,42 +273,3 @@ class INotifyBeaconTestCase(TestCase, LoaderModuleMockMixin): - self.assertEqual(len(ret), 1) - self.assertEqual(ret[0]["path"], fp) - self.assertEqual(ret[0]["change"], "IN_DELETE") -- -- # Check __get_notifier and ensure that the right bits are in __context__ -- # including a beacon_name specific notifier is found. -- def test__get_notifier(self): -- config = { -- "files": { -- "/tmp/httpd/vhost.d": { -- "mask": ["delete", "modify"], -- "recurse": True, -- "auto_add": True, -- "exclude": [ -- {"/tmp/httpd/vhost.d/.+?\\.sw[px]*$|4913|~$": {"regex": True}} -- ], -- }, -- "/tmp/httpd/conf.d": { -- "mask": ["delete", "modify"], -- "recurse": True, -- "auto_add": True, -- "exclude": [ -- {"/tmp/httpd/vhost.d/.+?\\.sw[px]*$|4913|~$": {"regex": True}} -- ], -- }, -- "/tmp/httpd/conf": { -- "mask": ["delete", "modify"], -- "recurse": True, -- "auto_add": True, -- "exclude": [ -- {"/tmp/httpd/vhost.d/.+?\\.sw[px]*$|4913|~$": {"regex": True}} -- ], -- }, -- }, -- "coalesce": True, -- "beacon_module": "inotify", -- "_beacon_name": "httpd.inotify", -- } -- -- ret = inotify._get_notifier(config) -- self.assertIn("inotify.queue", inotify.__context__) -- self.assertIn("httpd.inotify.notifier", inotify.__context__) -diff --git a/tests/unit/test_beacons.py b/tests/unit/test_beacons.py -index b7a5127179..be629f49d4 100644 ---- a/tests/unit/test_beacons.py -+++ b/tests/unit/test_beacons.py -@@ -7,7 +7,7 @@ import logging - import salt.beacons as beacons - import salt.config - from tests.support.mixins import LoaderModuleMockMixin --from tests.support.mock import MagicMock, call, patch -+from tests.support.mock import patch - from tests.support.unit import TestCase +diff --git a/tests/pytests/unit/beacons/test_inotify.py b/tests/pytests/unit/beacons/test_inotify.py +index f5befb2756..dfaf1d499a 100644 +--- a/tests/pytests/unit/beacons/test_inotify.py ++++ b/tests/pytests/unit/beacons/test_inotify.py +@@ -273,7 +273,6 @@ def test_multi_files_exclude(tmp_path): - log = logging.getLogger(__name__) -@@ -35,9 +35,9 @@ class BeaconsTestCase(TestCase, LoaderModuleMockMixin): - ] - } - with patch.dict(beacons.__opts__, mock_opts): -- beacon = salt.beacons.Beacon(mock_opts, []) -- ret = beacon.process(mock_opts["beacons"], mock_opts["grains"]) -- -+ ret = salt.beacons.Beacon(mock_opts, []).process( -+ mock_opts["beacons"], mock_opts["grains"] -+ ) - _expected = [ - { - "tag": "salt/beacon/minion/watch_apache/", -@@ -46,20 +46,3 @@ class BeaconsTestCase(TestCase, LoaderModuleMockMixin): - } - ] - self.assertEqual(ret, _expected) -- -- # Ensure that "beacon_name" is available in the call to the beacon function -- name = "ps.beacon" -- mocked = {name: MagicMock(return_value=_expected)} -- mocked[name].__globals__ = {} -- calls = [ -- call( -- [ -- {"processes": {"apache2": "stopped"}}, -- {"beacon_module": "ps"}, -- {"_beacon_name": "watch_apache"}, -- ] -- ) + + # Check __get_notifier and ensure that the right bits are in __context__ +-# including a beacon_name specific notifier is found. + def test__get_notifier(): + config = { + "files": { +@@ -303,10 +302,8 @@ def test__get_notifier(): + }, + }, + "coalesce": True, +- "beacon_module": "inotify", +- "_beacon_name": "httpd.inotify", + } + + ret = inotify._get_notifier(config) + assert "inotify.queue" in inotify.__context__ +- assert "httpd.inotify.notifier" in inotify.__context__ ++ assert "inotify.notifier" in inotify.__context__ +diff --git a/tests/pytests/unit/test_beacons.py b/tests/pytests/unit/test_beacons.py +index 27940c6f65..a347f3f27f 100644 +--- a/tests/pytests/unit/test_beacons.py ++++ b/tests/pytests/unit/test_beacons.py +@@ -70,19 +70,3 @@ def test_beacon_module(): + ] + assert ret == _expected + +- # Ensure that "beacon_name" is available in the call to the beacon function +- name = "ps.beacon" +- mocked = {name: MagicMock(return_value=_expected)} +- mocked[name].__globals__ = {} +- calls = [ +- call( +- [ +- {"processes": {"apache2": "stopped"}}, +- {"beacon_module": "ps"}, +- {"_beacon_name": "watch_apache"}, - ] -- with patch.object(beacon, "beacons", mocked) as patched: -- beacon.process(mock_opts["beacons"], mock_opts["grains"]) -- patched[name].assert_has_calls(calls) +- ) +- ] +- with patch.object(beacon, "beacons", mocked) as patched: +- beacon.process(mock_opts["beacons"], mock_opts["grains"]) +- patched[name].assert_has_calls(calls) -- -2.33.0 +2.34.1 diff --git a/run-salt-master-as-dedicated-salt-user.patch b/run-salt-master-as-dedicated-salt-user.patch index 754ef80..2ea2968 100644 --- a/run-salt-master-as-dedicated-salt-user.patch +++ b/run-salt-master-as-dedicated-salt-user.patch @@ -1,4 +1,4 @@ -From 88f40fff3b81edaa55f37949f56c67112ca2dcad Mon Sep 17 00:00:00 2001 +From 3d75826c24a6a1533623982cc4d92325c739d908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20K=C3=A4mpf?= Date: Wed, 20 Jan 2016 11:01:06 +0100 Subject: [PATCH] Run salt master as dedicated salt user @@ -10,7 +10,7 @@ Subject: [PATCH] Run salt master as dedicated salt user 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/conf/master b/conf/master -index 41a62f2f34..943c5b5846 100644 +index 07bf2e9591..6415a536e7 100644 --- a/conf/master +++ b/conf/master @@ -25,7 +25,8 @@ @@ -42,6 +42,6 @@ index a0306ff370..97d158db18 100644 missingok rotate 7 -- -2.29.2 +2.34.1 diff --git a/salt.changes b/salt.changes index 40ce008..7af2ebe 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,61 @@ +------------------------------------------------------------------- +Wed Jan 26 17:04:36 UTC 2022 - Alexander Graul + +- Update to version 3004, see release notes: https://docs.saltproject.io/en/master/topics/releases/3004.html +- Don't check for cached pillar errors on state.apply (bsc#1190781) + +- Added: + * state.apply-don-t-check-for-cached-pillar-errors.patch + +- Modified: + * add-migrated-state-and-gpg-key-management-functions-.patch + * switch-firewalld-state-to-use-change_interface.patch + * include-aliases-in-the-fqdns-grains.patch + * debian-info_installed-compatibility-50453.patch + * info_installed-works-without-status-attr-now.patch + * fix-traceback.print_exc-calls-for-test_pip_state-432.patch + * add-custom-suse-capabilities-as-grains.patch + * add-rpm_vercmp-python-library-for-version-comparison.patch + * 3003.3-do-not-consider-skipped-targets-as-failed-for.patch + * support-transactional-systems-microos.patch + * do-not-crash-when-unexpected-cmd-output-at-listing-p.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * update-target-fix-for-salt-ssh-to-process-targets-li.patch + * fix-exception-in-yumpkg.remove-for-not-installed-pac.patch + * enhance-openscap-module-add-xccdf_eval-call-386.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + * run-salt-master-as-dedicated-salt-user.patch + * 3003.3-postgresql-json-support-in-pillar-423.patch + * prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch + * early-feature-support-config.patch + * implementation-of-held-unheld-functions-for-state-pk.patch + * x509-fixes-111.patch + * fix-issues-with-salt-ssh-s-extra-filerefs.patch + * mock-ip_addrs-in-utils-minions.py-unit-test-443.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * refactor-and-improvements-for-transactional-updates-.patch + * improvements-on-ansiblegate-module-354.patch + * revert-fixing-a-use-case-when-multiple-inotify-beaco.patch + +- Removed: + * add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch + * prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch + * do-not-break-master_tops-for-minion-with-version-low.patch + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * do-not-monkey-patch-yaml-bsc-1177474.patch + * add-missing-aarch64-to-rpm-package-architectures-405.patch + * figure-out-python-interpreter-to-use-inside-containe.patch + * parsing-epoch-out-of-version-provided-during-pkg-rem.patch + * fix-a-test-and-some-variable-names-229.patch + * add-astra-linux-common-edition-to-the-os-family-list.patch + * better-handling-of-bad-public-keys-from-minions-bsc-.patch + * templates-move-the-globals-up-to-the-environment-jin.patch + * virt-enhancements.patch + * fix-aptpkg.normalize_name-when-package-arch-is-all.patch + * adding-preliminary-support-for-rocky.-59682-391.patch + * fix-save-for-iptables-state-module-bsc-1185131-372.patch + ------------------------------------------------------------------- Mon Nov 15 15:14:54 UTC 2021 - Pablo Suárez Hernández diff --git a/salt.spec b/salt.spec index a1a2d30..5940c05 100644 --- a/salt.spec +++ b/salt.spec @@ -36,7 +36,7 @@ %bcond_with builddocs Name: salt -Version: 3003.3 +Version: 3004 Release: 0 Summary: A parallel remote execution system License: Apache-2.0 @@ -115,12 +115,10 @@ Patch16: return-the-expected-powerpc-os-arch-bsc-1117995.patch Patch17: fix-issue-2068-test.patch # PATCH_FIX_OPENSUSE Temporary fix allowing "id_" and "force" params while upstrem figures it out Patch18: temporary-fix-extend-the-whitelist-of-allowed-comman.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58086 (merged but not in 3003) -Patch19: don-t-call-zypper-with-more-than-one-no-refresh.patch ### FQDNS #### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51384 (master PR not yet created) -Patch20: include-aliases-in-the-fqdns-grains.patch +Patch19: include-aliases-in-the-fqdns-grains.patch ########### #### BATCH ASYNC - unified ##### @@ -143,172 +141,134 @@ Patch20: include-aliases-in-the-fqdns-grains.patch # PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/b4c401cfe6031b61e27f7795bfa1aca6e8341e52 # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/320 # PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/25b4e3ea983b2606b2fb3d3c0e42f9840208bf84 (cleanup local code) -Patch21: async-batch-implementation.patch +Patch20: async-batch-implementation.patch ########### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52743 -Patch22: switch-firewalld-state-to-use-change_interface.patch +Patch21: switch-firewalld-state-to-use-change_interface.patch ### STANDALONE FORMULA CONFIGURATION ### # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/8ad65d6fa39edc7fc1967e2df1f3db0aa7df4d11 -Patch23: add-standalone-configuration-file-for-enabling-packa.patch +Patch22: add-standalone-configuration-file-for-enabling-packa.patch ############# # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/53159 (missing PR to master) -Patch24: batch.py-avoid-exception-when-minion-does-not-respon.patch +Patch23: batch.py-avoid-exception-when-minion-does-not-respon.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/177 # (deviation from upstream - we should probably port this) -Patch25: restore-default-behaviour-of-pkg-list-return.patch +Patch24: restore-default-behaviour-of-pkg-list-return.patch # PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/186 (missing upstream PR to master) -Patch26: read-repo-info-without-using-interpolation-bsc-11356.patch +Patch25: read-repo-info-without-using-interpolation-bsc-11356.patch # PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/191 (missing upstream PR to master) -Patch27: let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/56125 (merged but not in 3003: https://github.com/saltstack/salt/pull/59353) -Patch28: add-astra-linux-common-edition-to-the-os-family-list.patch +Patch26: let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch # PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/a8f0a15e4067ec278c8a2d690e3bf815523286ca (missing upstream PR) -Patch29: fix-wrong-test_mod_del_repo_multiline_values-test-af.patch +Patch27: fix-wrong-test_mod_del_repo_multiline_values-test-af.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/a18ac47b75550bd55f4ca91dc221ed408881984c -Patch30: make-setup.py-script-to-not-require-setuptools-9.1.patch +Patch28: make-setup.py-script-to-not-require-setuptools-9.1.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/228 (missing upstream PR) # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/61017 -Patch31: adds-explicit-type-cast-for-port.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57123 -Patch32: prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch -# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/229 -# (no upstream PR, fixes tests locally) -Patch33: fix-a-test-and-some-variable-names-229.patch +Patch29: adds-explicit-type-cast-for-port.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/da936daeebd701e147707ad814c07bfc259d4be (not yet upstream PR) -Patch34: add-publish_batch-to-clearfuncs-exposed-methods.patch +Patch30: add-publish_batch-to-clearfuncs-exposed-methods.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57779 -Patch35: info_installed-works-without-status-attr-now.patch +Patch31: info_installed-works-without-status-attr-now.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58552 -Patch36: zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch +Patch32: zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch #### MICROOS - TRANSACTIONAL UPDATES #### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58520 (master PR merged but not included in 3003) # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60903 -Patch37: support-transactional-systems-microos.patch +Patch33: support-transactional-systems-microos.patch ########### # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/275 (missing upstream PR) -Patch38: bsc-1176024-fix-file-directory-user-and-group-owners.patch +Patch34: bsc-1176024-fix-file-directory-user-and-group-owners.patch #### NO VENDOR CHANGE #### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60421 -Patch39: allow-vendor-change-option-with-zypper.patch +Patch35: allow-vendor-change-option-with-zypper.patch ########### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58784 -Patch40: add-migrated-state-and-gpg-key-management-functions-.patch +Patch36: add-migrated-state-and-gpg-key-management-functions-.patch ### BEACON CONFIG ### # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/5ea2f10b15684dd417bad858642faafc92cd382 # (revert https://github.com/saltstack/salt/pull/58655) -Patch41: revert-fixing-a-use-case-when-multiple-inotify-beaco.patch +Patch37: revert-fixing-a-use-case-when-multiple-inotify-beaco.patch ########### -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59269 (master PR merged but not included in 3003) -Patch42: fix-aptpkg.normalize_name-when-package-arch-is-all.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/298 (missing upstream PR) -Patch43: fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch +Patch38: fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/304 (missing uptstream PR) -Patch44: force-zyppnotify-to-prefer-packages.db-than-packages.patch +Patch39: force-zyppnotify-to-prefer-packages.db-than-packages.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59354 (master PR merged but not included in 3003) -Patch45: do-not-crash-when-unexpected-cmd-output-at-listing-p.patch - -#### VIRT ENHANCEMENTS ##### -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59417 (master PR merged but not included in 3003) -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59693 (master PR merged but not included in 3003) -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60133 (master PR merged but not included in 3003) -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60420 (master PR merged but not included in 3003) -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60492 (master PR merged but not included in 3003) -Patch46: virt-enhancements.patch -################## +Patch40: do-not-crash-when-unexpected-cmd-output-at-listing-p.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/56173 -Patch47: fixes-56144-to-enable-hotadd-profile-support.patch +Patch41: fixes-56144-to-enable-hotadd-profile-support.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/307 (missing upstream PR) -Patch48: add-sleep-on-exception-handling-on-minion-connection.patch +Patch42: add-sleep-on-exception-handling-on-minion-connection.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/323 (missing upstream PR) -Patch49: implementation-of-suse_ip-execution-module-bsc-10999.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59746 (master PR merged but not included in 3003) -Patch50: do-not-monkey-patch-yaml-bsc-1177474.patch +Patch43: implementation-of-suse_ip-execution-module-bsc-10999.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/347 (missing upstream PR) -Patch51: notify-beacon-for-debian-ubuntu-systems-347.patch +Patch44: notify-beacon-for-debian-ubuntu-systems-347.patch ### SALT-SSH PROCESSING TARGETS ### # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/336 (missing upstream PR) # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/353 (missing upstream PR) -Patch52: update-target-fix-for-salt-ssh-to-process-targets-li.patch +Patch45: update-target-fix-for-salt-ssh-to-process-targets-li.patch ############ -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59687 (master PR merged but not included in 3003) -Patch53: add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch - #### ANSIBLE GATE IMPROVEMENTS #### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60056 -Patch54: improvements-on-ansiblegate-module-354.patch +Patch46: improvements-on-ansiblegate-module-354.patch ########## # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/376 (missing upstream PR) -Patch55: check-if-dpkgnotify-is-executable-bsc-1186674-376.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57881 (master PR merged but not included in 3003) -Patch56: parsing-epoch-out-of-version-provided-during-pkg-rem.patch +Patch47: check-if-dpkgnotify-is-executable-bsc-1186674-376.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58503 -Patch57: fix-missing-minion-returns-in-batch-mode-360.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60229 (master PR merged but not included in 3003) -Patch58: figure-out-python-interpreter-to-use-inside-containe.patch +Patch48: fix-missing-minion-returns-in-batch-mode-360.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60402 -Patch59: enhance-logging-when-inotify-beacon-is-missing-pyino.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60358 (master PR merged but not included in 3003) -Patch60: fix-save-for-iptables-state-module-bsc-1185131-372.patch +Patch49: enhance-logging-when-inotify-beacon-is-missing-pyino.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60356 -Patch61: fix-exception-in-yumpkg.remove-for-not-installed-pac.patch +Patch50: fix-exception-in-yumpkg.remove-for-not-installed-pac.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60432 (merged on master but not included in 3003) -Patch62: implementation-of-held-unheld-functions-for-state-pk.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59682 (master PR merged but not included in 3003) -Patch63: adding-preliminary-support-for-rocky.-59682-391.patch +Patch51: implementation-of-held-unheld-functions-for-state-pk.patch #### OPENSCAP ENHANCE #### # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59756 -Patch64: enhance-openscap-module-add-xccdf_eval-call-386.patch +Patch52: enhance-openscap-module-add-xccdf_eval-call-386.patch ############### -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60662 (master PR merged but not included in 3003) -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60688 (master PR merged but not included in 3003) -Patch65: better-handling-of-bad-public-keys-from-minions-bsc-.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60650 (master PR merged but not included in 3003) -Patch66: add-missing-aarch64-to-rpm-package-architectures-405.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/413 (missing upstream PR) -Patch67: don-t-use-shell-sbin-nologin-in-requisites.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60811 (master PR merged but not included in 3003) -Patch68: templates-move-the-globals-up-to-the-environment-jin.patch +Patch53: don-t-use-shell-sbin-nologin-in-requisites.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59777 -Patch69: 3003.3-postgresql-json-support-in-pillar-423.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60980 -Patch70: do-not-break-master_tops-for-minion-with-version-low.patch +Patch54: 3003.3-postgresql-json-support-in-pillar-423.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60983 -Patch71: 3003.3-do-not-consider-skipped-targets-as-failed-for.patch +Patch55: 3003.3-do-not-consider-skipped-targets-as-failed-for.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/61017 -Patch72: fix-crash-when-calling-manage.not_alive-runners.patch +Patch56: fix-crash-when-calling-manage.not_alive-runners.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/61014 -Patch73: fix-issues-with-salt-ssh-s-extra-filerefs.patch +Patch57: fix-issues-with-salt-ssh-s-extra-filerefs.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61061 -Patch74: fix-ip6_interface-grain-to-not-leak-secondary-ipv4-a.patch +Patch58: fix-ip6_interface-grain-to-not-leak-secondary-ipv4-a.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/432 (missing upstream PR) -Patch75: fix-traceback.print_exc-calls-for-test_pip_state-432.patch +Patch59: fix-traceback.print_exc-calls-for-test_pip_state-432.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/415 (missing upstream PR) -Patch76: prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch +Patch60: prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/60815 -Patch77: add-rpm_vercmp-python-library-for-version-comparison.patch +Patch61: add-rpm_vercmp-python-library-for-version-comparison.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61180 -Patch78: dnfnotify-pkgset-plugin-implementation-3002.2-450.patch +Patch62: dnfnotify-pkgset-plugin-implementation-3002.2-450.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/60324 -Patch79: mock-ip_addrs-in-utils-minions.py-unit-test-443.patch +Patch63: mock-ip_addrs-in-utils-minions.py-unit-test-443.patch # PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/456 (missing upstream PR) -Patch80: fix-the-regression-for-yumnotify-plugin-456.patch +Patch64: fix-the-regression-for-yumnotify-plugin-456.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61188 -Patch81: refactor-and-improvements-for-transactional-updates-.patch +Patch65: refactor-and-improvements-for-transactional-updates-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61189 +Patch66: state.apply-don-t-check-for-cached-pillar-errors.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -729,87 +689,7 @@ list of active executors. This package add the configuration file. cp %{S:1} . cp %{S:5} ./.travis.yml cp %{S:6} . -%patch1 -p1 -%patch2 -p1 -%patch3 -p1 -%patch4 -p1 -%patch5 -p1 -%patch6 -p1 -%patch7 -p1 -%patch8 -p1 -%patch9 -p1 -%patch10 -p1 -%patch11 -p1 -%patch12 -p1 -%patch13 -p1 -%patch14 -p1 -%patch15 -p1 -%patch16 -p1 -%patch17 -p1 -%patch18 -p1 -%patch19 -p1 -%patch20 -p1 -%patch21 -p1 -%patch22 -p1 -%patch23 -p1 -%patch24 -p1 -%patch25 -p1 -%patch26 -p1 -%patch27 -p1 -%patch28 -p1 -%patch29 -p1 -%patch30 -p1 -%patch31 -p1 -%patch32 -p1 -%patch33 -p1 -%patch34 -p1 -%patch35 -p1 -%patch36 -p1 -%patch37 -p1 -%patch38 -p1 -%patch39 -p1 -%patch40 -p1 -%patch41 -p1 -%patch42 -p1 -%patch43 -p1 -%patch44 -p1 -%patch45 -p1 -%patch46 -p1 -%patch47 -p1 -%patch48 -p1 -%patch49 -p1 -%patch50 -p1 -%patch51 -p1 -%patch52 -p1 -%patch53 -p1 -%patch54 -p1 -%patch55 -p1 -%patch56 -p1 -%patch57 -p1 -%patch58 -p1 -%patch59 -p1 -%patch60 -p1 -%patch61 -p1 -%patch62 -p1 -%patch63 -p1 -%patch64 -p1 -%patch65 -p1 -%patch66 -p1 -%patch67 -p1 -%patch68 -p1 -%patch69 -p1 -%patch70 -p1 -%patch71 -p1 -%patch72 -p1 -%patch73 -p1 -%patch74 -p1 -%patch75 -p1 -%patch76 -p1 -%patch77 -p1 -%patch78 -p1 -%patch79 -p1 -%patch80 -p1 -%patch81 -p1 +%autopatch -p1 %build # Putting /usr/bin at the front of $PATH is needed for RHEL/RES 7. Without this @@ -973,7 +853,7 @@ install -Dpm 0644 pkg/suse/salt.SuSEfirewall2 %{buildroot}%{_sysconfdir}/syscon install -Dpm 0644 pkg/salt.bash %{buildroot}%{_sysconfdir}/bash_completion.d/salt %endif %if %{with zsh_completion} -install -Dpm 0644 pkg/zsh_completion.zsh %{buildroot}%{_sysconfdir}/zsh_completion.d/salt +install -Dpm 0644 pkg/salt.zsh %{buildroot}%{_sysconfdir}/zsh_completion.d/salt %endif %if %{with fish_completion} diff --git a/state.apply-don-t-check-for-cached-pillar-errors.patch b/state.apply-don-t-check-for-cached-pillar-errors.patch new file mode 100644 index 0000000..05200a1 --- /dev/null +++ b/state.apply-don-t-check-for-cached-pillar-errors.patch @@ -0,0 +1,362 @@ +From 5880703551d82a68a0e2f3108878124d8ae98bf0 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 25 Jan 2022 17:20:55 +0100 +Subject: [PATCH] state.apply: don't check for cached pillar errors + +state.apply request new pillar data from the server. This done to always +have the most up-to-date pillar to work with. Previously, checking for +pillar errors looked at both the new pillar and the in-memory pillar. +The latter might contain pillar rendering errors even if the former does +not. + +For this reason, only the new pillar should be checked, not both. +--- + changelog/52354.fixed | 1 + + changelog/57180.fixed | 1 + + changelog/59339.fixed | 1 + + salt/modules/state.py | 17 ++- + .../modules/state/test_state_pillar_errors.py | 131 ++++++++++++++++++ + .../pytests/unit/modules/state/test_state.py | 115 +++++---------- + 6 files changed, 177 insertions(+), 89 deletions(-) + create mode 100644 changelog/52354.fixed + create mode 100644 changelog/57180.fixed + create mode 100644 changelog/59339.fixed + create mode 100644 tests/pytests/integration/modules/state/test_state_pillar_errors.py + +diff --git a/changelog/52354.fixed b/changelog/52354.fixed +new file mode 100644 +index 0000000000..af885d77fa +--- /dev/null ++++ b/changelog/52354.fixed +@@ -0,0 +1 @@ ++Don't check for cached pillar errors on state.apply +diff --git a/changelog/57180.fixed b/changelog/57180.fixed +new file mode 100644 +index 0000000000..af885d77fa +--- /dev/null ++++ b/changelog/57180.fixed +@@ -0,0 +1 @@ ++Don't check for cached pillar errors on state.apply +diff --git a/changelog/59339.fixed b/changelog/59339.fixed +new file mode 100644 +index 0000000000..af885d77fa +--- /dev/null ++++ b/changelog/59339.fixed +@@ -0,0 +1 @@ ++Don't check for cached pillar errors on state.apply +diff --git a/salt/modules/state.py b/salt/modules/state.py +index 0c3dfc3317..0027744229 100644 +--- a/salt/modules/state.py ++++ b/salt/modules/state.py +@@ -106,18 +106,17 @@ def _set_retcode(ret, highstate=None): + + def _get_pillar_errors(kwargs, pillar=None): + """ +- Checks all pillars (external and internal) for errors. +- Return an error message, if anywhere or None. ++ Check pillar for errors. ++ ++ If a pillar is passed, it will be checked. Otherwise, the in-memory pillar ++ will checked instead. Passing kwargs['force'] = True short cuts the check ++ and always returns None, indicating no errors. + + :param kwargs: dictionary of options +- :param pillar: external pillar +- :return: None or an error message ++ :param pillar: pillar ++ :return: None or a list of error messages + """ +- return ( +- None +- if kwargs.get("force") +- else (pillar or {}).get("_errors", __pillar__.get("_errors")) or None +- ) ++ return None if kwargs.get("force") else (pillar or __pillar__).get("_errors") + + + def _wait(jid): +diff --git a/tests/pytests/integration/modules/state/test_state_pillar_errors.py b/tests/pytests/integration/modules/state/test_state_pillar_errors.py +new file mode 100644 +index 0000000000..af65a05945 +--- /dev/null ++++ b/tests/pytests/integration/modules/state/test_state_pillar_errors.py +@@ -0,0 +1,131 @@ ++#!/usr/bin/python3 ++ ++import textwrap ++ ++import pytest ++from saltfactories.utils.functional import StateResult ++ ++pytestmark = [ ++ pytest.mark.slow_test, ++] ++ ++ ++@pytest.fixture(scope="module") ++def reset_pillar(salt_call_cli): ++ try: ++ # Run tests ++ yield ++ finally: ++ # Refresh pillar once all tests are done. ++ ret = salt_call_cli.run("saltutil.refresh_pillar", wait=True) ++ assert ret.exitcode == 0 ++ assert ret.json is True ++ ++ ++@pytest.fixture ++def testfile_path(tmp_path, base_env_state_tree_root_dir): ++ testfile = tmp_path / "testfile" ++ sls_contents = textwrap.dedent( ++ """ ++ {}: ++ file: ++ - managed ++ - source: salt://testfile ++ - makedirs: true ++ """.format(testfile) ++ ) ++ with pytest.helpers.temp_file( ++ "sls-id-test.sls", sls_contents, base_env_state_tree_root_dir ++ ): ++ yield testfile ++ ++ ++@pytest.mark.usefixtures("testfile_path", "reset_pillar") ++def test_state_apply_aborts_on_pillar_error( ++ salt_cli, ++ salt_minion, ++ base_env_pillar_tree_root_dir, ++): ++ """ ++ Test state.apply with error in pillar. ++ """ ++ pillar_top_file = textwrap.dedent( ++ """ ++ base: ++ '{}': ++ - basic ++ """ ++ ).format(salt_minion.id) ++ basic_pillar_file = textwrap.dedent( ++ """ ++ syntax_error ++ """ ++ ) ++ ++ with pytest.helpers.temp_file( ++ "top.sls", pillar_top_file, base_env_pillar_tree_root_dir ++ ), pytest.helpers.temp_file( ++ "basic.sls", basic_pillar_file, base_env_pillar_tree_root_dir ++ ): ++ expected_comment = [ ++ "Pillar failed to render with the following messages:", ++ "SLS 'basic' does not render to a dictionary", ++ ] ++ shell_result = salt_cli.run( ++ "state.apply", "sls-id-test", minion_tgt=salt_minion.id ++ ) ++ assert shell_result.exitcode == 1 ++ assert shell_result.json == expected_comment ++ ++ ++@pytest.mark.usefixtures("testfile_path", "reset_pillar") ++def test_state_apply_continues_after_pillar_error_is_fixed( ++ salt_cli, ++ salt_minion, ++ base_env_pillar_tree_root_dir, ++): ++ """ ++ Test state.apply with error in pillar. ++ """ ++ pillar_top_file = textwrap.dedent( ++ """ ++ base: ++ '{}': ++ - basic ++ """.format(salt_minion.id) ++ ) ++ basic_pillar_file_error = textwrap.dedent( ++ """ ++ syntax_error ++ """ ++ ) ++ basic_pillar_file = textwrap.dedent( ++ """ ++ syntax_error: Fixed! ++ """ ++ ) ++ ++ # save pillar render error in minion's in-memory pillar ++ with pytest.helpers.temp_file( ++ "top.sls", pillar_top_file, base_env_pillar_tree_root_dir ++ ), pytest.helpers.temp_file( ++ "basic.sls", basic_pillar_file_error, base_env_pillar_tree_root_dir ++ ): ++ shell_result = salt_cli.run( ++ "saltutil.refresh_pillar", minion_tgt=salt_minion.id ++ ) ++ assert shell_result.exitcode == 0 ++ ++ # run state.apply with fixed pillar render error ++ with pytest.helpers.temp_file( ++ "top.sls", pillar_top_file, base_env_pillar_tree_root_dir ++ ), pytest.helpers.temp_file( ++ "basic.sls", basic_pillar_file, base_env_pillar_tree_root_dir ++ ): ++ shell_result = salt_cli.run( ++ "state.apply", "sls-id-test", minion_tgt=salt_minion.id ++ ) ++ assert shell_result.exitcode == 0 ++ state_result = StateResult(shell_result.json) ++ assert state_result.result is True ++ assert state_result.changes == {"diff": "New file", "mode": "0644"} +diff --git a/tests/pytests/unit/modules/state/test_state.py b/tests/pytests/unit/modules/state/test_state.py +index 02fd2dd307..30cda303cc 100644 +--- a/tests/pytests/unit/modules/state/test_state.py ++++ b/tests/pytests/unit/modules/state/test_state.py +@@ -1,14 +1,16 @@ + """ + :codeauthor: Rahul Handay + """ +- + import datetime + import logging + import os ++from collections import namedtuple + + import pytest ++ + import salt.config + import salt.loader ++import salt.loader.context + import salt.modules.config as config + import salt.modules.state as state + import salt.state +@@ -1200,85 +1202,6 @@ def test_lock_saltenv(): + ) + + +-def test_get_pillar_errors_CC(): +- """ +- Test _get_pillar_errors function. +- CC: External clean, Internal clean +- :return: +- """ +- for int_pillar, ext_pillar in [ +- ({"foo": "bar"}, {"fred": "baz"}), +- ({"foo": "bar"}, None), +- ({}, {"fred": "baz"}), +- ]: +- with patch("salt.modules.state.__pillar__", int_pillar): +- for opts, res in [ +- ({"force": True}, None), +- ({"force": False}, None), +- ({}, None), +- ]: +- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar) +- +- +-def test_get_pillar_errors_EC(): +- """ +- Test _get_pillar_errors function. +- EC: External erroneous, Internal clean +- :return: +- """ +- errors = ["failure", "everywhere"] +- for int_pillar, ext_pillar in [ +- ({"foo": "bar"}, {"fred": "baz", "_errors": errors}), +- ({}, {"fred": "baz", "_errors": errors}), +- ]: +- with patch("salt.modules.state.__pillar__", int_pillar): +- for opts, res in [ +- ({"force": True}, None), +- ({"force": False}, errors), +- ({}, errors), +- ]: +- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar) +- +- +-def test_get_pillar_errors_EE(): +- """ +- Test _get_pillar_errors function. +- CC: External erroneous, Internal erroneous +- :return: +- """ +- errors = ["failure", "everywhere"] +- for int_pillar, ext_pillar in [ +- ({"foo": "bar", "_errors": errors}, {"fred": "baz", "_errors": errors}) +- ]: +- with patch("salt.modules.state.__pillar__", int_pillar): +- for opts, res in [ +- ({"force": True}, None), +- ({"force": False}, errors), +- ({}, errors), +- ]: +- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar) +- +- +-def test_get_pillar_errors_CE(): +- """ +- Test _get_pillar_errors function. +- CC: External clean, Internal erroneous +- :return: +- """ +- errors = ["failure", "everywhere"] +- for int_pillar, ext_pillar in [ +- ({"foo": "bar", "_errors": errors}, {"fred": "baz"}), +- ({"foo": "bar", "_errors": errors}, None), +- ]: +- with patch("salt.modules.state.__pillar__", int_pillar): +- for opts, res in [ +- ({"force": True}, None), +- ({"force": False}, errors), +- ({}, errors), +- ]: +- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar) +- +- + def test_event(): + """ + test state.event runner +@@ -1318,3 +1241,35 @@ def test_event(): + if _expected in x.args[0]: + found = True + assert found is True ++ ++ ++PillarPair = namedtuple("PillarPair", ["in_memory", "fresh"]) ++pillar_combinations = [ ++ (PillarPair({"foo": "bar"}, {"fred": "baz"}), None), ++ (PillarPair({"foo": "bar"}, {"fred": "baz", "_errors": ["Failure"]}), ["Failure"]), ++ (PillarPair({"foo": "bar"}, None), None), ++ (PillarPair({"foo": "bar", "_errors": ["Failure"]}, None), ["Failure"]), ++ (PillarPair({"foo": "bar", "_errors": ["Failure"]}, {"fred": "baz"}), None), ++] ++ ++ ++@pytest.mark.parametrize("pillar,expected_errors", pillar_combinations) ++def test_get_pillar_errors(pillar: PillarPair, expected_errors): ++ """ ++ test _get_pillar_errors function ++ ++ There are three cases to consider: ++ 1. kwargs['force'] is True -> None, no matter what's in pillar/__pillar__ ++ 2. pillar kwarg is available -> only check pillar, no matter what's in __pillar__ ++ 3. pillar kwarg is not available -> check __pillar__ ++ """ ++ ctx = salt.loader.context.LoaderContext() ++ named_ctx = ctx.named_context("__pillar__", pillar.in_memory) ++ with patch("salt.modules.state.__pillar__", named_ctx, create=True): ++ assert ( ++ state._get_pillar_errors(kwargs={"force": True}, pillar=pillar.fresh) ++ is None ++ ) ++ assert ( ++ state._get_pillar_errors(kwargs={}, pillar=pillar.fresh) == expected_errors ++ ) +-- +2.34.1 + + diff --git a/support-transactional-systems-microos.patch b/support-transactional-systems-microos.patch index 05be6a5..831dbc2 100644 --- a/support-transactional-systems-microos.patch +++ b/support-transactional-systems-microos.patch @@ -1,6 +1,6 @@ -From 686529e73fb099af90ebce9a6c5e1f432c6484f9 Mon Sep 17 00:00:00 2001 -From: Alberto Planas -Date: Tue, 21 Jul 2020 11:00:10 +0200 +From 5d7b9be571b765faae5cefc5a0810c61c2a25814 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 17:36:12 +0100 Subject: [PATCH] Support transactional systems (MicroOS) Add rebootmgr module @@ -69,37 +69,13 @@ test_transactional_update: convert to pytest Update release documentation to 3004 --- - changelog/58519.added | 1 + - doc/ref/executors/all/index.rst | 1 + - .../salt.executors.transactional_update.rst | 6 + - doc/ref/modules/all/index.rst | 2 + - .../modules/all/salt.modules.rebootmgr.rst | 5 + - .../all/salt.modules.transactional_update.rst | 5 + - doc/topics/releases/3004.rst | 45 + - salt/executors/transactional_update.py | 132 ++ - salt/grains/extra.py | 37 + - salt/modules/chroot.py | 75 +- - salt/modules/rebootmgr.py | 359 +++++ - salt/modules/systemd_service.py | 28 +- - salt/modules/transactional_update.py | 1325 +++++++++++++++++ - salt/states/service.py | 14 + - salt/utils/systemd.py | 26 +- - tests/integration/states/test_service.py | 4 + - tests/pytests/unit/modules/test_rebootmgr.py | 309 ++++ - .../unit/modules/test_transactional_update.py | 999 +++++++++++++ - tests/pytests/unit/states/test_service.py | 34 + - tests/unit/modules/test_chroot.py | 41 +- - tests/unit/modules/test_systemd_service.py | 24 +- - 21 files changed, 3441 insertions(+), 31 deletions(-) + changelog/58519.added | 1 + + salt/modules/chroot.py | 10 +++++++--- + salt/modules/transactional_update.py | 8 ++++---- + .../unit/modules/test_transactional_update.py | 10 +++++++++- + tests/unit/modules/test_chroot.py | 13 ++++++++++++- + 5 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 changelog/58519.added - create mode 100644 doc/ref/executors/all/salt.executors.transactional_update.rst - create mode 100644 doc/ref/modules/all/salt.modules.rebootmgr.rst - create mode 100644 doc/ref/modules/all/salt.modules.transactional_update.rst - create mode 100644 salt/executors/transactional_update.py - create mode 100644 salt/modules/rebootmgr.py - create mode 100644 salt/modules/transactional_update.py - create mode 100644 tests/pytests/unit/modules/test_rebootmgr.py - create mode 100644 tests/pytests/unit/modules/test_transactional_update.py diff --git a/changelog/58519.added b/changelog/58519.added new file mode 100644 @@ -109,434 +85,11 @@ index 0000000000..1cc8d7dc74 @@ -0,0 +1 @@ +Add support for transactional systems, like openSUSE MicroOS \ No newline at end of file -diff --git a/doc/ref/executors/all/index.rst b/doc/ref/executors/all/index.rst -index 1f26a86fc3..4cd430d8e3 100644 ---- a/doc/ref/executors/all/index.rst -+++ b/doc/ref/executors/all/index.rst -@@ -14,3 +14,4 @@ executors modules - docker - splay - sudo -+ transactional_update -diff --git a/doc/ref/executors/all/salt.executors.transactional_update.rst b/doc/ref/executors/all/salt.executors.transactional_update.rst -new file mode 100644 -index 0000000000..17f00b2d27 ---- /dev/null -+++ b/doc/ref/executors/all/salt.executors.transactional_update.rst -@@ -0,0 +1,6 @@ -+salt.executors.transactional_update module -+========================================== -+ -+.. automodule:: salt.executors.transactional_update -+ :members: -+ -diff --git a/doc/ref/modules/all/index.rst b/doc/ref/modules/all/index.rst -index 3fff7ad636..73958181dd 100644 ---- a/doc/ref/modules/all/index.rst -+++ b/doc/ref/modules/all/index.rst -@@ -393,6 +393,7 @@ execution modules - rbac_solaris - rbenv - rdp -+ rebootmgr - redismod - reg - rest_pkg -@@ -479,6 +480,7 @@ execution modules - tls - tomcat - trafficserver -+ transactional_update - travisci - tuned - twilio_notify -diff --git a/doc/ref/modules/all/salt.modules.rebootmgr.rst b/doc/ref/modules/all/salt.modules.rebootmgr.rst -new file mode 100644 -index 0000000000..22240080b0 ---- /dev/null -+++ b/doc/ref/modules/all/salt.modules.rebootmgr.rst -@@ -0,0 +1,5 @@ -+salt.modules.rebootmgr module -+============================= -+ -+.. automodule:: salt.modules.rebootmgr -+ :members: -diff --git a/doc/ref/modules/all/salt.modules.transactional_update.rst b/doc/ref/modules/all/salt.modules.transactional_update.rst -new file mode 100644 -index 0000000000..2f15b95ad4 ---- /dev/null -+++ b/doc/ref/modules/all/salt.modules.transactional_update.rst -@@ -0,0 +1,5 @@ -+salt.modules.transactional_update module -+======================================== -+ -+.. automodule:: salt.modules.transactional_update -+ :members: -diff --git a/doc/topics/releases/3004.rst b/doc/topics/releases/3004.rst -index 4cb9527f6a..638e2f4aad 100644 ---- a/doc/topics/releases/3004.rst -+++ b/doc/topics/releases/3004.rst -@@ -6,3 +6,48 @@ Salt 3004 Release Notes - Codename Silicon - - Salt 3004 is an *unreleased* upcoming feature release. - -+Changed -+======= -+ -+State Engine support for ``onfail`` as a requisite now can be -+used with multiple requisites. This behavior was previously -+broken and is described in detail at https://github.com/saltstack/salt/issues/59026 -+ -+ -+New Features -+============ -+ -+Transactional System Support (MicroOS) -+-------------------------------------- -+ -+A transactional system, like ``MicroOS``, can present some challenges -+when the user decided to manage it via Salt. -+ -+MicroOS provide a read-only rootfs and a tool, -+``transactional-update``, that takes care of the management of the -+system (updating, upgrading, installation or reboot, among others) in -+an atomic way. -+ -+Atomicity is the main feature of MicroOS, and to guarantee this -+property, this model leverages ``snapper``, ``zypper``, ``btrfs`` and -+``overlayfs`` to create snapshots that will be updated independently -+of the currently running system, and that are activated after the -+reboot. This implies, for example, that some changes made on the -+system are not visible until the next reboot, as those changes are -+living in a different snapshot of the file system. -+ -+Salt 3004 (Silicon) support this type of system via two new modules -+(``transactional_update`` and ``rebootmgr``) and a new executor -+(``transactional_update``). -+ -+The new modules will provide all the low level API for interacting -+with transactional systems, like defining a mantenance window where -+the system is free to reboot and activate the new state, or install -+new software in a new transaction. It will also provide hight level -+of abstractions that will allows us to execute Salt module functions -+or applying states inside new transactions. -+ -+The execution module will help us to treat the transactional system -+transparently (like the traditional ones), using a mechanism that will -+delegate some Salt modules execution into the new -+``transactional_update`` module. -diff --git a/salt/executors/transactional_update.py b/salt/executors/transactional_update.py -new file mode 100644 -index 0000000000..6f36ed03b6 ---- /dev/null -+++ b/salt/executors/transactional_update.py -@@ -0,0 +1,132 @@ -+""" -+Transactional executor module -+ -+.. versionadded:: 3004 -+ -+""" -+ -+import os -+ -+import salt.utils.path -+ -+# Functions that are mapped into an equivalent one in -+# transactional_update module -+DELEGATION_MAP = { -+ "state.single": "transactional_update.single", -+ "state.sls": "transactional_update.sls", -+ "state.apply": "transactional_update.apply", -+ "state.highstate": "transactional_update.highstate", -+} -+ -+# By default, all modules and functions are executed outside the -+# transaction. The next two sets will enumerate the exceptions that -+# will be routed to transactional_update.call() -+DEFAULT_DELEGATED_MODULES = [ -+ "ansible", -+ "cabal", -+ "chef", -+ "cmd", -+ "composer", -+ "cp", -+ "cpan", -+ "cyg", -+ "file", -+ "freeze", -+ "nix", -+ "npm", -+ "pip", -+ "pkg", -+ "puppet", -+ "pyenv", -+ "rbenv", -+ "scp", -+] -+DEFAULT_DELEGATED_FUNCTIONS = [] -+ -+ -+def __virtual__(): -+ if salt.utils.path.which("transactional-update"): -+ return True -+ else: -+ return (False, "transactional_update executor requires a transactional system") -+ -+ -+def execute(opts, data, func, args, kwargs): -+ """Delegate into transactional_update module -+ -+ The ``transactional_update`` module support the execution of -+ functions inside a transaction, as support apply a state (via -+ ``apply``, ``sls``, ``single`` or ``highstate``). -+ -+ This execution module can be used to route some Salt modules and -+ functions to be executed inside the transaction snapshot. -+ -+ Add this executor in the minion configuration file: -+ -+ .. code-block:: yaml -+ -+ module_executors: -+ - transactional_update -+ - direct_call -+ -+ Or use the command line parameter: -+ -+ .. code-block:: bash -+ -+ salt-call --module-executors='[transactional_update, direct_call]' test.version -+ -+ You can also schedule a reboot if needed: -+ -+ .. code-block:: bash -+ -+ salt-call --module-executors='[transactional_update]' state.sls stuff activate_transaction=True -+ -+ There are some configuration parameters supported: -+ -+ .. code-block:: yaml -+ -+ # Replace the list of default modules that all the functions -+ # are delegated to `transactional_update.call()` -+ delegated_modules: [cmd, pkg] -+ -+ # Replace the list of default functions that are delegated to -+ # `transactional_update.call()` -+ delegated_functions: [pip.install] -+ -+ # Expand the default list of modules -+ add_delegated_modules: [ansible] -+ -+ # Expand the default list of functions -+ add_delegated_functions: [file.copy] -+ -+ """ -+ inside_transaction = os.environ.get("TRANSACTIONAL_UPDATE") -+ -+ fun = data["fun"] -+ module, _ = fun.split(".") -+ -+ delegated_modules = set(opts.get("delegated_modules", DEFAULT_DELEGATED_MODULES)) -+ delegated_functions = set( -+ opts.get("delegated_functions", DEFAULT_DELEGATED_FUNCTIONS) -+ ) -+ if "executor_opts" in data: -+ delegated_modules |= set(data["executor_opts"].get("add_delegated_modules", [])) -+ delegated_functions |= set( -+ data["executor_opts"].get("add_delegated_functions", []) -+ ) -+ else: -+ delegated_modules |= set(opts.get("add_delegated_modules", [])) -+ delegated_functions |= set(opts.get("add_delegated_functions", [])) -+ -+ if fun in DELEGATION_MAP and not inside_transaction: -+ result = __executors__["direct_call.execute"]( -+ opts, data, __salt__[DELEGATION_MAP[fun]], args, kwargs -+ ) -+ elif ( -+ module in delegated_modules or fun in delegated_functions -+ ) and not inside_transaction: -+ result = __salt__["transactional_update.call"](fun, *args, **kwargs) -+ else: -+ result = __executors__["direct_call.execute"](opts, data, func, args, kwargs) -+ -+ return result -diff --git a/salt/grains/extra.py b/salt/grains/extra.py -index 0eec27e628..9fd7b2b8b1 100644 ---- a/salt/grains/extra.py -+++ b/salt/grains/extra.py -@@ -3,14 +3,17 @@ - from __future__ import absolute_import, print_function, unicode_literals - - # Import third party libs -+import glob - import logging - - # Import python libs - import os - - # Import salt libs -+import salt.utils - import salt.utils.data - import salt.utils.files -+import salt.utils.path - import salt.utils.platform - import salt.utils.yaml - -@@ -74,3 +77,37 @@ def suse_backported_capabilities(): - '__suse_reserved_pkg_patches_support': True, - '__suse_reserved_saltutil_states_support': True - } -+ -+def __secure_boot(efivars_dir): -+ """Detect if secure-boot is enabled.""" -+ enabled = False -+ sboot = glob.glob(os.path.join(efivars_dir, "SecureBoot-*/data")) -+ if len(sboot) == 1: -+ # The minion is usually running as a privileged user, but is -+ # not the case for the master. Seems that the master can also -+ # pick the grains, and this file can only be readed by "root" -+ try: -+ with salt.utils.files.fopen(sboot[0], "rb") as fd: -+ enabled = fd.read()[-1:] == b"\x01" -+ except PermissionError: -+ pass -+ return enabled -+ -+ -+def uefi(): -+ """Populate UEFI grains.""" -+ efivars_dir = next( -+ filter(os.path.exists, ["/sys/firmware/efi/efivars", "/sys/firmware/efi/vars"]), -+ None, -+ ) -+ grains = { -+ "efi": bool(efivars_dir), -+ "efi-secure-boot": __secure_boot(efivars_dir) if efivars_dir else False, -+ } -+ -+ return grains -+ -+ -+def transactional(): -+ """Determine if the system is transactional.""" -+ return {"transactional": bool(salt.utils.path.which("transactional-update"))} diff --git a/salt/modules/chroot.py b/salt/modules/chroot.py -index 6512a70f88..6e917b5cf8 100644 +index 39dfff6b86..91f139455b 100644 --- a/salt/modules/chroot.py +++ b/salt/modules/chroot.py -@@ -1,12 +1,9 @@ --# -*- coding: utf-8 -*- -- - """ - :maintainer: Alberto Planas - :maturity: new - :depends: None - :platform: Linux - """ --from __future__ import absolute_import, print_function, unicode_literals - - import copy - import logging -@@ -19,8 +16,8 @@ import salt.client.ssh.state - import salt.client.ssh.wrapper.state - import salt.defaults.exitcodes - import salt.exceptions --import salt.ext.six as six - import salt.utils.args -+import salt.utils.files - - __func_alias__ = {"apply_": "apply"} - -@@ -40,6 +37,16 @@ def __virtual__(): - def exist(root): - """ - Return True if the chroot environment is present. -+ -+ root -+ Path to the chroot environment -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt myminion chroot.exist /chroot -+ - """ - dev = os.path.join(root, "dev") - proc = os.path.join(root, "proc") -@@ -79,6 +86,38 @@ def create(root): - return True - - -+def in_chroot(): -+ """ -+ Return True if the process is inside a chroot jail -+ -+ .. versionadded:: 3004 -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt myminion chroot.in_chroot -+ -+ """ -+ result = False -+ -+ try: -+ # We cannot assume that we are "root", so we cannot read -+ # '/proc/1/root', that is required for the usual way of -+ # detecting that we are in a chroot jail. We use the debian -+ # ischroot method. -+ with salt.utils.files.fopen( -+ "/proc/1/mountinfo" -+ ) as root_fd, salt.utils.files.fopen("/proc/self/mountinfo") as self_fd: -+ root_mountinfo = root_fd.read() -+ self_mountinfo = self_fd.read() -+ result = root_mountinfo != self_mountinfo -+ except OSError: -+ pass -+ -+ return result -+ -+ - def call(root, function, *args, **kwargs): - """ - Executes a Salt function inside a chroot environment. -@@ -116,7 +155,7 @@ def call(root, function, *args, **kwargs): - so_mods=__salt__["config.option"]("thin_so_mods", ""), - ) - # Some bug in Salt is preventing us to use `archive.tar` here. A -- # AsyncZeroMQReqChannel is not closed at the end os the salt-call, -+ # AsyncZeroMQReqChannel is not closed at the end of the salt-call, - # and makes the client never exit. - # - # stdout = __salt__['archive.tar']('xzf', thin_path, dest=thin_dest_path) -@@ -158,8 +197,12 @@ def call(root, function, *args, **kwargs): - if isinstance(local, dict) and "retcode" in local: - __context__["retcode"] = local["retcode"] - return local.get("return", data) -- except (KeyError, ValueError): -- return {"result": False, "comment": "Can't parse container command output"} -+ except ValueError: -+ return { -+ "result": False, -+ "retcode": ret["retcode"], -+ "comment": {"stdout": ret["stdout"], "stderr": ret["stderr"]}, -+ } - finally: - __utils__["files.rm_rf"](thin_dest_path) - -@@ -194,19 +237,23 @@ def apply_(root, mods=None, **kwargs): - - def _create_and_execute_salt_state(root, chunks, file_refs, test, hash_type): - """ -- Create the salt_stage tarball, and execute in the chroot -+ Create the salt_state tarball, and execute in the chroot - """ +@@ -242,7 +242,11 @@ def _create_and_execute_salt_state(root, chunks, file_refs, test, hash_type): # Create the tar containing the state pkg and relevant files. salt.client.ssh.wrapper.state._cleanup_slsmod_low_data(chunks) trans_tar = salt.client.ssh.state.prep_trans_tar( @@ -549,15 +102,7 @@ index 6512a70f88..6e917b5cf8 100644 ) trans_tar_sum = salt.utils.hashutils.get_hash(trans_tar, hash_type) - ret = None - - # Create a temporary directory inside the chroot where we can move -- # the salt_stage.tgz -+ # the salt_state.tgz - salt_state_path = tempfile.mkdtemp(dir=root) - salt_state_path = os.path.join(salt_state_path, "salt_state.tgz") - salt_state_path_in_chroot = salt_state_path.replace(root, "", 1) -@@ -260,7 +307,7 @@ def sls(root, mods, saltenv="base", test=None, exclude=None, **kwargs): +@@ -303,7 +307,7 @@ def sls(root, mods, saltenv="base", test=None, exclude=None, **kwargs): """ # Get a copy of the pillar data, to avoid overwriting the current # pillar, instead the one delegated @@ -566,22 +111,7 @@ index 6512a70f88..6e917b5cf8 100644 pillar.update(kwargs.get("pillar", {})) # Clone the options data and apply some default values. May not be -@@ -270,12 +317,12 @@ def sls(root, mods, saltenv="base", test=None, exclude=None, **kwargs): - opts, pillar, __salt__, salt.fileclient.get_file_client(__opts__) - ) - -- if isinstance(mods, six.string_types): -+ if isinstance(mods, str): - mods = mods.split(",") - - high_data, errors = st_.render_highstate({saltenv: mods}) - if exclude: -- if isinstance(exclude, six.string_types): -+ if isinstance(exclude, str): - exclude = exclude.split(",") - if "__exclude__" in high_data: - high_data["__exclude__"].extend(exclude) -@@ -329,7 +376,7 @@ def highstate(root, **kwargs): +@@ -372,7 +376,7 @@ def highstate(root, **kwargs): """ # Get a copy of the pillar data, to avoid overwriting the current # pillar, instead the one delegated @@ -590,3275 +120,99 @@ index 6512a70f88..6e917b5cf8 100644 pillar.update(kwargs.get("pillar", {})) # Clone the options data and apply some default values. May not be -diff --git a/salt/modules/rebootmgr.py b/salt/modules/rebootmgr.py -new file mode 100644 -index 0000000000..4354dd1fce ---- /dev/null -+++ b/salt/modules/rebootmgr.py -@@ -0,0 +1,359 @@ -+""" -+:maintainer: Alberto Planas -+:maturity: new -+:depends: None -+:platform: Linux -+ -+.. versionadded:: 3004 -+""" -+ -+import logging -+import re -+ -+import salt.exceptions -+ -+log = logging.getLogger(__name__) -+ -+ -+def __virtual__(): -+ """rebootmgrctl command is required.""" -+ if __utils__["path.which"]("rebootmgrctl") is not None: -+ return True -+ else: -+ return (False, "Module rebootmgt requires the command rebootmgrctl") -+ -+ -+def _cmd(cmd, retcode=False): -+ """Utility function to run commands.""" -+ result = __salt__["cmd.run_all"](cmd) -+ if retcode: -+ return result["retcode"] -+ -+ if result["retcode"]: -+ raise salt.exceptions.CommandExecutionError(result["stderr"]) -+ -+ return result["stdout"] -+ -+ -+def version(): -+ """Return the version of rebootmgrd -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr version -+ -+ """ -+ cmd = ["rebootmgrctl", "--version"] -+ -+ return _cmd(cmd).split()[-1] -+ -+ -+def is_active(): -+ """Check if the rebootmgrd is running and active or not. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr is_active -+ -+ """ -+ cmd = ["rebootmgrctl", "is_active", "--quiet"] -+ -+ return _cmd(cmd, retcode=True) == 0 -+ -+ -+def reboot(order=None): -+ """Tells rebootmgr to schedule a reboot. -+ -+ With the [now] option, a forced reboot is done, no lock from etcd -+ is requested and a set maintenance window is ignored. With the -+ [fast] option, a lock from etcd is requested if needed, but a -+ defined maintenance window is ignored. -+ -+ order -+ If specified, can be "now" or "fast" -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr reboot -+ salt microos rebootmgt reboot order=now -+ -+ """ -+ if order and order not in ("now", "fast"): -+ raise salt.exceptions.CommandExecutionError( -+ "Order parameter, if specified, must be 'now' or 'fast'" -+ ) -+ -+ cmd = ["rebootmgrctl", "reboot"] -+ if order: -+ cmd.append(order) -+ -+ return _cmd(cmd) -+ -+ -+def cancel(): -+ """Cancels an already running reboot. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr cancel -+ -+ """ -+ cmd = ["rebootmgrctl", "cancel"] -+ -+ return _cmd(cmd) -+ -+ -+def status(): -+ """Returns the current status of rebootmgrd. -+ -+ Valid returned values are: -+ 0 - No reboot requested -+ 1 - Reboot requested -+ 2 - Reboot requested, waiting for maintenance window -+ 3 - Reboot requested, waiting for etcd lock. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr status -+ -+ """ -+ cmd = ["rebootmgrctl", "status", "--quiet"] -+ -+ return _cmd(cmd, retcode=True) -+ -+ -+def set_strategy(strategy=None): -+ """A new strategy to reboot the machine is set and written into -+ /etc/rebootmgr.conf. -+ -+ strategy -+ If specified, must be one of those options: -+ -+ best-effort - This is the default strategy. If etcd is -+ running, etcd-lock is used. If no etcd is running, but a -+ maintenance window is specified, the strategy will be -+ maint-window. If no maintenance window is specified, the -+ machine is immediately rebooted (instantly). -+ -+ etcd-lock - A lock at etcd for the specified lock-group will -+ be acquired before reboot. If a maintenance window is -+ specified, the lock is only acquired during this window. -+ -+ maint-window - Reboot does happen only during a specified -+ maintenance window. If no window is specified, the -+ instantly strategy is followed. -+ -+ instantly - Other services will be informed that a reboot will -+ happen. Reboot will be done without getting any locks or -+ waiting for a maintenance window. -+ -+ off - Reboot requests are temporary -+ ignored. /etc/rebootmgr.conf is not modified. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr set_strategy stragegy=off -+ -+ """ -+ if strategy and strategy not in ( -+ "best-effort", -+ "etcd-lock", -+ "maint-window", -+ "instantly", -+ "off", -+ ): -+ raise salt.exceptions.CommandExecutionError("Strategy parameter not valid") -+ -+ cmd = ["rebootmgrctl", "set-strategy"] -+ if strategy: -+ cmd.append(strategy) -+ -+ return _cmd(cmd) -+ -+ -+def get_strategy(): -+ """The currently used reboot strategy of rebootmgrd will be printed. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr get_strategy -+ -+ """ -+ cmd = ["rebootmgrctl", "get-strategy"] -+ -+ return _cmd(cmd).split(":")[-1].strip() -+ -+ -+def set_window(time, duration): -+ """Set's the maintenance window. -+ -+ time -+ The format of time is the same as described in -+ systemd.time(7). -+ -+ duration -+ The format of duration is "[XXh][YYm]". -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr set_window time="Thu,Fri 2020-*-1,5 11:12:13" duration=1h -+ -+ """ -+ cmd = ["rebootmgrctl", "set-window", time, duration] -+ -+ return _cmd(cmd) -+ -+ -+def get_window(): -+ """The currently set maintenance window will be printed. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr get_window -+ -+ """ -+ cmd = ["rebootmgrctl", "get-window"] -+ window = _cmd(cmd) -+ -+ return dict( -+ zip( -+ ("time", "duration"), -+ re.search( -+ r"Maintenance window is set to (.*), lasting (.*).", window -+ ).groups(), -+ ) -+ ) -+ -+ -+def set_group(group): -+ """Set the group, to which this machine belongs to get a reboot lock -+ from etcd. -+ -+ group -+ Group name -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr set_group group=group_1 -+ -+ """ -+ cmd = ["rebootmgrctl", "set-group", group] -+ -+ return _cmd(cmd) -+ -+ -+def get_group(): -+ """The currently set lock group for etcd. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr get_group -+ -+ """ -+ cmd = ["rebootmgrctl", "get-group"] -+ group = _cmd(cmd) -+ -+ return re.search(r"Etcd lock group is set to (.*)", group).groups()[0] -+ -+ -+def set_max(max_locks, group=None): -+ """Set the maximal number of hosts in a group, which are allowed to -+ reboot at the same time. -+ -+ number -+ Maximal number of hosts in a group -+ -+ group -+ Group name -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr set_max 4 -+ -+ """ -+ cmd = ["rebootmgrctl", "set-max"] -+ if group: -+ cmd.extend(["--group", group]) -+ cmd.append(max_locks) -+ -+ return _cmd(cmd) -+ -+ -+def lock(machine_id=None, group=None): -+ """Lock a machine. If no group is specified, the local default group -+ will be used. If no machine-id is specified, the local machine -+ will be locked. -+ -+ machine_id -+ The machine-id is a network wide, unique ID. Per default the -+ ID from /etc/machine-id is used. -+ -+ group -+ Group name -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr lock group=group1 -+ -+ """ -+ cmd = ["rebootmgrctl", "lock"] -+ if group: -+ cmd.extend(["--group", group]) -+ if machine_id: -+ cmd.append(machine_id) -+ -+ return _cmd(cmd) -+ -+ -+def unlock(machine_id=None, group=None): -+ """Unlock a machine. If no group is specified, the local default group -+ will be used. If no machine-id is specified, the local machine -+ will be locked. -+ -+ machine_id -+ The machine-id is a network wide, unique ID. Per default the -+ ID from /etc/machine-id is used. -+ -+ group -+ Group name -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos rebootmgr unlock group=group1 -+ -+ """ -+ cmd = ["rebootmgrctl", "unlock"] -+ if group: -+ cmd.extend(["--group", group]) -+ if machine_id: -+ cmd.append(machine_id) -+ -+ return _cmd(cmd) -diff --git a/salt/modules/systemd_service.py b/salt/modules/systemd_service.py -index edc810db9b..df20de29a1 100644 ---- a/salt/modules/systemd_service.py -+++ b/salt/modules/systemd_service.py -@@ -63,7 +63,10 @@ def __virtual__(): - """ - Only work on systems that have been booted with systemd - """ -- if __grains__.get("kernel") == "Linux" and salt.utils.systemd.booted(__context__): -+ is_linux = __grains__.get("kernel") == "Linux" -+ is_booted = salt.utils.systemd.booted(__context__) -+ is_offline = salt.utils.systemd.offline(__context__) -+ if is_linux and (is_booted or is_offline): - return __virtualname__ - return ( - False, -@@ -98,6 +101,11 @@ def _check_available(name): - """ - Returns boolean telling whether or not the named service is available - """ -+ if offline(): -+ raise CommandExecutionError( -+ "Cannot run in offline mode. Failed to get information on unit '%s'" % name -+ ) -+ - _status = _systemctl_status(name) - sd_version = salt.utils.systemd.version(__context__) - if sd_version is not None and sd_version >= 231: -@@ -1452,3 +1460,21 @@ def firstboot( - raise CommandExecutionError("systemd-firstboot error: {}".format(out["stderr"])) - - return True -+ -+ -+def offline(): -+ """ -+ .. versionadded:: 3004 -+ -+ Check if systemd is working in offline mode, where is not possible -+ to talk with PID 1. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt '*' service.offline -+ -+ """ -+ -+ return salt.utils.systemd.offline(__context__) diff --git a/salt/modules/transactional_update.py b/salt/modules/transactional_update.py -new file mode 100644 -index 0000000000..799fe08e4d ---- /dev/null +index 6fcad40b35..799fe08e4d 100644 +--- a/salt/modules/transactional_update.py +++ b/salt/modules/transactional_update.py -@@ -0,0 +1,1325 @@ -+"""Transactional update -+==================== -+ -+.. versionadded:: 3004 -+ -+A transactional system, like `MicroOS`_, can present some challenges -+when the user decided to manage it via Salt. -+ -+MicroOS provide a read-only rootfs and a tool, -+``transactional-update``, that takes care of the management of the -+system (updating, upgrading, installation or reboot, among others) in -+an atomic way. -+ -+Atomicity is the main feature of MicroOS, and to guarantee this -+property, this model leverages ``snapper``, ``zypper``, ``btrfs`` and -+``overlayfs`` to create snapshots that will be updated independently -+of the currently running system, and that are activated after the -+reboot. This implies, for example, that some changes made on the -+system are not visible until the next reboot, as those changes are -+living in a different snapshot of the file system. -+ -+This model presents a lot of problems with the traditional Salt model, -+where the inspections (like 'is this package installed?') are executed -+in order to determine if a subsequent action is required (like -+'install this package'). -+ -+Lets consider this use case, to see how it works on a traditional -+system, and in a transactional system: -+ -+1) Check if ``apache`` is installed -+ -+2) If it is not installed, install it -+ -+3) Check that a ``vhost`` is configured for ``apache`` -+ -+4) Make sure that ``apache2.service`` is enabled -+ -+5) If the configuration changes, restart ``apache2.service`` -+ -+In the traditional system everything will work as expected. The -+system can see if the package is present or not, install it if it -+isn't, and a re-check will shows that is already present. The same -+will happen to the configuration file in ``/etc/apache2``, that will -+be available as soon the package gets installed. Salt can inspect the -+current form of this file, and add the missing bits if required. Salt -+can annotate that a change is present, and restart the service. -+ -+In a transactional system we will have multiple issues. The first one -+is that Salt can only see the content of the snapshot where the system -+booted from. Later snapshots may contain different content, including -+the presence of ``apache``. If Salt decides to install ``apache`` -+calling ``zypper``, it will fail, as this will try to write into the -+read-only rootfs. Even if Salt would call ``transactional-update pkg -+install``, the package would only be present in the new transaction -+(snapshot), and will not be found in the currently running system when -+later Salt tries to validate the presence of the package in the -+current one. -+ -+Any change in ``/etc`` alone will have also problems, as the changes -+will be alive in a different overlay, only visible after the reboot. -+And, finally, the service can only be enabled and restarted if the -+service file is already present in the current ``/etc``. -+ -+ -+General strategy -+---------------- -+ -+``transactional-update`` is the reference tool used for the -+administration of transactional systems. Newer versions of this tool -+support the execution of random commands in the new transaction, the -+continuation of a transaction, the automatic detection of changes in -+new transactions and the merge of ``/etc`` overlays. -+ -+Continue a transaction -+...................... -+ -+One prerequisite already present is the support for branching from a -+different snapshot than the current one in snapper. -+ -+With this feature we can represent in ``transactional-update`` the -+action of creating a transaction snapshot based on one that is planned -+to be the active one after the reboot. This feature removes a lot of -+user complains (like, for example, losing changes that are stored in a -+transaction not yet activated), but also provide a more simple model -+to work with. -+ -+So, for example, if the user have this scenario:: -+ -+ +-----+ *=====* +--V--+ -+ --| T.1 |--| T.2 |--| T.3 | -+ +-----+ *=====* +--A--+ -+ -+where T.2 is the current active one, and T.3 is an snapshot generated -+from T.2 with a new package (``apache2``), and is marked to be the -+active after the reboot. -+ -+Previously, if the user (that is still on T.2) created a new -+transaction, maybe for adding a new package (``tomcat``, for example), -+the new T.4 will be based on the content of T.2 again, and not T.3, so -+the new T.4 will have lost the changes of T.3 (i.e. `apache2` will not -+be present in T.4). -+ -+With the ``--continue`` parameter, ``transactional-update`` will -+create T.4 based on T.3, and nothing will be lost. -+ -+Command execution inside a new transaction -+.......................................... -+ -+With ``transactional-update run`` we will create a new transaction -+based on the current one (T.2), where we can send interactive commands -+that can modify the new transaction, and as commented, with -+``transactional-update --continue run``, we will create a new -+transaction based on the last created (T.3) -+ -+The ``run`` command can execute any application inside the new -+transaction namespace. This module uses this feature to execute the -+different Salt execution modules, via ``call()``. Or even the full -+``salt-thin`` or ``salt-call`` via ``sls()``, ``apply()``, -+``single()`` or ``highstate``. -+ -+``transactional-update`` will drop empty snapshots -+.................................................. -+ -+The option ``--drop-if-no-change`` is used to detect whether there is -+any change in the file system on the read-only subvolume of the new -+transaction will be added. If a change is present, the new -+transaction will remain, if not it will be discarded. -+ -+For example:: -+ -+ transactional-update --continue --drop-if-no-change run zypper in apache2" -+ -+If we are in the scenario described before, ``apache2`` is already -+present in T.3. In this case a new transaction, T.4, will be created -+based on T.3, ``zypper`` will detect that the package is already -+present and no change will be produced on T.4. At the end of the -+execution, ``transactional-update`` will validate that T.3 and T.4 are -+equivalent and T.4 will be discarded. -+ -+If the command is:: -+ -+ transactional-update --continue --drop-if-no-change run zypper in tomcat -+ -+the new T.4 will be indeed different from T.3, and will remain after -+the transaction is closed. -+ -+With this feature, every time that we call any function of this -+execution module, we will minimize the amount of transaction, while -+maintaining the idempotence so some operations. -+ -+Report for pending transaction -+.............................. -+ -+A change in the system will create a new transaction, that needs to be -+activated via a reboot. With ``pending_transaction()`` we can check -+if a reboot is needed. We can execute the reboot using the -+``reboot()`` function, that will follow the plan established by the -+functions of the ``rebootmgr`` execution module. -+ -+``/etc`` overlay merge when no new transaction is created -+......................................................... -+ -+In a transactional model, ``/etc`` is an overlay file system. Changes -+done during the update are only present in the new transaction, and so -+will only be available after the reboot. Or worse, if the transaction -+gets dropped, because there is no change in the ``rootfs``, the -+changes in ``/etc`` will be dropped too!. This is designed like that -+in order to make the configuration files for the new package available -+only when new package is also available to the user. So, after the -+reboot. -+ -+This makes sense for the case when, for example, ``apache2`` is not -+present in the current transaction, but we installed it. The new -+snapshot contains the ``apache2`` service, and the configuration files -+in ``/etc`` will be accessible only after the reboot. -+ -+But this model presents an issue. If we use ``transactional-update -+--continue --drop-if-no-change run ``, where ```` -+does not make any change in the read-only subvolume, but only in -+``/etc`` (which is also read-write in the running system), the new -+overlay with the changes in ``/etc`` will be dropped together with the -+transaction. -+ -+To fix this, ``transactional-update`` will detect that when no change -+has been made on the read-only subvolume, but done in the overlay, the -+transaction will be dropped and the changes in the overlay will be -+merged back into ``/etc`` overlay of the current transaction. -+ -+ -+Using the execution module -+-------------------------- -+ -+With this module we can create states that leverage Salt into this -+kind of systems:: -+ -+ # Install apache (low-level API) -+ salt-call transactional_update.pkg_install apache2 -+ -+ # We can call any execution module -+ salt-call transactional_update.call pkg.install apache2 -+ -+ # Or via a state -+ salt-call transactional_update.single pkg.installed name=apache2 -+ -+ # We can also execute a zypper directly -+ salt-call transactional_update run "zypper in apache2" snapshot="continue" -+ -+ # We can reuse SLS states -+ salt-call transactional_update.apply install_and_configure_apache -+ -+ # Or apply the full highstate -+ salt-call transactional_update.highstate -+ -+ # Is there any change done in the system? -+ salt-call transactional_update pending_transaction -+ -+ # If so, reboot via rebootmgr -+ salt-call transactional_update reboot -+ -+ # We can enable the service -+ salt-call service.enable apache2 -+ -+ # If apache2 is available, this will work too -+ salt-call service.restart apache2 -+ -+ -+Fixing some expectations -+------------------------ -+ -+This module alone is an improvement over the current state, but is -+easy to see some limitations and problems: -+ -+Is not a fully transparent approach -+................................... -+ -+The user needs to know if the system is transactional or not, as not -+everything can be expressed inside a transaction (for example, -+restarting a service inside transaction is not allowed). -+ -+Two step for service restart -+............................ -+ -+In the ``apache2` example from the beginning we can observe the -+biggest drawback. If the package ``apache2`` is missing, the new -+module will create a new transaction, will execute ``pkg.install`` -+inside the transaction (creating the salt-thin, moving it inside and -+delegating the execution to `transactional-update` CLI as part of the -+full state). Inside the transaction we can do too the required -+changes in ``/etc`` for adding the new ``vhost``, and we can enable the -+service via systemctl inside the same transaction. -+ -+At this point we will not merge the ``/etc`` overlay into the current -+one, and we expect from the user call the ``reboot`` function inside -+this module, in order to activate the new transaction and start the -+``apache2`` service. -+ -+In the case that the package is already there, but the configuration -+for the ``vhost`` is required, the new transaction will be dropped and -+the ``/etc`` overlay will be visible in the live system. Then from -+outside the transaction, via a different call to Salt, we can command -+a restart of the ``apache2`` service. -+ -+We can see that in both cases we break the user expectation, where a -+change on the configuration will trigger automatically the restart of -+the associated service. In a transactional scenario we need two -+different steps: or a reboot, or a restart from outside of the -+transaction. -+ -+.. _MicroOS: https://microos.opensuse.org/ -+ -+:maintainer: Alberto Planas -+:maturity: new -+:depends: None -+:platform: Linux -+ -+""" -+ -+import copy -+import logging -+import os -+import sys -+import tempfile -+ -+# required by _check_queue invocation later -+import time # pylint: disable=unused-import -+ -+import salt.client.ssh.state -+import salt.client.ssh.wrapper.state -+import salt.exceptions -+import salt.utils.args -+from salt.modules.state import _check_queue, _prior_running_states, _wait, running -+ -+__func_alias__ = {"apply_": "apply"} -+ -+log = logging.getLogger(__name__) -+ -+ -+def __virtual__(): -+ """ -+ transactional-update command is required. -+ """ -+ global _check_queue, _wait, _prior_running_states, running -+ if __utils__["path.which"]("transactional-update"): -+ _check_queue = salt.utils.functools.namespaced_function(_check_queue, globals()) -+ _wait = salt.utils.functools.namespaced_function(_wait, globals()) -+ _prior_running_states = salt.utils.functools.namespaced_function( -+ _prior_running_states, globals() -+ ) -+ running = salt.utils.functools.namespaced_function(running, globals()) -+ return True -+ else: -+ return (False, "Module transactional_update requires a transactional system") -+ -+ -+class TransactionalUpdateHighstate(salt.client.ssh.state.SSHHighState): -+ def _master_tops(self): -+ return self.client.master_tops() -+ -+ -+def _global_params(self_update, snapshot=None, quiet=False): -+ """Utility function to prepare common global parameters.""" -+ params = ["--non-interactive", "--drop-if-no-change"] -+ if self_update is False: -+ params.append("--no-selfupdate") -+ if snapshot and snapshot != "continue": -+ params.extend(["--continue", snapshot]) -+ elif snapshot: -+ params.append("--continue") -+ if quiet: -+ params.append("--quiet") -+ return params -+ -+ -+def _pkg_params(pkg, pkgs, args): -+ """Utility function to prepare common package parameters.""" -+ params = [] -+ -+ if not pkg and not pkgs: -+ raise salt.exceptions.CommandExecutionError("Provide pkg or pkgs parameters") -+ -+ if args and isinstance(args, str): -+ params.extend(args.split()) -+ elif args and isinstance(args, list): -+ params.extend(args) -+ -+ if pkg: -+ params.append(pkg) -+ -+ if pkgs and isinstance(pkgs, str): -+ params.extend(pkgs.split()) -+ elif pkgs and isinstance(pkgs, list): -+ params.extend(pkgs) -+ -+ return params -+ -+ -+def _cmd(cmd, retcode=False): -+ """Utility function to run commands.""" -+ result = __salt__["cmd.run_all"](cmd) -+ if retcode: -+ return result["retcode"] -+ -+ if result["retcode"]: -+ raise salt.exceptions.CommandExecutionError(result["stderr"]) -+ -+ return result["stdout"] -+ -+ -+def transactional(): -+ """Check if the system is a transactional system -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update transactional -+ -+ """ -+ return bool(__utils__["path.which"]("transactional-update")) -+ -+ -+def in_transaction(): -+ """Check if Salt is executing while in a transaction -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update in_transaction -+ -+ """ -+ return transactional() and __salt__["chroot.in_chroot"]() -+ -+ -+def cleanup(self_update=False): -+ """Run both cleanup-snapshots and cleanup-overlays. -+ -+ Identical to calling both cleanup-snapshots and cleanup-overlays. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update cleanup -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update)) -+ cmd.append("cleanup") -+ return _cmd(cmd) -+ -+ -+def cleanup_snapshots(self_update=False): -+ """Mark unused snapshots for snapper removal. -+ -+ If the current root filesystem is identical to the active root -+ filesystem (means after a reboot, before transactional-update -+ creates a new snapshot with updates), all old snapshots without a -+ cleanup algorithm get a cleanup algorithm set. This is to make -+ sure, that old snapshots will be deleted by snapper. See the -+ section about cleanup algorithms in snapper(8). -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update cleanup_snapshots -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update)) -+ cmd.append("cleanup-snapshots") -+ return _cmd(cmd) -+ -+ -+def cleanup_overlays(self_update=False): -+ """Remove unused overlay layers. -+ -+ Removes all unreferenced (and thus unused) /etc overlay -+ directories in /var/lib/overlay. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update cleanup_overlays -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update)) -+ cmd.append("cleanup-overlays") -+ return _cmd(cmd) -+ -+ -+def grub_cfg(self_update=False, snapshot=None): -+ """Regenerate grub.cfg -+ -+ grub2-mkconfig(8) is called to create a new /boot/grub2/grub.cfg -+ configuration file for the bootloader. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update grub_cfg snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("grub.cfg") -+ return _cmd(cmd) -+ -+ -+def bootloader(self_update=False, snapshot=None): -+ """Reinstall the bootloader -+ -+ Same as grub.cfg, but will also rewrite the bootloader itself. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update bootloader snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("bootloader") -+ return _cmd(cmd) -+ -+ -+def initrd(self_update=False, snapshot=None): -+ """Regenerate initrd -+ -+ A new initrd is created in a snapshot. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update initrd snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("initrd") -+ return _cmd(cmd) -+ -+ -+def kdump(self_update=False, snapshot=None): -+ """Regenerate kdump initrd -+ -+ A new initrd for kdump is created in a snapshot. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update kdump snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("kdump") -+ return _cmd(cmd) -+ -+ -+def run(command, self_update=False, snapshot=None): -+ """Run a command in a new snapshot -+ -+ Execute the command inside a new snapshot. By default this snaphot -+ will remain, but if --drop-if-no-chage is set, the new snapshot -+ will be dropped if there is no change in the file system. -+ -+ command -+ Command with parameters that will be executed (as string or -+ array) -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update run "mkdir /tmp/dir" snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot, quiet=True)) -+ cmd.append("run") -+ if isinstance(command, str): -+ cmd.extend(command.split()) -+ elif isinstance(command, list): -+ cmd.extend(command) -+ else: -+ raise salt.exceptions.CommandExecutionError("Command parameter not recognized") -+ return _cmd(cmd) -+ -+ -+def reboot(self_update=False): -+ """Reboot after update -+ -+ Trigger a reboot after updating the system. -+ -+ Several different reboot methods are supported, configurable via -+ the REBOOT_METHOD configuration option in -+ transactional-update.conf(5). By default rebootmgrd(8) will be -+ used to reboot the system according to the configured policies if -+ the service is running, otherwise systemctl reboot will be called. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update reboot -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update)) -+ cmd.append("reboot") -+ return _cmd(cmd) -+ -+ -+def dup(self_update=False, snapshot=None): -+ """Call 'zypper dup' -+ -+ If new updates are available, a new snapshot is created and zypper -+ dup --no-allow-vendor-change is used to update the -+ snapshot. Afterwards, the snapshot is activated and will be used -+ as the new root filesystem during next boot. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update dup snapshot="continue" -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("dup") -+ return _cmd(cmd) -+ -+ -+def up(self_update=False, snapshot=None): -+ """Call 'zypper up' -+ -+ If new updates are available, a new snapshot is created and zypper -+ up is used to update the snapshot. Afterwards, the snapshot is -+ activated and will be used as the new root filesystem during next -+ boot. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update up snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("up") -+ return _cmd(cmd) -+ -+ -+def patch(self_update=False, snapshot=None): -+ """Call 'zypper patch' -+ -+ If new updates are available, a new snapshot is created and zypper -+ patch is used to update the snapshot. Afterwards, the snapshot is -+ activated and will be used as the new root filesystem during next -+ boot. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update patch snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("patch") -+ return _cmd(cmd) -+ -+ -+def migration(self_update=False, snapshot=None): -+ """Updates systems registered via SCC / SMT -+ -+ On systems which are registered against the SUSE Customer Center -+ (SCC) or SMT, a migration to a new version of the installed -+ products can be made with this option. -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update migration snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.append("migration") -+ return _cmd(cmd) -+ -+ -+def pkg_install(pkg=None, pkgs=None, args=None, self_update=False, snapshot=None): -+ """Install individual packages -+ -+ Installs additional software. See the install description in the -+ "Package Management Commands" section of zypper's man page for all -+ available arguments. -+ -+ pkg -+ Package name to install -+ -+ pkgs -+ List of packages names to install -+ -+ args -+ String or list of extra parameters for zypper -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update pkg_install pkg=emacs snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.extend(["pkg", "install"]) -+ cmd.extend(_pkg_params(pkg, pkgs, args)) -+ return _cmd(cmd) -+ -+ -+def pkg_remove(pkg=None, pkgs=None, args=None, self_update=False, snapshot=None): -+ """Remove individual packages -+ -+ Removes installed software. See the remove description in the -+ "Package Management Commands" section of zypper's man page for all -+ available arguments. -+ -+ pkg -+ Package name to install -+ -+ pkgs -+ List of packages names to install -+ -+ args -+ String or list of extra parameters for zypper -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update pkg_remove pkg=vim snapshot="continue" -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.extend(["pkg", "remove"]) -+ cmd.extend(_pkg_params(pkg, pkgs, args)) -+ return _cmd(cmd) -+ -+ -+def pkg_update(pkg=None, pkgs=None, args=None, self_update=False, snapshot=None): -+ """Updates individual packages -+ -+ Update selected software. See the update description in the -+ "Update Management Commands" section of zypper's man page for all -+ available arguments. -+ -+ pkg -+ Package name to install -+ -+ pkgs -+ List of packages names to install -+ -+ args -+ String or list of extra parameters for zypper -+ -+ self_update -+ Check for newer transactional-update versions. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "continue" to indicate the last snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update pkg_update pkg=emacs snapshot="continue" -+ -+ """ -+ cmd = ["transactional-update"] -+ cmd.extend(_global_params(self_update=self_update, snapshot=snapshot)) -+ cmd.extend(["pkg", "update"]) -+ cmd.extend(_pkg_params(pkg, pkgs, args)) -+ return _cmd(cmd) -+ -+ -+def rollback(snapshot=None): -+ """Set the current, given or last working snapshot as default snapshot -+ -+ Sets the default root file system. On a read-only system the root -+ file system is set directly using btrfs. On read-write systems -+ snapper(8) rollback is called. -+ -+ If no snapshot number is given, the current root file system is -+ set as the new default root file system. Otherwise number can -+ either be a snapshot number (as displayed by snapper list) or the -+ word last. last will try to reset to the latest working snapshot. -+ -+ snapshot -+ Use the given snapshot or, if no number is given, the current -+ default snapshot as a base for the next snapshot. Use -+ "last" to indicate the last working snapshot done. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update rollback -+ -+ """ -+ if ( -+ snapshot -+ and isinstance(snapshot, str) -+ and snapshot != "last" -+ and not snapshot.isnumeric() -+ ): -+ raise salt.exceptions.CommandExecutionError( -+ "snapshot should be a number or 'last'" -+ ) -+ cmd = ["transactional-update"] -+ cmd.append("rollback") -+ if snapshot: -+ cmd.append(snapshot) -+ return _cmd(cmd) -+ -+ -+def pending_transaction(): -+ """Check if there is a pending transaction -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update pending_transaction -+ -+ """ -+ # If we are running inside a transaction, we do not have a good -+ # way yet to detect a pending transaction -+ if in_transaction(): -+ raise salt.exceptions.CommandExecutionError( -+ "pending_transaction cannot be executed inside a transaction" -+ ) -+ -+ cmd = ["snapper", "--no-dbus", "list", "--columns", "number"] -+ snapshots = _cmd(cmd) -+ -+ return any(snapshot.endswith("+") for snapshot in snapshots) -+ -+ -+def call(function, *args, **kwargs): -+ """Executes a Salt function inside a transaction. -+ -+ The chroot does not need to have Salt installed, but Python is -+ required. -+ -+ function -+ Salt execution module function -+ -+ activate_transaction -+ If at the end of the transaction there is a pending activation -+ (i.e there is a new snaphot in the system), a new reboot will -+ be scheduled (default False) -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update.call test.ping -+ salt microos transactional_update.call ssh.set_auth_key user key=mykey -+ salt microos transactional_update.call pkg.install emacs activate_transaction=True -+ -+ """ -+ -+ if not function: -+ raise salt.exceptions.CommandExecutionError("Missing function parameter") -+ -+ activate_transaction = kwargs.pop("activate_transaction", False) -+ -+ # Generate the salt-thin and create a temporary directory in a -+ # place that the new transaction will have access to, and where we -+ # can untar salt-thin -+ thin_path = __utils__["thin.gen_thin"]( -+ __opts__["cachedir"], -+ extra_mods=__salt__["config.option"]("thin_extra_mods", ""), -+ so_mods=__salt__["config.option"]("thin_so_mods", ""), -+ ) -+ thin_dest_path = tempfile.mkdtemp(dir=__opts__["cachedir"]) -+ # Some bug in Salt is preventing us to use `archive.tar` here. A -+ # AsyncZeroMQReqChannel is not closed at the end of the salt-call, -+ # and makes the client never exit. -+ # -+ # stdout = __salt__['archive.tar']('xzf', thin_path, dest=thin_dest_path) -+ # -+ stdout = __salt__["cmd.run"](["tar", "xzf", thin_path, "-C", thin_dest_path]) -+ if stdout: -+ __utils__["files.rm_rf"](thin_dest_path) -+ return {"result": False, "comment": stdout} -+ -+ try: -+ safe_kwargs = salt.utils.args.clean_kwargs(**kwargs) -+ salt_argv = ( -+ [ -+ "python{}".format(sys.version_info[0]), -+ os.path.join(thin_dest_path, "salt-call"), -+ "--metadata", -+ "--local", -+ "--log-file", -+ os.path.join(thin_dest_path, "log"), -+ "--cachedir", -+ os.path.join(thin_dest_path, "cache"), -+ "--out", -+ "json", -+ "-l", -+ "quiet", -+ "--", -+ function, -+ ] -+ + list(args) -+ + ["{}={}".format(k, v) for (k, v) in safe_kwargs.items()] -+ ) -+ try: -+ ret_stdout = run([str(x) for x in salt_argv], snapshot="continue") -+ except salt.exceptions.CommandExecutionError as e: -+ ret_stdout = e.message -+ -+ # Process "real" result in stdout -+ try: -+ data = __utils__["json.find_json"](ret_stdout) -+ local = data.get("local", data) -+ if isinstance(local, dict) and "retcode" in local: -+ __context__["retcode"] = local["retcode"] -+ return local.get("return", data) -+ except ValueError: -+ return {"result": False, "retcode": 1, "comment": ret_stdout} -+ finally: -+ __utils__["files.rm_rf"](thin_dest_path) -+ -+ # Check if reboot is needed -+ if activate_transaction and pending_transaction(): -+ reboot() -+ -+ -+def apply_(mods=None, **kwargs): -+ """Apply an state inside a transaction. -+ -+ This function will call `transactional_update.highstate` or -+ `transactional_update.sls` based on the arguments passed to this -+ function. It exists as a more intuitive way of applying states. -+ -+ For a formal description of the possible parameters accepted in -+ this function, check `state.apply_` documentation. -+ -+ activate_transaction -+ If at the end of the transaction there is a pending activation -+ (i.e there is a new snaphot in the system), a new reboot will -+ be scheduled (default False) -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update.apply -+ salt microos transactional_update.apply stuff -+ salt microos transactional_update.apply stuff pillar='{"foo": "bar"}' -+ salt microos transactional_update.apply stuff activate_transaction=True -+ -+ """ -+ if mods: -+ return sls(mods, **kwargs) -+ return highstate(**kwargs) -+ -+ -+def _create_and_execute_salt_state( -+ chunks, file_refs, test, hash_type, activate_transaction -+): -+ """Create the salt_state tarball, and execute it in a transaction""" -+ -+ # Create the tar containing the state pkg and relevant files. -+ salt.client.ssh.wrapper.state._cleanup_slsmod_low_data(chunks) -+ trans_tar = salt.client.ssh.state.prep_trans_tar( +@@ -1052,7 +1052,7 @@ def _create_and_execute_salt_state( + # Create the tar containing the state pkg and relevant files. + salt.client.ssh.wrapper.state._cleanup_slsmod_low_data(chunks) + trans_tar = salt.client.ssh.state.prep_trans_tar( +- salt.fileclient.get_file_client(__opts__), chunks, file_refs, __pillar__ + salt.fileclient.get_file_client(__opts__), chunks, file_refs, __pillar__.value() -+ ) -+ trans_tar_sum = salt.utils.hashutils.get_hash(trans_tar, hash_type) -+ -+ ret = None -+ -+ # Create a temporary directory accesible later by the transaction -+ # where we can move the salt_state.tgz -+ salt_state_path = tempfile.mkdtemp(dir=__opts__["cachedir"]) -+ salt_state_path = os.path.join(salt_state_path, "salt_state.tgz") -+ try: -+ salt.utils.files.copyfile(trans_tar, salt_state_path) -+ ret = call( -+ "state.pkg", -+ salt_state_path, -+ test=test, -+ pkg_sum=trans_tar_sum, -+ hash_type=hash_type, -+ activate_transaction=activate_transaction, -+ ) -+ finally: -+ __utils__["files.rm_rf"](salt_state_path) -+ -+ return ret -+ -+ -+def sls( -+ mods, -+ saltenv="base", -+ test=None, -+ exclude=None, -+ activate_transaction=False, -+ queue=False, -+ **kwargs -+): -+ """Execute the states in one or more SLS files inside a transaction. -+ -+ saltenv -+ Specify a salt fileserver environment to be used when applying -+ states -+ -+ mods -+ List of states to execute -+ -+ test -+ Run states in test-only (dry-run) mode -+ -+ exclude -+ Exclude specific states from execution. Accepts a list of sls -+ names, a comma-separated string of sls names, or a list of -+ dictionaries containing ``sls`` or ``id`` keys. Glob-patterns -+ may be used to match multiple states. -+ -+ activate_transaction -+ If at the end of the transaction there is a pending activation -+ (i.e there is a new snaphot in the system), a new reboot will -+ be scheduled (default False) -+ -+ queue -+ Instead of failing immediately when another state run is in progress, -+ queue the new state run to begin running once the other has finished. -+ -+ This option starts a new thread for each queued state run, so use this -+ option sparingly. (Default: False) -+ -+ For a formal description of the possible parameters accepted in -+ this function, check `state.sls` documentation. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update.sls stuff pillar='{"foo": "bar"}' -+ salt microos transactional_update.sls stuff activate_transaction=True -+ -+ """ -+ conflict = _check_queue(queue, kwargs) -+ if conflict is not None: -+ return conflict -+ -+ # Get a copy of the pillar data, to avoid overwriting the current -+ # pillar, instead the one delegated + ) + trans_tar_sum = salt.utils.hashutils.get_hash(trans_tar, hash_type) + +@@ -1134,7 +1134,7 @@ def sls( + + # Get a copy of the pillar data, to avoid overwriting the current + # pillar, instead the one delegated +- pillar = copy.deepcopy(__pillar__) + pillar = copy.deepcopy(__pillar__.value()) -+ pillar.update(kwargs.get("pillar", {})) -+ -+ # Clone the options data and apply some default values. May not be -+ # needed, as this module just delegate -+ opts = salt.utils.state.get_sls_opts(__opts__, **kwargs) -+ st_ = TransactionalUpdateHighstate( -+ opts, pillar, __salt__, salt.fileclient.get_file_client(__opts__) -+ ) -+ -+ if isinstance(mods, str): -+ mods = mods.split(",") -+ -+ high_data, errors = st_.render_highstate({saltenv: mods}) -+ if exclude: -+ if isinstance(exclude, str): -+ exclude = exclude.split(",") -+ if "__exclude__" in high_data: -+ high_data["__exclude__"].extend(exclude) -+ else: -+ high_data["__exclude__"] = exclude -+ -+ high_data, ext_errors = st_.state.reconcile_extend(high_data) -+ errors += ext_errors -+ errors += st_.state.verify_high(high_data) -+ if errors: -+ return errors -+ -+ high_data, req_in_errors = st_.state.requisite_in(high_data) -+ errors += req_in_errors -+ if errors: -+ return errors -+ -+ high_data = st_.state.apply_exclude(high_data) -+ -+ # Compile and verify the raw chunks -+ chunks = st_.state.compile_high_data(high_data) -+ file_refs = salt.client.ssh.state.lowstate_file_refs( -+ chunks, -+ salt.client.ssh.wrapper.state._merge_extra_filerefs( -+ kwargs.get("extra_filerefs", ""), opts.get("extra_filerefs", "") -+ ), -+ ) -+ -+ hash_type = opts["hash_type"] -+ return _create_and_execute_salt_state( -+ chunks, file_refs, test, hash_type, activate_transaction -+ ) -+ -+ -+def highstate(activate_transaction=False, queue=False, **kwargs): -+ """Retrieve the state data from the salt master for this minion and -+ execute it inside a transaction. -+ -+ For a formal description of the possible parameters accepted in -+ this function, check `state.highstate` documentation. -+ -+ activate_transaction -+ If at the end of the transaction there is a pending activation -+ (i.e there is a new snaphot in the system), a new reboot will -+ be scheduled (default False) -+ -+ queue -+ Instead of failing immediately when another state run is in progress, -+ queue the new state run to begin running once the other has finished. -+ -+ This option starts a new thread for each queued state run, so use this -+ option sparingly. (Default: False) -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update.highstate -+ salt microos transactional_update.highstate pillar='{"foo": "bar"}' -+ salt microos transactional_update.highstate activate_transaction=True -+ -+ """ -+ conflict = _check_queue(queue, kwargs) -+ if conflict is not None: -+ return conflict -+ -+ # Get a copy of the pillar data, to avoid overwriting the current -+ # pillar, instead the one delegated + pillar.update(kwargs.get("pillar", {})) + + # Clone the options data and apply some default values. May not be +@@ -1218,7 +1218,7 @@ def highstate(activate_transaction=False, queue=False, **kwargs): + + # Get a copy of the pillar data, to avoid overwriting the current + # pillar, instead the one delegated +- pillar = copy.deepcopy(__pillar__) + pillar = copy.deepcopy(__pillar__.value()) -+ pillar.update(kwargs.get("pillar", {})) -+ -+ # Clone the options data and apply some default values. May not be -+ # needed, as this module just delegate -+ opts = salt.utils.state.get_sls_opts(__opts__, **kwargs) -+ st_ = TransactionalUpdateHighstate( -+ opts, pillar, __salt__, salt.fileclient.get_file_client(__opts__) -+ ) -+ -+ # Compile and verify the raw chunks -+ chunks = st_.compile_low_chunks() -+ file_refs = salt.client.ssh.state.lowstate_file_refs( -+ chunks, -+ salt.client.ssh.wrapper.state._merge_extra_filerefs( -+ kwargs.get("extra_filerefs", ""), opts.get("extra_filerefs", "") -+ ), -+ ) -+ # Check for errors -+ for chunk in chunks: -+ if not isinstance(chunk, dict): -+ __context__["retcode"] = 1 -+ return chunks -+ -+ test = kwargs.pop("test", False) -+ hash_type = opts["hash_type"] -+ return _create_and_execute_salt_state( -+ chunks, file_refs, test, hash_type, activate_transaction -+ ) -+ -+ -+def single(fun, name, test=None, activate_transaction=False, queue=False, **kwargs): -+ """Execute a single state function with the named kwargs, returns -+ False if insufficient data is sent to the command -+ -+ By default, the values of the kwargs will be parsed as YAML. So, -+ you can specify lists values, or lists of single entry key-value -+ maps, as you would in a YAML salt file. Alternatively, JSON format -+ of keyword values is also supported. -+ -+ activate_transaction -+ If at the end of the transaction there is a pending activation -+ (i.e there is a new snaphot in the system), a new reboot will -+ be scheduled (default False) -+ -+ queue -+ Instead of failing immediately when another state run is in progress, -+ queue the new state run to begin running once the other has finished. -+ -+ This option starts a new thread for each queued state run, so use this -+ option sparingly. (Default: False) -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt microos transactional_update.single pkg.installed name=emacs -+ salt microos transactional_update.single pkg.installed name=emacs activate_transaction=True -+ -+ """ -+ conflict = _check_queue(queue, kwargs) -+ if conflict is not None: -+ return conflict -+ -+ # Get a copy of the pillar data, to avoid overwriting the current -+ # pillar, instead the one delegated + pillar.update(kwargs.get("pillar", {})) + + # Clone the options data and apply some default values. May not be +@@ -1284,7 +1284,7 @@ def single(fun, name, test=None, activate_transaction=False, queue=False, **kwar + + # Get a copy of the pillar data, to avoid overwriting the current + # pillar, instead the one delegated +- pillar = copy.deepcopy(__pillar__) + pillar = copy.deepcopy(__pillar__.value()) -+ pillar.update(kwargs.get("pillar", {})) -+ -+ # Clone the options data and apply some default values. May not be -+ # needed, as this module just delegate -+ opts = salt.utils.state.get_sls_opts(__opts__, **kwargs) -+ st_ = salt.client.ssh.state.SSHState(opts, pillar) -+ -+ # state.fun -> [state, fun] -+ comps = fun.split(".") -+ if len(comps) < 2: -+ __context__["retcode"] = 1 -+ return "Invalid function passed" -+ -+ # Create the low chunk, using kwargs as a base -+ kwargs.update({"state": comps[0], "fun": comps[1], "__id__": name, "name": name}) -+ -+ # Verify the low chunk -+ err = st_.verify_data(kwargs) -+ if err: -+ __context__["retcode"] = 1 -+ return err -+ -+ # Must be a list of low-chunks -+ chunks = [kwargs] -+ -+ # Retrieve file refs for the state run, so we can copy relevant -+ # files down to the minion before executing the state -+ file_refs = salt.client.ssh.state.lowstate_file_refs( -+ chunks, -+ salt.client.ssh.wrapper.state._merge_extra_filerefs( -+ kwargs.get("extra_filerefs", ""), opts.get("extra_filerefs", "") -+ ), -+ ) -+ -+ hash_type = opts["hash_type"] -+ return _create_and_execute_salt_state( -+ chunks, file_refs, test, hash_type, activate_transaction -+ ) -diff --git a/salt/states/service.py b/salt/states/service.py -index 27595f7703..e11d773965 100644 ---- a/salt/states/service.py -+++ b/salt/states/service.py -@@ -345,6 +345,10 @@ def _disable(name, started, result=True, **kwargs): - return ret + pillar.update(kwargs.get("pillar", {})) - -+def _offline(): -+ return "service.offline" in __salt__ and __salt__["service.offline"]() -+ -+ - def _available(name, ret): - """ - Check if the service is available -@@ -439,6 +443,11 @@ def running(name, enable=None, sig=None, init_delay=None, **kwargs): - if isinstance(enable, str): - enable = salt.utils.data.is_true(enable) - -+ if _offline(): -+ ret["result"] = True -+ ret["comment"] = "Running in OFFLINE mode. Nothing to do" -+ return ret -+ - # Check if the service is available - try: - if not _available(name, ret): -@@ -634,6 +643,11 @@ def dead(name, enable=None, sig=None, init_delay=None, **kwargs): - if isinstance(enable, str): - enable = salt.utils.data.is_true(enable) - -+ if _offline(): -+ ret["result"] = True -+ ret["comment"] = "Running in OFFLINE mode. Nothing to do" -+ return ret -+ - # Check if the service is available - try: - if not _available(name, ret): -diff --git a/salt/utils/systemd.py b/salt/utils/systemd.py -index 85a9c9172c..702e06147a 100644 ---- a/salt/utils/systemd.py -+++ b/salt/utils/systemd.py -@@ -7,7 +7,7 @@ import os - import re - import subprocess - --import salt.loader_context -+import salt.utils.path - import salt.utils.stringutils - from salt.exceptions import SaltInvocationError - -@@ -51,6 +51,30 @@ def booted(context=None): - return ret - - -+def offline(context=None): -+ """Return True if systemd is in offline mode -+ -+ .. versionadded:: 3004 -+ """ -+ contextkey = "salt.utils.systemd.offline" -+ if isinstance(context, (dict, salt.loader_context.NamedLoaderContext)): -+ if contextkey in context: -+ return context[contextkey] -+ elif context is not None: -+ raise SaltInvocationError("context must be a dictionary if passed") -+ -+ # Note that there is a difference from SYSTEMD_OFFLINE=1. Here we -+ # assume that there is no PID 1 to talk with. -+ ret = not booted(context) and salt.utils.path.which("systemctl") -+ -+ try: -+ context[contextkey] = ret -+ except TypeError: -+ pass -+ -+ return ret -+ -+ - def version(context=None): - """ - Attempts to run systemctl --version. Returns None if unable to determine -diff --git a/tests/integration/states/test_service.py b/tests/integration/states/test_service.py -index 9aa393b630..b775e6765a 100644 ---- a/tests/integration/states/test_service.py -+++ b/tests/integration/states/test_service.py -@@ -25,6 +25,7 @@ class ServiceTest(ModuleCase, SaltReturnAssertsMixin): - cmd_name = "crontab" - os_family = self.run_function("grains.get", ["os_family"]) - os_release = self.run_function("grains.get", ["osrelease"]) -+ is_systemd = self.run_function("grains.get", ["systemd"]) - self.stopped = False - self.running = True - if os_family == "RedHat": -@@ -52,6 +53,9 @@ class ServiceTest(ModuleCase, SaltReturnAssertsMixin): - if os_family != "Windows" and salt.utils.path.which(cmd_name) is None: - self.skipTest("{} is not installed".format(cmd_name)) - -+ if is_systemd and self.run_function("service.offline"): -+ self.skipTest("systemd is OFFLINE") -+ - def tearDown(self): - if self.post_srv_disable: - self.run_function("service.disable", name=self.service_name) -diff --git a/tests/pytests/unit/modules/test_rebootmgr.py b/tests/pytests/unit/modules/test_rebootmgr.py -new file mode 100644 -index 0000000000..a2ceb65cd9 ---- /dev/null -+++ b/tests/pytests/unit/modules/test_rebootmgr.py -@@ -0,0 +1,309 @@ -+import pytest -+import salt.modules.rebootmgr as rebootmgr -+from salt.exceptions import CommandExecutionError -+from tests.support.mock import MagicMock, patch -+ -+ -+@pytest.fixture -+def configure_loader_modules(): -+ return {rebootmgr: {"__salt__": {}, "__utils__": {}}} -+ -+ -+def test_version(): -+ """ -+ Test rebootmgr.version without parameters -+ """ -+ version = "rebootmgrctl (rebootmgr) 1.3" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": version, "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.version() == "1.3" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "--version"]) -+ -+ -+def test_is_active(): -+ """ -+ Test rebootmgr.is_active without parameters -+ """ -+ salt_mock = {"cmd.run_all": MagicMock(return_value={"stdout": None, "retcode": 0})} -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.is_active() -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "is_active", "--quiet"] -+ ) -+ -+ -+def test_reboot(): -+ """ -+ Test rebootmgr.reboot without parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.reboot() == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "reboot"]) -+ -+ -+def test_reboot_order(): -+ """ -+ Test rebootmgr.reboot with order parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.reboot("now") == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "reboot", "now"]) -+ -+ -+def test_reboot_invalid(): -+ """ -+ Test rebootmgr.reboot with invalid parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ with pytest.raises(CommandExecutionError): -+ rebootmgr.reboot("invalid") -+ -+ -+def test_cancel(): -+ """ -+ Test rebootmgr.cancel without parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.cancel() == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "cancel"]) -+ -+ -+def test_status(): -+ """ -+ Test rebootmgr.status without parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ # 0 - No reboot requested -+ assert rebootmgr.status() == 0 -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "status", "--quiet"] -+ ) -+ -+ -+def test_set_strategy_default(): -+ """ -+ Test rebootmgr.set_strategy without parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.set_strategy() == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "set-strategy"]) -+ -+ -+def test_set_strategy(): -+ """ -+ Test rebootmgr.set_strategy with strategy parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.set_strategy("best-effort") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "set-strategy", "best-effort"] -+ ) -+ -+ -+def test_set_strategy_invalid(): -+ """ -+ Test rebootmgr.strategy with invalid parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ with pytest.raises(CommandExecutionError): -+ rebootmgr.set_strategy("invalid") -+ -+ -+def test_get_strategy(): -+ """ -+ Test rebootmgr.get_strategy without parameters -+ """ -+ strategy = "Reboot strategy: best-effort" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": strategy, "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.get_strategy() == "best-effort" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "get-strategy"]) -+ -+ -+def test_set_window(): -+ """ -+ Test rebootmgr.set_window with parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.set_window("Thu,Fri 2020-*-1,5 11:12:13", "1h") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "set-window", "Thu,Fri 2020-*-1,5 11:12:13", "1h"] -+ ) -+ -+ -+def test_get_window(): -+ """ -+ Test rebootmgr.get_window without parameters -+ """ -+ window = "Maintenance window is set to *-*-* 03:30:00, lasting 01h30m." -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": window, "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.get_window() == { -+ "time": "*-*-* 03:30:00", -+ "duration": "01h30m", -+ } -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "get-window"]) -+ -+ -+def test_set_group(): -+ """ -+ Test rebootmgr.set_group with parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.set_group("group1") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "set-group", "group1"] -+ ) -+ -+ -+def test_get_group(): -+ """ -+ Test rebootmgr.get_group without parameters -+ """ -+ group = "Etcd lock group is set to group1" -+ salt_mock = {"cmd.run_all": MagicMock(return_value={"stdout": group, "retcode": 0})} -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.get_group() == "group1" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "get-group"]) -+ -+ -+def test_set_max(): -+ """ -+ Test rebootmgr.set_max with default parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.set_max(10) == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "set-max", 10]) -+ -+ -+def test_set_max_group(): -+ """ -+ Test rebootmgr.set_max with group parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.set_max(10, "group1") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "set-max", "--group", "group1", 10] -+ ) -+ -+ -+def test_lock(): -+ """ -+ Test rebootmgr.lock without parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.lock() == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "lock"]) -+ -+ -+def test_lock_machine_id(): -+ """ -+ Test rebootmgr.lock with machine_id parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.lock("machine-id") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "lock", "machine-id"] -+ ) -+ -+ -+def test_lock_machine_id_group(): -+ """ -+ Test rebootmgr.lock with machine_id and group parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.lock("machine-id", "group1") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "lock", "--group", "group1", "machine-id"] -+ ) -+ -+ -+def test_unlock(): -+ """ -+ Test rebootmgr.unlock without parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.unlock() == "output" -+ salt_mock["cmd.run_all"].assert_called_with(["rebootmgrctl", "unlock"]) -+ -+ -+def test_unlock_machine_id(): -+ """ -+ Test rebootmgr.unlock with machine_id parameter -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.unlock("machine-id") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "unlock", "machine-id"] -+ ) -+ -+ -+def test_unlock_machine_id_group(): -+ """ -+ Test rebootmgr.unlock with machine_id and group parameters -+ """ -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(rebootmgr.__salt__, salt_mock): -+ assert rebootmgr.unlock("machine-id", "group1") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["rebootmgrctl", "unlock", "--group", "group1", "machine-id"] -+ ) + # Clone the options data and apply some default values. May not be diff --git a/tests/pytests/unit/modules/test_transactional_update.py b/tests/pytests/unit/modules/test_transactional_update.py -new file mode 100644 -index 0000000000..e7293cf3e2 ---- /dev/null +index f9eb1fd595..032ca0c9e8 100644 +--- a/tests/pytests/unit/modules/test_transactional_update.py +++ b/tests/pytests/unit/modules/test_transactional_update.py -@@ -0,0 +1,999 @@ -+import sys -+ -+import pytest -+import salt.loader_context -+import salt.modules.state as statemod -+import salt.modules.transactional_update as tu -+from salt.exceptions import CommandExecutionError -+from tests.support.mock import MagicMock, patch -+ -+pytestmark = [ -+ pytest.mark.skip_on_windows(reason="Not supported on Windows"), -+] -+ -+ -+@pytest.fixture -+def configure_loader_modules(): -+ loader_context = salt.loader_context.LoaderContext() -+ return { +@@ -1,6 +1,7 @@ + import sys + + import pytest ++import salt.loader.context + import salt.modules.state as statemod + import salt.modules.transactional_update as tu + from salt.exceptions import CommandExecutionError +@@ -13,8 +14,15 @@ pytestmark = [ + + @pytest.fixture + def configure_loader_modules(): ++ loader_context = salt.loader.context.LoaderContext() + return { +- tu: {"__salt__": {}, "__utils__": {}}, + tu: { + "__salt__": {}, + "__utils__": {}, -+ "__pillar__": salt.loader_context.NamedLoaderContext( ++ "__pillar__": salt.loader.context.NamedLoaderContext( + "__pillar__", loader_context, {} + ), + }, -+ statemod: {"__salt__": {}, "__context__": {}}, -+ } -+ -+ -+def test__global_params_no_self_update(): -+ """Test transactional_update._global_params without self_update""" -+ assert tu._global_params(self_update=False) == [ -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ ] -+ -+ -+def test__global_params_self_update(): -+ """Test transactional_update._global_params with self_update""" -+ assert tu._global_params(self_update=True) == [ -+ "--non-interactive", -+ "--drop-if-no-change", -+ ] -+ -+ -+def test__global_params_no_self_update_snapshot(): -+ """Test transactional_update._global_params without self_update and -+ snapshot -+ -+ """ -+ assert tu._global_params(self_update=False, snapshot=10) == [ -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ 10, -+ ] -+ -+ -+def test__global_params_no_self_update_continue(): -+ """Test transactional_update._global_params without self_update and -+ snapshot conitue -+ -+ """ -+ assert tu._global_params(self_update=False, snapshot="continue") == [ -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ ] -+ -+ -+def test__pkg_params_no_packages(): -+ """Test transactional_update._pkg_params without packages""" -+ with pytest.raises(CommandExecutionError): -+ tu._pkg_params(pkg=None, pkgs=None, args=None) -+ -+ -+def test__pkg_params_pkg(): -+ """Test transactional_update._pkg_params with single package""" -+ assert tu._pkg_params(pkg="pkg1", pkgs=None, args=None) == ["pkg1"] -+ -+ -+def test__pkg_params_pkgs(): -+ """Test transactional_update._pkg_params with packages""" -+ assert tu._pkg_params(pkg=None, pkgs="pkg1", args=None) == ["pkg1"] -+ assert tu._pkg_params(pkg=None, pkgs="pkg1 pkg2 ", args=None) == [ -+ "pkg1", -+ "pkg2", -+ ] -+ assert tu._pkg_params(pkg=None, pkgs=["pkg1", "pkg2"], args=None) == [ -+ "pkg1", -+ "pkg2", -+ ] -+ -+ -+def test__pkg_params_pkg_pkgs(): -+ """Test transactional_update._pkg_params with packages""" -+ assert tu._pkg_params(pkg="pkg1", pkgs="pkg2", args=None) == [ -+ "pkg1", -+ "pkg2", -+ ] -+ -+ -+def test__pkg_params_args(): -+ """Test transactional_update._pkg_params with argumens""" -+ assert tu._pkg_params(pkg="pkg1", pkgs=None, args="--arg1") == [ -+ "--arg1", -+ "pkg1", -+ ] -+ assert tu._pkg_params(pkg="pkg1", pkgs=None, args="--arg1 --arg2") == [ -+ "--arg1", -+ "--arg2", -+ "pkg1", -+ ] -+ assert tu._pkg_params(pkg="pkg1", pkgs=None, args=["--arg1", "--arg2"]) == [ -+ "--arg1", -+ "--arg2", -+ "pkg1", -+ ] -+ -+ -+def test_transactional_transactional(): -+ """Test transactional_update.transactional""" -+ matrix = (("/usr/sbin/transactional-update", True), ("", False)) -+ -+ for path_which, result in matrix: -+ utils_mock = {"path.which": MagicMock(return_value=path_which)} -+ -+ with patch.dict(tu.__utils__, utils_mock): -+ assert tu.transactional() is result -+ utils_mock["path.which"].assert_called_with("transactional-update") -+ -+ -+def test_in_transaction(): -+ """Test transactional_update.in_transaction""" -+ matrix = ( -+ ("/usr/sbin/transactional-update", True, True), -+ ("/usr/sbin/transactional-update", False, False), -+ ("", True, False), -+ ("", False, False), -+ ) -+ -+ for path_which, in_chroot, result in matrix: -+ utils_mock = {"path.which": MagicMock(return_value=path_which)} -+ salt_mock = {"chroot.in_chroot": MagicMock(return_value=in_chroot)} -+ -+ with patch.dict(tu.__utils__, utils_mock): -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.in_transaction() is result -+ -+ -+def test_commands_with_global_params(): -+ """Test commands that only accept global params""" -+ for cmd in [ -+ "cleanup", -+ "cleanup_snapshots", -+ "cleanup_overlays", -+ "grub_cfg", -+ "bootloader", -+ "initrd", -+ "kdump", -+ "reboot", -+ "dup", -+ "up", -+ "patch", -+ "migration", -+ ]: -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert getattr(tu, cmd)() == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ cmd.replace("_", ".") -+ if cmd.startswith("grub") -+ else cmd.replace("_", "-"), -+ ] -+ ) -+ -+ -+def test_run_error(): -+ """Test transactional_update.run with missing command""" -+ with pytest.raises(CommandExecutionError): -+ tu.run(None) -+ -+ -+def test_run_string(): -+ """Test transactional_update.run with command as string""" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.run("cmd --flag p1 p2") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--quiet", -+ "run", -+ "cmd", -+ "--flag", -+ "p1", -+ "p2", -+ ] -+ ) -+ -+ -+def test_run_array(): -+ """Test transactional_update.run with command as array""" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.run(["cmd", "--flag", "p1", "p2"]) == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--quiet", -+ "run", -+ "cmd", -+ "--flag", -+ "p1", -+ "p2", -+ ] -+ ) -+ -+ -+def test_pkg_commands(): -+ """Test transactional_update.pkg_* commands""" -+ for cmd in ["pkg_install", "pkg_remove", "pkg_update"]: -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert getattr(tu, cmd)("pkg1", "pkg2 pkg3", "--arg") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "pkg", -+ cmd.replace("pkg_", ""), -+ "--arg", -+ "pkg1", -+ "pkg2", -+ "pkg3", -+ ] -+ ) -+ -+ -+def test_rollback_error(): -+ """Test transactional_update.rollback with wrong snapshot""" -+ with pytest.raises(CommandExecutionError): -+ tu.rollback("error") -+ -+ -+def test_rollback_default(): -+ """Test transactional_update.rollback with default snapshot""" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.rollback() == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["transactional-update", "rollback"] -+ ) -+ -+ -+def test_rollback_snapshot_number(): -+ """Test transactional_update.rollback with numeric snapshot""" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.rollback(10) == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["transactional-update", "rollback", 10] -+ ) -+ -+ -+def test_rollback_snapshot_str(): -+ """Test transactional_update.rollback with string snapshot""" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.rollback("10") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["transactional-update", "rollback", "10"] -+ ) -+ -+ -+def test_rollback_last(): -+ """Test transactional_update.rollback with last snapshot""" -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": "output", "retcode": 0}) -+ } -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.rollback("last") == "output" -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["transactional-update", "rollback", "last"] -+ ) -+ -+ -+def test_pending_transaction(): -+ """Test transactional_update.pending_transaction""" -+ matrix = ( -+ (False, ["1", "2+", "3-"], True), -+ (False, ["1", "2-", "3+"], True), -+ (False, ["1", "2", "3*"], False), -+ ) -+ -+ for in_transaction, snapshots, result in matrix: -+ salt_mock = { -+ "cmd.run_all": MagicMock(return_value={"stdout": snapshots, "retcode": 0}) -+ } -+ -+ tu_in_transaction = "salt.modules.transactional_update.in_transaction" -+ with patch(tu_in_transaction) as in_transaction_mock: -+ in_transaction_mock.return_value = in_transaction -+ with patch.dict(tu.__salt__, salt_mock): -+ assert tu.pending_transaction() is result -+ salt_mock["cmd.run_all"].assert_called_with( -+ ["snapper", "--no-dbus", "list", "--columns", "number"] -+ ) -+ -+ -+def test_pending_transaction_in_transaction(): -+ """Test transactional_update.pending_transaction when in transaction""" -+ tu_in_transaction = "salt.modules.transactional_update.in_transaction" -+ with patch(tu_in_transaction) as in_transaction_mock: -+ in_transaction_mock.return_value = True -+ with pytest.raises(CommandExecutionError): -+ tu.pending_transaction() -+ -+ -+def test_call_fails_input_validation(): -+ """Test transactional_update.call missing function name""" -+ with pytest.raises(CommandExecutionError): -+ tu.call("") -+ -+ -+@patch("tempfile.mkdtemp", MagicMock(return_value="/var/cache/salt/minion/tmp01")) -+def test_call_fails_untar(): -+ """Test transactional_update.call when tar fails""" -+ utils_mock = { -+ "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), -+ "files.rm_rf": MagicMock(), -+ } -+ opts_mock = {"cachedir": "/var/cache/salt/minion"} -+ salt_mock = { -+ "cmd.run": MagicMock(return_value="Error"), -+ "config.option": MagicMock(), -+ } -+ with patch.dict(tu.__utils__, utils_mock), patch.dict( -+ tu.__opts__, opts_mock -+ ), patch.dict(tu.__salt__, salt_mock): -+ assert tu.call("/chroot", "test.ping") == { -+ "result": False, -+ "comment": "Error", -+ } -+ -+ utils_mock["thin.gen_thin"].assert_called_once() -+ salt_mock["config.option"].assert_called() -+ salt_mock["cmd.run"].assert_called_once() -+ utils_mock["files.rm_rf"].assert_called_once() -+ -+ -+@patch("tempfile.mkdtemp", MagicMock(return_value="/var/cache/salt/minion/tmp01")) -+def test_call_fails_salt_thin(): -+ """Test transactional_update.chroot when fails salt_thin""" -+ utils_mock = { -+ "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), -+ "files.rm_rf": MagicMock(), -+ "json.find_json": MagicMock(side_effect=ValueError()), -+ } -+ opts_mock = {"cachedir": "/var/cache/salt/minion"} -+ salt_mock = { -+ "cmd.run": MagicMock(return_value=""), -+ "config.option": MagicMock(), -+ "cmd.run_all": MagicMock(return_value={"retcode": 1, "stderr": "Error"}), -+ } -+ with patch.dict(tu.__utils__, utils_mock), patch.dict( -+ tu.__opts__, opts_mock -+ ), patch.dict(tu.__salt__, salt_mock): -+ assert tu.call("test.ping") == { -+ "result": False, -+ "retcode": 1, -+ "comment": "Error", -+ } -+ -+ utils_mock["thin.gen_thin"].assert_called_once() -+ salt_mock["config.option"].assert_called() -+ salt_mock["cmd.run"].assert_called_once() -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ "--quiet", -+ "run", -+ "python{}".format(sys.version_info[0]), -+ "/var/cache/salt/minion/tmp01/salt-call", -+ "--metadata", -+ "--local", -+ "--log-file", -+ "/var/cache/salt/minion/tmp01/log", -+ "--cachedir", -+ "/var/cache/salt/minion/tmp01/cache", -+ "--out", -+ "json", -+ "-l", -+ "quiet", -+ "--", -+ "test.ping", -+ ] -+ ) -+ utils_mock["files.rm_rf"].assert_called_once() -+ -+ -+@patch("tempfile.mkdtemp", MagicMock(return_value="/var/cache/salt/minion/tmp01")) -+def test_call_fails_function(): -+ """Test transactional_update.chroot when fails the function""" -+ utils_mock = { -+ "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), -+ "files.rm_rf": MagicMock(), -+ "json.find_json": MagicMock(side_effect=ValueError()), -+ } -+ opts_mock = {"cachedir": "/var/cache/salt/minion"} -+ salt_mock = { -+ "cmd.run": MagicMock(return_value=""), -+ "config.option": MagicMock(), -+ "cmd.run_all": MagicMock( -+ return_value={"retcode": 0, "stdout": "Not found", "stderr": ""} -+ ), -+ } -+ with patch.dict(tu.__utils__, utils_mock), patch.dict( -+ tu.__opts__, opts_mock -+ ), patch.dict(tu.__salt__, salt_mock): -+ assert tu.call("test.ping") == { -+ "result": False, -+ "retcode": 1, -+ "comment": "Not found", -+ } -+ -+ utils_mock["thin.gen_thin"].assert_called_once() -+ salt_mock["config.option"].assert_called() -+ salt_mock["cmd.run"].assert_called_once() -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ "--quiet", -+ "run", -+ "python{}".format(sys.version_info[0]), -+ "/var/cache/salt/minion/tmp01/salt-call", -+ "--metadata", -+ "--local", -+ "--log-file", -+ "/var/cache/salt/minion/tmp01/log", -+ "--cachedir", -+ "/var/cache/salt/minion/tmp01/cache", -+ "--out", -+ "json", -+ "-l", -+ "quiet", -+ "--", -+ "test.ping", -+ ] -+ ) -+ utils_mock["files.rm_rf"].assert_called_once() -+ -+ -+@patch("tempfile.mkdtemp", MagicMock(return_value="/var/cache/salt/minion/tmp01")) -+def test_call_success_no_reboot(): -+ """Test transactional_update.chroot when succeed""" -+ utils_mock = { -+ "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), -+ "files.rm_rf": MagicMock(), -+ "json.find_json": MagicMock(return_value={"return": "result"}), -+ } -+ opts_mock = {"cachedir": "/var/cache/salt/minion"} -+ salt_mock = { -+ "cmd.run": MagicMock(return_value=""), -+ "config.option": MagicMock(), -+ "cmd.run_all": MagicMock(return_value={"retcode": 0, "stdout": ""}), -+ } -+ with patch.dict(tu.__utils__, utils_mock), patch.dict( -+ tu.__opts__, opts_mock -+ ), patch.dict(tu.__salt__, salt_mock): -+ assert tu.call("test.ping") == "result" -+ -+ utils_mock["thin.gen_thin"].assert_called_once() -+ salt_mock["config.option"].assert_called() -+ salt_mock["cmd.run"].assert_called_once() -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ "--quiet", -+ "run", -+ "python{}".format(sys.version_info[0]), -+ "/var/cache/salt/minion/tmp01/salt-call", -+ "--metadata", -+ "--local", -+ "--log-file", -+ "/var/cache/salt/minion/tmp01/log", -+ "--cachedir", -+ "/var/cache/salt/minion/tmp01/cache", -+ "--out", -+ "json", -+ "-l", -+ "quiet", -+ "--", -+ "test.ping", -+ ] -+ ) -+ utils_mock["files.rm_rf"].assert_called_once() -+ -+ -+@patch("tempfile.mkdtemp", MagicMock(return_value="/var/cache/salt/minion/tmp01")) -+def test_call_success_reboot(): -+ """Test transactional_update.chroot when succeed and reboot""" -+ pending_transaction_mock = MagicMock(return_value=True) -+ reboot_mock = MagicMock() -+ utils_mock = { -+ "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), -+ "files.rm_rf": MagicMock(), -+ "json.find_json": MagicMock(return_value={"return": "result"}), -+ } -+ opts_mock = {"cachedir": "/var/cache/salt/minion"} -+ salt_mock = { -+ "cmd.run": MagicMock(return_value=""), -+ "config.option": MagicMock(), -+ "cmd.run_all": MagicMock(return_value={"retcode": 0, "stdout": ""}), -+ } -+ with patch.dict(tu.__utils__, utils_mock), patch.dict( -+ tu.__opts__, opts_mock -+ ), patch.dict(tu.__salt__, salt_mock), patch( -+ "salt.modules.transactional_update.pending_transaction", -+ pending_transaction_mock, -+ ), patch( -+ "salt.modules.transactional_update.reboot", reboot_mock -+ ): -+ assert ( -+ tu.call("transactional_update.dup", activate_transaction=True) == "result" -+ ) -+ -+ utils_mock["thin.gen_thin"].assert_called_once() -+ salt_mock["config.option"].assert_called() -+ salt_mock["cmd.run"].assert_called_once() -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ "--quiet", -+ "run", -+ "python{}".format(sys.version_info[0]), -+ "/var/cache/salt/minion/tmp01/salt-call", -+ "--metadata", -+ "--local", -+ "--log-file", -+ "/var/cache/salt/minion/tmp01/log", -+ "--cachedir", -+ "/var/cache/salt/minion/tmp01/cache", -+ "--out", -+ "json", -+ "-l", -+ "quiet", -+ "--", -+ "transactional_update.dup", -+ ] -+ ) -+ utils_mock["files.rm_rf"].assert_called_once() -+ pending_transaction_mock.assert_called_once() -+ reboot_mock.assert_called_once() -+ -+ -+@patch("tempfile.mkdtemp", MagicMock(return_value="/var/cache/salt/minion/tmp01")) -+def test_call_success_parameters(): -+ """Test transactional_update.chroot when succeed with parameters""" -+ utils_mock = { -+ "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), -+ "files.rm_rf": MagicMock(), -+ "json.find_json": MagicMock(return_value={"return": "result"}), -+ } -+ opts_mock = {"cachedir": "/var/cache/salt/minion"} -+ salt_mock = { -+ "cmd.run": MagicMock(return_value=""), -+ "config.option": MagicMock(), -+ "cmd.run_all": MagicMock(return_value={"retcode": 0, "stdout": ""}), -+ } -+ with patch.dict(tu.__utils__, utils_mock), patch.dict( -+ tu.__opts__, opts_mock -+ ), patch.dict(tu.__salt__, salt_mock): -+ assert tu.call("module.function", key="value") == "result" -+ -+ utils_mock["thin.gen_thin"].assert_called_once() -+ salt_mock["config.option"].assert_called() -+ salt_mock["cmd.run"].assert_called_once() -+ salt_mock["cmd.run_all"].assert_called_with( -+ [ -+ "transactional-update", -+ "--non-interactive", -+ "--drop-if-no-change", -+ "--no-selfupdate", -+ "--continue", -+ "--quiet", -+ "run", -+ "python{}".format(sys.version_info[0]), -+ "/var/cache/salt/minion/tmp01/salt-call", -+ "--metadata", -+ "--local", -+ "--log-file", -+ "/var/cache/salt/minion/tmp01/log", -+ "--cachedir", -+ "/var/cache/salt/minion/tmp01/cache", -+ "--out", -+ "json", -+ "-l", -+ "quiet", -+ "--", -+ "module.function", -+ "key=value", -+ ] -+ ) -+ utils_mock["files.rm_rf"].assert_called_once() -+ -+ -+def test_sls(): -+ """Test transactional_update.sls""" -+ transactional_update_highstate_mock = MagicMock() -+ transactional_update_highstate_mock.return_value = ( -+ transactional_update_highstate_mock -+ ) -+ transactional_update_highstate_mock.render_highstate.return_value = (None, []) -+ transactional_update_highstate_mock.state.reconcile_extend.return_value = (None, []) -+ transactional_update_highstate_mock.state.requisite_in.return_value = (None, []) -+ transactional_update_highstate_mock.state.verify_high.return_value = [] -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock(return_value=[]), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.modules.transactional_update.TransactionalUpdateHighstate", -+ transactional_update_highstate_mock, -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.sls("module") == "result" -+ _create_and_execute_salt_state_mock.assert_called_once() -+ -+ -+def test_sls_queue_true(): -+ """Test transactional_update.sls""" -+ transactional_update_highstate_mock = MagicMock() -+ transactional_update_highstate_mock.return_value = ( -+ transactional_update_highstate_mock -+ ) -+ transactional_update_highstate_mock.render_highstate.return_value = (None, []) -+ transactional_update_highstate_mock.state.reconcile_extend.return_value = (None, []) -+ transactional_update_highstate_mock.state.requisite_in.return_value = (None, []) -+ transactional_update_highstate_mock.state.verify_high.return_value = [] -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock( -+ side_effect=[ -+ [ -+ { -+ "fun": "state.running", -+ "pid": "4126", -+ "jid": "20150325123407204096", -+ } -+ ], -+ [], -+ ] -+ ), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.modules.transactional_update.TransactionalUpdateHighstate", -+ transactional_update_highstate_mock, -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.sls("module", queue=True) == "result" -+ _create_and_execute_salt_state_mock.assert_called_once() -+ -+ -+def test_sls_queue_false_failing(): -+ """Test transactional_update.sls""" -+ transactional_update_highstate_mock = MagicMock() -+ transactional_update_highstate_mock.return_value = ( -+ transactional_update_highstate_mock -+ ) -+ transactional_update_highstate_mock.render_highstate.return_value = (None, []) -+ transactional_update_highstate_mock.state.reconcile_extend.return_value = (None, []) -+ transactional_update_highstate_mock.state.requisite_in.return_value = (None, []) -+ transactional_update_highstate_mock.state.verify_high.return_value = [] -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock( -+ side_effect=[ -+ [ -+ { -+ "fun": "state.running", -+ "pid": "4126", -+ "jid": "20150325123407204096", -+ } -+ ], -+ [], -+ ] -+ ), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.modules.transactional_update.TransactionalUpdateHighstate", -+ transactional_update_highstate_mock, -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.sls("module", queue=False) == [ -+ 'The function "state.running" is running as PID 4126 and was started at 2015, Mar 25 12:34:07.204096 with jid 20150325123407204096' -+ ] -+ _create_and_execute_salt_state_mock.assert_not_called() -+ -+ -+def test_highstate(): -+ """Test transactional_update.highstage""" -+ transactional_update_highstate_mock = MagicMock() -+ transactional_update_highstate_mock.return_value = ( -+ transactional_update_highstate_mock -+ ) -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock(return_value=[]), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.modules.transactional_update.TransactionalUpdateHighstate", -+ transactional_update_highstate_mock, -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.highstate() == "result" -+ _create_and_execute_salt_state_mock.assert_called_once() -+ -+ -+def test_highstate_queue_true(): -+ """Test transactional_update.highstage""" -+ transactional_update_highstate_mock = MagicMock() -+ transactional_update_highstate_mock.return_value = ( -+ transactional_update_highstate_mock -+ ) -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock( -+ side_effect=[ -+ [ -+ { -+ "fun": "state.running", -+ "pid": "4126", -+ "jid": "20150325123407204096", -+ } -+ ], -+ [], -+ ] -+ ), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.modules.transactional_update.TransactionalUpdateHighstate", -+ transactional_update_highstate_mock, -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.highstate(queue=True) == "result" -+ _create_and_execute_salt_state_mock.assert_called_once() -+ -+ -+def test_highstate_queue_false_failing(): -+ """Test transactional_update.highstage""" -+ transactional_update_highstate_mock = MagicMock() -+ transactional_update_highstate_mock.return_value = ( -+ transactional_update_highstate_mock -+ ) -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock( -+ side_effect=[ -+ [ -+ { -+ "fun": "state.running", -+ "pid": "4126", -+ "jid": "20150325123407204096", -+ } -+ ], -+ [], -+ ] -+ ), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.modules.transactional_update.TransactionalUpdateHighstate", -+ transactional_update_highstate_mock, -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.highstate(queue=False) == [ -+ 'The function "state.running" is running as PID 4126 and was started at 2015, Mar 25 12:34:07.204096 with jid 20150325123407204096' -+ ] -+ _create_and_execute_salt_state_mock.assert_not_called() -+ -+ -+def test_single(): -+ """Test transactional_update.single""" -+ ssh_state_mock = MagicMock() -+ ssh_state_mock.return_value = ssh_state_mock -+ ssh_state_mock.verify_data.return_value = None -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock(return_value=[]), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.client.ssh.state.SSHState", ssh_state_mock -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.single("pkg.installed", name="emacs") == "result" -+ _create_and_execute_salt_state_mock.assert_called_once() -+ -+ -+def test_single_queue_false_failing(): -+ """Test transactional_update.single""" -+ ssh_state_mock = MagicMock() -+ ssh_state_mock.return_value = ssh_state_mock -+ ssh_state_mock.verify_data.return_value = None -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock( -+ side_effect=[ -+ [ -+ { -+ "fun": "state.running", -+ "pid": "4126", -+ "jid": "20150325123407204096", -+ } -+ ], -+ [], -+ ] -+ ), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.client.ssh.state.SSHState", ssh_state_mock -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.single("pkg.installed", name="emacs", queue=False) == [ -+ 'The function "state.running" is running as PID 4126 and was started at 2015, Mar 25 12:34:07.204096 with jid 20150325123407204096' -+ ] -+ _create_and_execute_salt_state_mock.assert_not_called() -+ -+ -+def test_single_queue_true(): -+ """Test transactional_update.single""" -+ ssh_state_mock = MagicMock() -+ ssh_state_mock.return_value = ssh_state_mock -+ ssh_state_mock.verify_data.return_value = None -+ -+ _create_and_execute_salt_state_mock = MagicMock(return_value="result") -+ opts_mock = { -+ "hash_type": "md5", -+ } -+ salt_mock = { -+ "saltutil.is_running": MagicMock( -+ side_effect=[ -+ [ -+ { -+ "fun": "state.running", -+ "pid": "4126", -+ "jid": "20150325123407204096", -+ } -+ ], -+ [], -+ ] -+ ), -+ } -+ get_sls_opts_mock = MagicMock(return_value=opts_mock) -+ with patch.dict(tu.__opts__, opts_mock), patch.dict( -+ statemod.__salt__, salt_mock -+ ), patch("salt.utils.state.get_sls_opts", get_sls_opts_mock), patch( -+ "salt.fileclient.get_file_client", MagicMock() -+ ), patch( -+ "salt.client.ssh.state.SSHState", ssh_state_mock -+ ), patch( -+ "salt.modules.transactional_update._create_and_execute_salt_state", -+ _create_and_execute_salt_state_mock, -+ ): -+ assert tu.single("pkg.installed", name="emacs", queue=True) == "result" -+ _create_and_execute_salt_state_mock.assert_called_once() -diff --git a/tests/pytests/unit/states/test_service.py b/tests/pytests/unit/states/test_service.py -index 16deafdbe9..1006aee317 100644 ---- a/tests/pytests/unit/states/test_service.py -+++ b/tests/pytests/unit/states/test_service.py -@@ -316,6 +316,22 @@ def test_running(): - assert service.__context__ == {"service.state": "running"} + statemod: {"__salt__": {}, "__context__": {}}, + } - -+def test_running_in_offline_mode(): -+ """ -+ Tests the case in which a service.running state is executed on an offline environemnt -+ -+ """ -+ name = "thisisnotarealservice" -+ with patch.object(service, "_offline", MagicMock(return_value=True)): -+ ret = service.running(name=name) -+ assert ret == { -+ "changes": {}, -+ "comment": "Running in OFFLINE mode. Nothing to do", -+ "result": True, -+ "name": name, -+ } -+ -+ - def test_dead(): - """ - Test to ensure that the named service is dead -@@ -454,6 +470,22 @@ def test_dead_with_missing_service(): - } - - -+def test_dead_in_offline_mode(): -+ """ -+ Tests the case in which a service.dead state is executed on an offline environemnt -+ -+ """ -+ name = "thisisnotarealservice" -+ with patch.object(service, "_offline", MagicMock(return_value=True)): -+ ret = service.dead(name=name) -+ assert ret == { -+ "changes": {}, -+ "comment": "Running in OFFLINE mode. Nothing to do", -+ "result": True, -+ "name": name, -+ } -+ -+ - def test_enabled(): - """ - Test to verify that the service is enabled -@@ -664,6 +696,8 @@ def test_running_with_reload(): - service.__utils__, utils - ), patch.dict( - service.__opts__, {"test": False} -+ ), patch( -+ "salt.utils.systemd.offline", MagicMock(return_value=False) - ): - service.dead(service_name, enable=False) - result = service.running(name=service_name, enable=True, reload=False) diff --git a/tests/unit/modules/test_chroot.py b/tests/unit/modules/test_chroot.py -index 76811df46e..9480f3aa7a 100644 +index cdbfcb0fab..9cdfeaf066 100644 --- a/tests/unit/modules/test_chroot.py +++ b/tests/unit/modules/test_chroot.py -@@ -1,4 +1,3 @@ --# -*- coding: utf-8 -*- - # - # Author: Alberto Planas - # -@@ -26,16 +25,13 @@ - :platform: Linux - """ - --# Import Python Libs --from __future__ import absolute_import, print_function, unicode_literals +@@ -27,6 +27,7 @@ import sys -+import salt.loader_context ++import salt.loader.context import salt.modules.chroot as chroot import salt.utils.platform from salt.exceptions import CommandExecutionError -- --# Import Salt Testing Libs - from tests.support.mixins import LoaderModuleMockMixin - from tests.support.mock import MagicMock, patch - from tests.support.unit import TestCase, skipIf -@@ -48,7 +44,17 @@ class ChrootTestCase(TestCase, LoaderModuleMockMixin): +@@ -42,7 +43,17 @@ class ChrootTestCase(TestCase, LoaderModuleMockMixin): """ def setup_loader_modules(self): - return {chroot: {"__salt__": {}, "__utils__": {}, "__opts__": {"cachedir": ""}}} -+ loader_context = salt.loader_context.LoaderContext() ++ loader_context = salt.loader.context.LoaderContext() + return { + chroot: { + "__salt__": {}, + "__utils__": {}, + "__opts__": {"cachedir": ""}, -+ "__pillar__": salt.loader_context.NamedLoaderContext( ++ "__pillar__": salt.loader.context.NamedLoaderContext( + "__pillar__", loader_context, {} + ), + } @@ -3866,120 +220,7 @@ index 76811df46e..9480f3aa7a 100644 @patch("os.path.isdir") def test_exist(self, isdir): -@@ -75,6 +81,17 @@ class ChrootTestCase(TestCase, LoaderModuleMockMixin): - self.assertTrue(chroot.create("/chroot")) - makedirs.assert_called() - -+ @patch("salt.utils.files.fopen") -+ def test_in_chroot(self, fopen): -+ """ -+ Test the detection of chroot environment. -+ """ -+ matrix = (("a", "b", True), ("a", "a", False)) -+ for root_mountinfo, self_mountinfo, result in matrix: -+ fopen.return_value.__enter__.return_value = fopen -+ fopen.read = MagicMock(side_effect=(root_mountinfo, self_mountinfo)) -+ self.assertEqual(chroot.in_chroot(), result) -+ - @patch("salt.modules.chroot.exist") - def test_call_fails_input_validation(self, exist): - """ -@@ -126,19 +143,25 @@ class ChrootTestCase(TestCase, LoaderModuleMockMixin): - utils_mock = { - "thin.gen_thin": MagicMock(return_value="/salt-thin.tgz"), - "files.rm_rf": MagicMock(), -- "json.find_json": MagicMock(return_value={"return": {}}), -+ "json.find_json": MagicMock(side_effect=ValueError()), - } - salt_mock = { - "cmd.run": MagicMock(return_value=""), - "config.option": MagicMock(), -- "cmd.run_chroot": MagicMock(return_value={"retcode": 1, "stderr": "Error"}), -+ "cmd.run_chroot": MagicMock( -+ return_value={"retcode": 1, "stdout": "", "stderr": "Error"} -+ ), - } - with patch.dict(chroot.__utils__, utils_mock), patch.dict( - chroot.__salt__, salt_mock - ): - self.assertEqual( - chroot.call("/chroot", "test.ping"), -- {"result": False, "comment": "Can't parse container command output"}, -+ { -+ "result": False, -+ "retcode": 1, -+ "comment": {"stdout": "", "stderr": "Error"}, -+ }, - ) - utils_mock["thin.gen_thin"].assert_called_once() - salt_mock["config.option"].assert_called() -diff --git a/tests/unit/modules/test_systemd_service.py b/tests/unit/modules/test_systemd_service.py -index 65ca30e42d..ffaa05efe6 100644 ---- a/tests/unit/modules/test_systemd_service.py -+++ b/tests/unit/modules/test_systemd_service.py -@@ -243,21 +243,27 @@ class SystemdTestCase(TestCase, LoaderModuleMockMixin): - - # systemd < 231 - with patch.dict(systemd.__context__, {"salt.utils.systemd.version": 230}): -- with patch.object(systemd, "_systemctl_status", mock): -+ with patch.object(systemd, "_systemctl_status", mock), patch.object( -+ systemd, "offline", MagicMock(return_value=False) -+ ): - self.assertTrue(systemd.available("sshd.service")) - self.assertFalse(systemd.available("foo.service")) - - # systemd >= 231 - with patch.dict(systemd.__context__, {"salt.utils.systemd.version": 231}): - with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231): -- with patch.object(systemd, "_systemctl_status", mock): -+ with patch.object(systemd, "_systemctl_status", mock), patch.object( -+ systemd, "offline", MagicMock(return_value=False) -+ ): - self.assertTrue(systemd.available("sshd.service")) - self.assertFalse(systemd.available("bar.service")) - - # systemd < 231 with retcode/output changes backported (e.g. RHEL 7.3) - with patch.dict(systemd.__context__, {"salt.utils.systemd.version": 219}): - with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231): -- with patch.object(systemd, "_systemctl_status", mock): -+ with patch.object(systemd, "_systemctl_status", mock), patch.object( -+ systemd, "offline", MagicMock(return_value=False) -+ ): - self.assertTrue(systemd.available("sshd.service")) - self.assertFalse(systemd.available("bar.service")) - -@@ -269,21 +275,27 @@ class SystemdTestCase(TestCase, LoaderModuleMockMixin): - - # systemd < 231 - with patch.dict(systemd.__context__, {"salt.utils.systemd.version": 230}): -- with patch.object(systemd, "_systemctl_status", mock): -+ with patch.object(systemd, "_systemctl_status", mock), patch.object( -+ systemd, "offline", MagicMock(return_value=False) -+ ): - self.assertFalse(systemd.missing("sshd.service")) - self.assertTrue(systemd.missing("foo.service")) - - # systemd >= 231 - with patch.dict(systemd.__context__, {"salt.utils.systemd.version": 231}): - with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231): -- with patch.object(systemd, "_systemctl_status", mock): -+ with patch.object(systemd, "_systemctl_status", mock), patch.object( -+ systemd, "offline", MagicMock(return_value=False) -+ ): - self.assertFalse(systemd.missing("sshd.service")) - self.assertTrue(systemd.missing("bar.service")) - - # systemd < 231 with retcode/output changes backported (e.g. RHEL 7.3) - with patch.dict(systemd.__context__, {"salt.utils.systemd.version": 219}): - with patch.dict(_SYSTEMCTL_STATUS, _SYSTEMCTL_STATUS_GTE_231): -- with patch.object(systemd, "_systemctl_status", mock): -+ with patch.object(systemd, "_systemctl_status", mock), patch.object( -+ systemd, "offline", MagicMock(return_value=False) -+ ): - self.assertFalse(systemd.missing("sshd.service")) - self.assertTrue(systemd.missing("bar.service")) - -- -2.33.0 +2.34.1 diff --git a/switch-firewalld-state-to-use-change_interface.patch b/switch-firewalld-state-to-use-change_interface.patch index c4859d2..6e7a03b 100644 --- a/switch-firewalld-state-to-use-change_interface.patch +++ b/switch-firewalld-state-to-use-change_interface.patch @@ -1,7 +1,6 @@ -From 74d5d84ada50609c60008d3160492c1f4a29d72d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= - -Date: Mon, 20 May 2019 11:59:39 +0100 +From c3e73e4a9e0c81b5dca198fac5c49a1ae91c7111 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 17:12:04 +0100 Subject: [PATCH] Switch firewalld state to use change_interface firewalld.present state allows to bind interface to given zone. @@ -14,296 +13,18 @@ zone to another. This PR adds `firewalld.change_interface` call to firewalld module and updates `firewalld.present` state to use this call. --- - salt/modules/firewalld.py | 100 ++++++++++++++++++++++---------------- - salt/states/firewalld.py | 93 +++++++++++++++++------------------ - 2 files changed, 104 insertions(+), 89 deletions(-) + salt/modules/firewalld.py | 23 +++++++++++++++++++++++ + salt/states/firewalld.py | 4 +++- + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/salt/modules/firewalld.py b/salt/modules/firewalld.py -index ca35568c3f..12808cb038 100644 +index 135713d851..70bc738240 100644 --- a/salt/modules/firewalld.py +++ b/salt/modules/firewalld.py -@@ -1,19 +1,14 @@ --# -*- coding: utf-8 -*- - """ - Support for firewalld. - - .. versionadded:: 2015.2.0 - """ - --# Import Python Libs --from __future__ import absolute_import, print_function, unicode_literals - - import logging - import re - - import salt.utils.path -- --# Import Salt Libs - from salt.exceptions import CommandExecutionError - - log = logging.getLogger(__name__) -@@ -36,7 +31,7 @@ def __firewall_cmd(cmd): - """ - Return the firewall-cmd location - """ -- firewall_cmd = "{0} {1}".format(salt.utils.path.which("firewall-cmd"), cmd) -+ firewall_cmd = "{} {}".format(salt.utils.path.which("firewall-cmd"), cmd) - out = __salt__["cmd.run_all"](firewall_cmd) - - if out["retcode"] != 0: -@@ -44,7 +39,7 @@ def __firewall_cmd(cmd): - msg = out["stdout"] - else: - msg = out["stderr"] -- raise CommandExecutionError("firewall-cmd failed: {0}".format(msg)) -+ raise CommandExecutionError("firewall-cmd failed: {}".format(msg)) - return out["stdout"] - - -@@ -53,7 +48,7 @@ def __mgmt(name, _type, action): - Perform zone management - """ - # It's permanent because the 4 concerned functions need the permanent option, it's wrong without -- cmd = "--{0}-{1}={2} --permanent".format(action, _type, name) -+ cmd = "--{}-{}={} --permanent".format(action, _type, name) - - return __firewall_cmd(cmd) - -@@ -250,7 +245,7 @@ def set_default_zone(zone): - - salt '*' firewalld.set_default_zone damian - """ -- return __firewall_cmd("--set-default-zone={0}".format(zone)) -+ return __firewall_cmd("--set-default-zone={}".format(zone)) - - - def new_service(name, restart=True): -@@ -327,7 +322,7 @@ def list_all(zone=None, permanent=True): - id_ = "" - - if zone: -- cmd = "--zone={0} --list-all".format(zone) -+ cmd = "--zone={} --list-all".format(zone) - else: - cmd = "--list-all" - -@@ -372,7 +367,7 @@ def list_services(zone=None, permanent=True): - salt '*' firewalld.list_services my_zone - """ - if zone: -- cmd = "--zone={0} --list-services".format(zone) -+ cmd = "--zone={} --list-services".format(zone) - else: - cmd = "--list-services" - -@@ -399,9 +394,9 @@ def add_service(service, zone=None, permanent=True): - salt '*' firewalld.add_service ssh my_zone - """ - if zone: -- cmd = "--zone={0} --add-service={1}".format(zone, service) -+ cmd = "--zone={} --add-service={}".format(zone, service) - else: -- cmd = "--add-service={0}".format(service) -+ cmd = "--add-service={}".format(service) - - if permanent: - cmd += " --permanent" -@@ -427,9 +422,9 @@ def remove_service(service, zone=None, permanent=True): - salt '*' firewalld.remove_service ssh dmz - """ - if zone: -- cmd = "--zone={0} --remove-service={1}".format(zone, service) -+ cmd = "--zone={} --remove-service={}".format(zone, service) - else: -- cmd = "--remove-service={0}".format(service) -+ cmd = "--remove-service={}".format(service) - - if permanent: - cmd += " --permanent" -@@ -452,7 +447,7 @@ def add_service_port(service, port): - if service not in get_services(permanent=True): - raise CommandExecutionError("The service does not exist.") - -- cmd = "--permanent --service={0} --add-port={1}".format(service, port) -+ cmd = "--permanent --service={} --add-port={}".format(service, port) +@@ -918,6 +918,29 @@ def remove_interface(zone, interface, permanent=True): return __firewall_cmd(cmd) -@@ -471,7 +466,7 @@ def remove_service_port(service, port): - if service not in get_services(permanent=True): - raise CommandExecutionError("The service does not exist.") - -- cmd = "--permanent --service={0} --remove-port={1}".format(service, port) -+ cmd = "--permanent --service={} --remove-port={}".format(service, port) - return __firewall_cmd(cmd) - - -@@ -487,7 +482,7 @@ def get_service_ports(service): - - salt '*' firewalld.get_service_ports zone - """ -- cmd = "--permanent --service={0} --get-ports".format(service) -+ cmd = "--permanent --service={} --get-ports".format(service) - return __firewall_cmd(cmd).split() - - -@@ -503,7 +498,7 @@ def add_service_protocol(service, protocol): - - salt '*' firewalld.add_service_protocol zone ssh - """ -- cmd = "--permanent --service={0} --add-protocol={1}".format(service, protocol) -+ cmd = "--permanent --service={} --add-protocol={}".format(service, protocol) - return __firewall_cmd(cmd) - - -@@ -519,7 +514,7 @@ def remove_service_protocol(service, protocol): - - salt '*' firewalld.remove_service_protocol zone ssh - """ -- cmd = "--permanent --service={0} --remove-protocol={1}".format(service, protocol) -+ cmd = "--permanent --service={} --remove-protocol={}".format(service, protocol) - return __firewall_cmd(cmd) - - -@@ -535,7 +530,7 @@ def get_service_protocols(service): - - salt '*' firewalld.get_service_protocols zone - """ -- cmd = "--permanent --service={0} --get-protocols".format(service) -+ cmd = "--permanent --service={} --get-protocols".format(service) - return __firewall_cmd(cmd).split() - - -@@ -578,7 +573,7 @@ def add_masquerade(zone=None, permanent=True): - salt '*' firewalld.add_masquerade dmz - """ - if zone: -- cmd = "--zone={0} --add-masquerade".format(zone) -+ cmd = "--zone={} --add-masquerade".format(zone) - else: - cmd = "--add-masquerade" - -@@ -608,7 +603,7 @@ def remove_masquerade(zone=None, permanent=True): - salt '*' firewalld.remove_masquerade dmz - """ - if zone: -- cmd = "--zone={0} --remove-masquerade".format(zone) -+ cmd = "--zone={} --remove-masquerade".format(zone) - else: - cmd = "--remove-masquerade" - -@@ -637,7 +632,7 @@ def add_port(zone, port, permanent=True, force_masquerade=False): - if force_masquerade and not get_masquerade(zone): - add_masquerade(zone) - -- cmd = "--zone={0} --add-port={1}".format(zone, port) -+ cmd = "--zone={} --add-port={}".format(zone, port) - - if permanent: - cmd += " --permanent" -@@ -657,7 +652,7 @@ def remove_port(zone, port, permanent=True): - - salt '*' firewalld.remove_port internal 443/tcp - """ -- cmd = "--zone={0} --remove-port={1}".format(zone, port) -+ cmd = "--zone={} --remove-port={}".format(zone, port) - - if permanent: - cmd += " --permanent" -@@ -677,7 +672,7 @@ def list_ports(zone, permanent=True): - - salt '*' firewalld.list_ports - """ -- cmd = "--zone={0} --list-ports".format(zone) -+ cmd = "--zone={} --list-ports".format(zone) - - if permanent: - cmd += " --permanent" -@@ -706,7 +701,7 @@ def add_port_fwd( - if force_masquerade and not get_masquerade(zone): - add_masquerade(zone) - -- cmd = "--zone={0} --add-forward-port=port={1}:proto={2}:toport={3}:toaddr={4}".format( -+ cmd = "--zone={} --add-forward-port=port={}:proto={}:toport={}:toaddr={}".format( - zone, src, proto, dest, dstaddr - ) - -@@ -728,7 +723,7 @@ def remove_port_fwd(zone, src, dest, proto="tcp", dstaddr="", permanent=True): - - salt '*' firewalld.remove_port_fwd public 80 443 tcp - """ -- cmd = "--zone={0} --remove-forward-port=port={1}:proto={2}:toport={3}:toaddr={4}".format( -+ cmd = "--zone={} --remove-forward-port=port={}:proto={}:toport={}:toaddr={}".format( - zone, src, proto, dest, dstaddr - ) - -@@ -752,7 +747,7 @@ def list_port_fwd(zone, permanent=True): - """ - ret = [] - -- cmd = "--zone={0} --list-forward-ports".format(zone) -+ cmd = "--zone={} --list-forward-ports".format(zone) - - if permanent: - cmd += " --permanent" -@@ -792,7 +787,7 @@ def block_icmp(zone, icmp, permanent=True): - log.info("ICMP block already exists") - return "success" - -- cmd = "--zone={0} --add-icmp-block={1}".format(zone, icmp) -+ cmd = "--zone={} --add-icmp-block={}".format(zone, icmp) - - if permanent: - cmd += " --permanent" -@@ -820,7 +815,7 @@ def allow_icmp(zone, icmp, permanent=True): - log.info("ICMP Type is already permitted") - return "success" - -- cmd = "--zone={0} --remove-icmp-block={1}".format(zone, icmp) -+ cmd = "--zone={} --remove-icmp-block={}".format(zone, icmp) - - if permanent: - cmd += " --permanent" -@@ -840,7 +835,7 @@ def list_icmp_block(zone, permanent=True): - - salt '*' firewlld.list_icmp_block zone - """ -- cmd = "--zone={0} --list-icmp-blocks".format(zone) -+ cmd = "--zone={} --list-icmp-blocks".format(zone) - - if permanent: - cmd += " --permanent" -@@ -875,7 +870,7 @@ def get_interfaces(zone, permanent=True): - - salt '*' firewalld.get_interfaces zone - """ -- cmd = "--zone={0} --list-interfaces".format(zone) -+ cmd = "--zone={} --list-interfaces".format(zone) - - if permanent: - cmd += " --permanent" -@@ -898,7 +893,7 @@ def add_interface(zone, interface, permanent=True): - if interface in get_interfaces(zone, permanent): - log.info("Interface is already bound to zone.") - -- cmd = "--zone={0} --add-interface={1}".format(zone, interface) -+ cmd = "--zone={} --add-interface={}".format(zone, interface) - - if permanent: - cmd += " --permanent" -@@ -921,7 +916,30 @@ def remove_interface(zone, interface, permanent=True): - if interface not in get_interfaces(zone, permanent): - log.info("Interface is not bound to zone.") - -- cmd = "--zone={0} --remove-interface={1}".format(zone, interface) -+ cmd = "--zone={} --remove-interface={}".format(zone, interface) -+ -+ if permanent: -+ cmd += " --permanent" -+ -+ return __firewall_cmd(cmd) -+ -+ +def change_interface(zone, interface, permanent=True): + """ + Change zone the interface bound to @@ -320,355 +41,21 @@ index ca35568c3f..12808cb038 100644 + log.info("Interface is already bound to zone.") + + cmd = "--zone={} --change-interface={}".format(zone, interface) - - if permanent: - cmd += " --permanent" -@@ -941,7 +959,7 @@ def get_sources(zone, permanent=True): - - salt '*' firewalld.get_sources zone ++ ++ if permanent: ++ cmd += " --permanent" ++ ++ return __firewall_cmd(cmd) ++ ++ + def get_sources(zone, permanent=True): """ -- cmd = "--zone={0} --list-sources".format(zone) -+ cmd = "--zone={} --list-sources".format(zone) - - if permanent: - cmd += " --permanent" -@@ -964,7 +982,7 @@ def add_source(zone, source, permanent=True): - if source in get_sources(zone, permanent): - log.info("Source is already bound to zone.") - -- cmd = "--zone={0} --add-source={1}".format(zone, source) -+ cmd = "--zone={} --add-source={}".format(zone, source) - - if permanent: - cmd += " --permanent" -@@ -987,7 +1005,7 @@ def remove_source(zone, source, permanent=True): - if source not in get_sources(zone, permanent): - log.info("Source is not bound to zone.") - -- cmd = "--zone={0} --remove-source={1}".format(zone, source) -+ cmd = "--zone={} --remove-source={}".format(zone, source) - - if permanent: - cmd += " --permanent" -@@ -1007,7 +1025,7 @@ def get_rich_rules(zone, permanent=True): - - salt '*' firewalld.get_rich_rules zone - """ -- cmd = "--zone={0} --list-rich-rules".format(zone) -+ cmd = "--zone={} --list-rich-rules".format(zone) - - if permanent: - cmd += " --permanent" -@@ -1027,7 +1045,7 @@ def add_rich_rule(zone, rule, permanent=True): - - salt '*' firewalld.add_rich_rule zone 'rule' - """ -- cmd = "--zone={0} --add-rich-rule='{1}'".format(zone, rule) -+ cmd = "--zone={} --add-rich-rule='{}'".format(zone, rule) - - if permanent: - cmd += " --permanent" -@@ -1047,7 +1065,7 @@ def remove_rich_rule(zone, rule, permanent=True): - - salt '*' firewalld.remove_rich_rule zone 'rule' - """ -- cmd = "--zone={0} --remove-rich-rule='{1}'".format(zone, rule) -+ cmd = "--zone={} --remove-rich-rule='{}'".format(zone, rule) - - if permanent: - cmd += " --permanent" + List sources bound to a zone diff --git a/salt/states/firewalld.py b/salt/states/firewalld.py -index 4114e99f43..425defcfb1 100644 +index cc6eaba5c3..534b9dd62d 100644 --- a/salt/states/firewalld.py +++ b/salt/states/firewalld.py -@@ -1,4 +1,3 @@ --# -*- coding: utf-8 -*- - """ - Management of firewalld - -@@ -76,21 +75,17 @@ would allow access to the salt master from the 10.0.0.0/8 subnet: - - 10.0.0.0/8 - """ - --# Import Python Libs --from __future__ import absolute_import, print_function, unicode_literals - - import logging - - import salt.utils.path -- --# Import Salt Libs - from salt.exceptions import CommandExecutionError - from salt.output import nested - - log = logging.getLogger(__name__) - - --class ForwardingMapping(object): -+class ForwardingMapping: - """ - Represents a port forwarding statement mapping a local port to a remote - port for a specific protocol (TCP or UDP) -@@ -282,7 +277,7 @@ def service(name, ports=None, protocols=None): - try: - _current_ports = __salt__["firewalld.get_service_ports"](name) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_ports = set(ports) - set(_current_ports) -@@ -293,7 +288,7 @@ def service(name, ports=None, protocols=None): - try: - __salt__["firewalld.add_service_port"](name, port) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - for port in old_ports: -@@ -301,7 +296,7 @@ def service(name, ports=None, protocols=None): - try: - __salt__["firewalld.remove_service_port"](name, port) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_ports or old_ports: -@@ -312,7 +307,7 @@ def service(name, ports=None, protocols=None): - try: - _current_protocols = __salt__["firewalld.get_service_protocols"](name) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_protocols = set(protocols) - set(_current_protocols) -@@ -323,7 +318,7 @@ def service(name, ports=None, protocols=None): - try: - __salt__["firewalld.add_service_protocol"](name, protocol) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - for protocol in old_protocols: -@@ -331,7 +326,7 @@ def service(name, ports=None, protocols=None): - try: - __salt__["firewalld.remove_service_protocol"](name, protocol) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_protocols or old_protocols: -@@ -344,15 +339,15 @@ def service(name, ports=None, protocols=None): - - ret["result"] = True - if ret["changes"] == {}: -- ret["comment"] = "'{0}' is already in the desired state.".format(name) -+ ret["comment"] = "'{}' is already in the desired state.".format(name) - return ret - - if __opts__["test"]: - ret["result"] = None -- ret["comment"] = "Configuration for '{0}' will change.".format(name) -+ ret["comment"] = "Configuration for '{}' will change.".format(name) - return ret - -- ret["comment"] = "'{0}' was configured.".format(name) -+ ret["comment"] = "'{}' was configured.".format(name) - return ret - - -@@ -385,7 +380,7 @@ def _present( - try: - zones = __salt__["firewalld.get_zones"](permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if name not in zones: -@@ -393,7 +388,7 @@ def _present( - try: - __salt__["firewalld.new_zone"](name) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - ret["changes"].update({name: {"old": zones, "new": name}}) -@@ -408,14 +403,14 @@ def _present( - name, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if block_icmp: - try: - _valid_icmp_types = __salt__["firewalld.get_icmp_types"](permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - # log errors for invalid ICMP types in block_icmp input -@@ -431,7 +426,7 @@ def _present( - name, icmp_type, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if prune_block_icmp: -@@ -446,7 +441,7 @@ def _present( - name, icmp_type, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_icmp_types or old_icmp_types: -@@ -464,21 +459,21 @@ def _present( - try: - default_zone = __salt__["firewalld.default_zone"]() - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - if name != default_zone: - if not __opts__["test"]: - try: - __salt__["firewalld.set_default_zone"](name) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - ret["changes"].update({"default": {"old": default_zone, "new": name}}) - - try: - masquerade_ret = __salt__["firewalld.get_masquerade"](name, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if masquerade and not masquerade_ret: -@@ -486,7 +481,7 @@ def _present( - try: - __salt__["firewalld.add_masquerade"](name, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - ret["changes"].update( - {"masquerade": {"old": "", "new": "Masquerading successfully set."}} -@@ -496,7 +491,7 @@ def _present( - try: - __salt__["firewalld.remove_masquerade"](name, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - ret["changes"].update( - {"masquerade": {"old": "", "new": "Masquerading successfully " "disabled."}} -@@ -507,7 +502,7 @@ def _present( - try: - _current_ports = __salt__["firewalld.list_ports"](name, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_ports = set(ports) - set(_current_ports) -@@ -520,7 +515,7 @@ def _present( - name, port, permanent=True, force_masquerade=False - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if prune_ports: -@@ -530,7 +525,7 @@ def _present( - try: - __salt__["firewalld.remove_port"](name, port, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_ports or old_ports: -@@ -547,7 +542,7 @@ def _present( - name, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - port_fwd = [_parse_forward(fwd) for fwd in port_fwd] -@@ -577,7 +572,7 @@ def _present( - force_masquerade=False, - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if prune_port_fwd: -@@ -594,7 +589,7 @@ def _present( - permanent=True, - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_port_fwd or old_port_fwd: -@@ -618,7 +613,7 @@ def _present( - name, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_services = set(services) - set(_current_services) -@@ -629,7 +624,7 @@ def _present( - try: - __salt__["firewalld.add_service"](new_service, name, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if prune_services: -@@ -641,7 +636,7 @@ def _present( - old_service, name, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_services or old_services: -@@ -660,7 +655,7 @@ def _present( - name, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_interfaces = set(interfaces) - set(_current_interfaces) -@@ -669,9 +664,11 @@ def _present( +@@ -691,7 +691,9 @@ def _present( for interface in new_interfaces: if not __opts__["test"]: try: @@ -677,100 +64,9 @@ index 4114e99f43..425defcfb1 100644 + name, interface, permanent=True + ) except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) + ret["comment"] = "Error: {}".format(err) return ret - - if prune_interfaces: -@@ -683,7 +680,7 @@ def _present( - name, interface, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_interfaces or old_interfaces: -@@ -700,7 +697,7 @@ def _present( - try: - _current_sources = __salt__["firewalld.get_sources"](name, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_sources = set(sources) - set(_current_sources) -@@ -711,7 +708,7 @@ def _present( - try: - __salt__["firewalld.add_source"](name, source, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if prune_sources: -@@ -723,7 +720,7 @@ def _present( - name, source, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_sources or old_sources: -@@ -742,7 +739,7 @@ def _present( - name, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - new_rich_rules = set(rich_rules) - set(_current_rich_rules) -@@ -753,7 +750,7 @@ def _present( - try: - __salt__["firewalld.add_rich_rule"](name, rich_rule, permanent=True) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if prune_rich_rules: -@@ -765,7 +762,7 @@ def _present( - name, rich_rule, permanent=True - ) - except CommandExecutionError as err: -- ret["comment"] = "Error: {0}".format(err) -+ ret["comment"] = "Error: {}".format(err) - return ret - - if new_rich_rules or old_rich_rules: -@@ -780,7 +777,7 @@ def _present( - # No changes - if ret["changes"] == {}: - ret["result"] = True -- ret["comment"] = "'{0}' is already in the desired state.".format(name) -+ ret["comment"] = "'{}' is already in the desired state.".format(name) - return ret - - # test=True and changes predicted -@@ -789,7 +786,7 @@ def _present( - # build comment string - nested.__opts__ = __opts__ - comment = [] -- comment.append("Configuration for '{0}' will change:".format(name)) -+ comment.append("Configuration for '{}' will change:".format(name)) - comment.append(nested.output(ret["changes"]).rstrip()) - ret["comment"] = "\n".join(comment) - ret["changes"] = {} -@@ -797,5 +794,5 @@ def _present( - - # Changes were made successfully - ret["result"] = True -- ret["comment"] = "'{0}' was configured.".format(name) -+ ret["comment"] = "'{}' was configured.".format(name) - return ret -- -2.29.2 +2.34.1 diff --git a/templates-move-the-globals-up-to-the-environment-jin.patch b/templates-move-the-globals-up-to-the-environment-jin.patch deleted file mode 100644 index 2b1dbf1..0000000 --- a/templates-move-the-globals-up-to-the-environment-jin.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 4664c4b31fdef9ef5b3987b9d993ccba2675d18f Mon Sep 17 00:00:00 2001 -From: Alberto Planas -Date: Tue, 31 Aug 2021 11:20:49 +0200 -Subject: [PATCH] templates: move the globals up to the Environment - (Jinja2 3.0.0) (#418) - -* jinja: fix TemplateNotFound missing name - -The TemplateNotFound exception requires a parameter, name, that is -missing in one of the calls. - -File "/usr/lib/python3.8/site-packages/salt/utils/jinja.py", line 158, in get_source - raise TemplateNotFound -TypeError: __init__() missing 1 required positional argument: 'name' - -This patch add the missing parameter in the raise call. - -Signed-off-by: Alberto Planas - -* templates: move the globals up to the Environment - -When creating a Jinja2 environment, we populate the globals in the -Template object that we generate from the environment. This cause a -problem when there is a {% include "./file.sls" %} in the template, as -cannot find in the environment globals information like the "tpldir", -for example, making the relative path to be unresolved. - -Seems that in Jinja2 2.X this behaviour is not present, so attaching the -globals to the Template will make the include to work, but since Jinja2 -3.0.0 this is not the case. Maybe related with the re-architecture from -https://github.com/pallets/jinja/issues/295 - -This patch populate the globals in the Environment level, making this -and other variables reachable by the Jinja templates. - -Fix #55159 - -Signed-off-by: Alberto Planas ---- - changelog/55159.fixed | 1 + - salt/utils/jinja.py | 2 +- - salt/utils/templates.py | 3 ++- - tests/unit/utils/test_jinja.py | 16 ++++++++++++++++ - 4 files changed, 20 insertions(+), 2 deletions(-) - create mode 100644 changelog/55159.fixed - -diff --git a/changelog/55159.fixed b/changelog/55159.fixed -new file mode 100644 -index 0000000000..6ee1a78366 ---- /dev/null -+++ b/changelog/55159.fixed -@@ -0,0 +1 @@ -+Jinja renderer resolves wrong relative paths when importing subdirectories -diff --git a/salt/utils/jinja.py b/salt/utils/jinja.py -index 68a8646474..3f27c2c7e1 100644 ---- a/salt/utils/jinja.py -+++ b/salt/utils/jinja.py -@@ -148,7 +148,7 @@ class SaltCacheLoader(BaseLoader): - 'Relative path "%s" cannot be resolved without an environment', - template, - ) -- raise TemplateNotFound -+ raise TemplateNotFound(template) - base_path = environment.globals["tpldir"] - _template = os.path.normpath("/".join((base_path, _template))) - if _template.split("/", 1)[0] == "..": -diff --git a/salt/utils/templates.py b/salt/utils/templates.py -index 0f705cfa37..4c59553bc8 100644 ---- a/salt/utils/templates.py -+++ b/salt/utils/templates.py -@@ -494,9 +494,10 @@ def render_jinja_tmpl(tmplstr, context, tmplpath=None): - SLS_ENCODING, - ) - decoded_context[key] = salt.utils.data.decode(value) -+ -+ jinja_env.globals.update(decoded_context) - try: - template = jinja_env.from_string(tmplstr) -- template.globals.update(decoded_context) - output = template.render(**decoded_context) - except jinja2.exceptions.UndefinedError as exc: - trace = traceback.extract_tb(sys.exc_info()[2]) -diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py -index fb67e17fa2..e23caf11ae 100644 ---- a/tests/unit/utils/test_jinja.py -+++ b/tests/unit/utils/test_jinja.py -@@ -619,6 +619,22 @@ class TestGetTemplate(TestCase): - dict(opts=self.local_opts, saltenv="test", salt=self.local_salt), - ) - -+ def test_relative_include(self): -+ template = "{% include './hello_import' %}" -+ expected = "Hey world !a b !" -+ filename = os.path.join(self.template_dir, "hello_import") -+ with salt.utils.files.fopen(filename) as fp_: -+ out = render_jinja_tmpl( -+ template, -+ dict( -+ opts=self.local_opts, -+ saltenv="test", -+ salt=self.local_salt, -+ tpldir=self.template_dir, -+ ), -+ ) -+ self.assertEqual(out, expected) -+ - - class TestJinjaDefaultOptions(TestCase): - @classmethod --- -2.33.0 - - diff --git a/update-target-fix-for-salt-ssh-to-process-targets-li.patch b/update-target-fix-for-salt-ssh-to-process-targets-li.patch index 9edff3f..d5bfd41 100644 --- a/update-target-fix-for-salt-ssh-to-process-targets-li.patch +++ b/update-target-fix-for-salt-ssh-to-process-targets-li.patch @@ -1,4 +1,4 @@ -From eb51c650e39359851588b611a0802d06eedeb5b5 Mon Sep 17 00:00:00 2001 +From 74d3d43d09c692ba41138278f34d2a2e2ef83dd8 Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> Date: Fri, 9 Apr 2021 16:01:32 +0300 Subject: [PATCH] Update target fix for salt-ssh to process targets list @@ -14,10 +14,10 @@ Regression fix of salt-ssh on processing targets (#353) 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py -index a4a74aa2c2..409d6e740e 100644 +index 564d622ab5..37faa869bc 100644 --- a/salt/client/ssh/__init__.py +++ b/salt/client/ssh/__init__.py -@@ -325,7 +325,7 @@ class SSH: +@@ -327,7 +327,7 @@ class SSH: if not self.opts.get("ssh_cli_tgt"): self.opts["ssh_cli_tgt"] = self.opts.get("tgt", "") hostname = self.opts.get("ssh_cli_tgt", "") @@ -26,7 +26,7 @@ index a4a74aa2c2..409d6e740e 100644 user, hostname = hostname.split("@", 1) else: user = self.opts.get("ssh_user") -@@ -376,7 +376,7 @@ class SSH: +@@ -378,7 +378,7 @@ class SSH: self.__parsed_rosters[self.ROSTER_UPDATE_FLAG] = False return @@ -35,8 +35,8 @@ index a4a74aa2c2..409d6e740e 100644 """ Update default flat roster with the passed in information. :return: -@@ -391,8 +391,8 @@ class SSH: - "\n passwd: {passwd}\n".format( +@@ -392,8 +392,8 @@ class SSH: + " host: {hostname}\n user: {user}\n passwd: {passwd}\n".format( s_user=getpass.getuser(), s_time=datetime.datetime.utcnow().isoformat(), - hostname=self.opts.get("tgt", ""), @@ -46,7 +46,7 @@ index a4a74aa2c2..409d6e740e 100644 passwd=self.opts.get("ssh_passwd", ""), ) ) -@@ -409,20 +409,32 @@ class SSH: +@@ -410,20 +410,32 @@ class SSH: Uptade targets in case hostname was directly passed without the roster. :return: """ @@ -93,6 +93,6 @@ index a4a74aa2c2..409d6e740e 100644 def get_pubkey(self): """ -- -2.33.0 +2.34.1 diff --git a/use-adler32-algorithm-to-compute-string-checksums.patch b/use-adler32-algorithm-to-compute-string-checksums.patch index 866b51f..546e45f 100644 --- a/use-adler32-algorithm-to-compute-string-checksums.patch +++ b/use-adler32-algorithm-to-compute-string-checksums.patch @@ -1,6 +1,6 @@ -From db2573bebc90f6e1b445c34ddd707ad547c4325a Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Sat, 28 Jul 2018 22:59:04 +0200 +From 466b188b52b064cbdda6cf3efa73da3861be8307 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 16:36:57 +0100 Subject: [PATCH] Use Adler32 algorithm to compute string checksums Generate the same numeric value across all Python versions and platforms @@ -20,24 +20,24 @@ Move server_id deprecation warning to reduce log spamming (bsc#1135567) (bsc#113 Remove deprecated warning that breaks miniion execution when "server_id_use_crc" opts are missing --- salt/config/__init__.py | 4 ++++ - salt/grains/core.py | 49 +++++++++++++++++++++++++++++++++++++---- - 2 files changed, 49 insertions(+), 4 deletions(-) + salt/grains/core.py | 48 +++++++++++++++++++++++++++++++++++++---- + 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/salt/config/__init__.py b/salt/config/__init__.py -index e63489cbce..e05cdfafda 100644 +index 97a7fce2f0..2c42290598 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py -@@ -953,6 +953,9 @@ VALID_OPTS = immutabletypes.freeze( - # Feature flag config - "features": dict, - "fips_mode": bool, +@@ -957,6 +957,9 @@ VALID_OPTS = immutabletypes.freeze( + # The port to be used when checking if a master is connected to a + # minion + "remote_minions_port": int, + # Use Adler32 hashing algorithm for server_id (default False until Sodium, "adler32" after) + # Possible values are: False, adler32, crc32 + "server_id_use_crc": (bool, str), } ) -@@ -1256,6 +1259,7 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze( +@@ -1260,6 +1263,7 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze( "disabled_requisites": [], "reactor_niceness": None, "fips_mode": False, @@ -46,7 +46,7 @@ index e63489cbce..e05cdfafda 100644 ) diff --git a/salt/grains/core.py b/salt/grains/core.py -index fdf0a6f0d8..e007f40c92 100644 +index 1077e64a11..dd0bd346b2 100644 --- a/salt/grains/core.py +++ b/salt/grains/core.py @@ -21,6 +21,7 @@ import subprocess @@ -57,15 +57,7 @@ index fdf0a6f0d8..e007f40c92 100644 from errno import EACCES, EPERM import distro -@@ -40,6 +41,7 @@ import salt.utils.path - import salt.utils.pkg.rpm - import salt.utils.platform - import salt.utils.stringutils -+import salt.utils.versions - from salt.ext.six.moves import range - from salt.utils.network import _get_interfaces - -@@ -2981,6 +2983,36 @@ def _hw_data(osdata): +@@ -3017,6 +3018,36 @@ def _hw_data(osdata): return grains @@ -102,7 +94,7 @@ index fdf0a6f0d8..e007f40c92 100644 def get_server_id(): """ Provides an integer based on the FQDN of a machine. -@@ -2991,10 +3023,19 @@ def get_server_id(): +@@ -3027,10 +3058,19 @@ def get_server_id(): # server_id if salt.utils.platform.is_proxy(): @@ -127,6 +119,6 @@ index fdf0a6f0d8..e007f40c92 100644 def get_master(): -- -2.33.0 +2.34.1 diff --git a/v3003.3.tar.gz b/v3003.3.tar.gz deleted file mode 100644 index 00d7330..0000000 --- a/v3003.3.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0cbc8ae5ac9f075d430df256be438ab3a3f34082f1afab41df12fc34f04be413 -size 16093920 diff --git a/v3004.tar.gz b/v3004.tar.gz new file mode 100644 index 0000000..2129d86 --- /dev/null +++ b/v3004.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fad2322cfef1a28351ef2cb645fcf7441502864326b63bb20d54d9bde97cf565 +size 17761294 diff --git a/virt-enhancements.patch b/virt-enhancements.patch deleted file mode 100644 index 48f883b..0000000 --- a/virt-enhancements.patch +++ /dev/null @@ -1,1602 +0,0 @@ -From 340570af70f48b2e97a2e0b863fd819f85e92cb7 Mon Sep 17 00:00:00 2001 -From: Cedric Bosdonnat -Date: Mon, 8 Feb 2021 16:42:47 +0100 -Subject: [PATCH] virt enhancements - -3002.2 Xen spicevmc, DNS SRV records backports (#314) - -* Fix virtual network generated DNS XML for SRV records - -libvirt network's srv element doesn't take a `name` property but a -`service` one. - -* Add missing property in virt.network_define dns srv doc - -* virt: convert spice generation tests to pytests - -* virt: don't add spicevmc channel to Xen VMs - -Xen fully virtualized VMs with spicevmc channel fail to start, so better -not write it out in such cases. - -* virt: inverse the remaining asserts in pytests - -virt.network_update: handle missing ipv4 netmask attribute (#328) - -In the libvirt definition, the IPv4 netmask XML attribute may be -replaced by the prefix one. Handle this situation gracefully rather than -miserably failing. - -Handle volumes on stopped pools in virt.vm_info (#373) - -For VMs having at least a disk on a stopped volume, we don't want the -user to get an exception when running virt.vm_info. Instead just provide -less information. - -virt: use /dev/kvm to detect KVM (#383) - -checking for kvm_* modules to be loaded is not robust enough since the -kernel could be compiled with builtin modules. /dev/kvm is much more -reliable. - -virt: pass emulator when getting domain capabilities from libvirt (#394) - -On aarch64, for some emulated architectures like armv6l libvirt needs to -have the emulator path to properly return the domain capabilities. - -Passing it will avoid virt.all_capabilities to fail on such -architectures. ---- - changelog/59416.fixed | 1 + - changelog/59692.fixed | 1 + - changelog/60132.fixed | 1 + - changelog/60419.fixed | 1 + - changelog/60491.fixed | 1 + - salt/modules/virt.py | 92 +++++---- - salt/templates/virt/libvirt_domain.jinja | 2 +- - .../pytests/unit/modules/virt/test_domain.py | 185 +++++++++++++----- - .../pytests/unit/modules/virt/test_helpers.py | 6 +- - tests/pytests/unit/modules/virt/test_host.py | 23 ++- - .../pytests/unit/modules/virt/test_network.py | 70 ++++--- - tests/pytests/unit/states/virt/test_domain.py | 144 +++++++------- - .../pytests/unit/states/virt/test_network.py | 88 ++++----- - tests/unit/modules/test_virt.py | 63 +----- - 14 files changed, 371 insertions(+), 307 deletions(-) - create mode 100644 changelog/59416.fixed - create mode 100644 changelog/59692.fixed - create mode 100644 changelog/60132.fixed - create mode 100644 changelog/60419.fixed - create mode 100644 changelog/60491.fixed - -diff --git a/changelog/59416.fixed b/changelog/59416.fixed -new file mode 100644 -index 0000000000..820124e99a ---- /dev/null -+++ b/changelog/59416.fixed -@@ -0,0 +1 @@ -+Don't create spicevmc channel for Xen virtual machines -diff --git a/changelog/59692.fixed b/changelog/59692.fixed -new file mode 100644 -index 0000000000..b4f4533ccc ---- /dev/null -+++ b/changelog/59692.fixed -@@ -0,0 +1 @@ -+Don't fail updating network without netmask ip attribute -diff --git a/changelog/60132.fixed b/changelog/60132.fixed -new file mode 100644 -index 0000000000..1e3bc96b98 ---- /dev/null -+++ b/changelog/60132.fixed -@@ -0,0 +1 @@ -+Gracefuly handle errors in virt.vm_info -diff --git a/changelog/60419.fixed b/changelog/60419.fixed -new file mode 100644 -index 0000000000..44c782da48 ---- /dev/null -+++ b/changelog/60419.fixed -@@ -0,0 +1 @@ -+Check for /dev/kvm to detect KVM hypervisor. -diff --git a/changelog/60491.fixed b/changelog/60491.fixed -new file mode 100644 -index 0000000000..256d29b5fb ---- /dev/null -+++ b/changelog/60491.fixed -@@ -0,0 +1 @@ -+Pass emulator path to get guest capabilities from libvirt -diff --git a/salt/modules/virt.py b/salt/modules/virt.py -index 17e3ba7b9b..8c0d743ba1 100644 ---- a/salt/modules/virt.py -+++ b/salt/modules/virt.py -@@ -516,41 +516,50 @@ def _get_disks(conn, dom): - def _get_disk_volume_data(pool_name, volume_name): - qemu_target = "{}/{}".format(pool_name, volume_name) - pool = conn.storagePoolLookupByName(pool_name) -- vol = pool.storageVolLookupByName(volume_name) -- vol_info = vol.info() -- extra_properties = { -- "virtual size": vol_info[1], -- "disk size": vol_info[2], -- } -- -- backing_files = [ -- { -- "file": node.find("source").get("file"), -- "file format": node.find("format").get("type"), -+ extra_properties = {} -+ try: -+ vol = pool.storageVolLookupByName(volume_name) -+ vol_info = vol.info() -+ extra_properties = { -+ "virtual size": vol_info[1], -+ "disk size": vol_info[2], - } -- for node in elem.findall(".//backingStore[source]") -- ] - -- if backing_files: -- # We had the backing files in a flat list, nest them again. -- extra_properties["backing file"] = backing_files[0] -- parent = extra_properties["backing file"] -- for sub_backing_file in backing_files[1:]: -- parent["backing file"] = sub_backing_file -- parent = sub_backing_file -+ backing_files = [ -+ { -+ "file": node.find("source").get("file"), -+ "file format": node.find("format").get("type"), -+ } -+ for node in elem.findall(".//backingStore[source]") -+ ] - -- else: -- # In some cases the backing chain is not displayed by the domain definition -- # Try to see if we have some of it in the volume definition. -- vol_desc = ElementTree.fromstring(vol.XMLDesc()) -- backing_path = vol_desc.find("./backingStore/path") -- backing_format = vol_desc.find("./backingStore/format") -- if backing_path is not None: -- extra_properties["backing file"] = {"file": backing_path.text} -- if backing_format is not None: -- extra_properties["backing file"][ -- "file format" -- ] = backing_format.get("type") -+ if backing_files: -+ # We had the backing files in a flat list, nest them again. -+ extra_properties["backing file"] = backing_files[0] -+ parent = extra_properties["backing file"] -+ for sub_backing_file in backing_files[1:]: -+ parent["backing file"] = sub_backing_file -+ parent = sub_backing_file -+ -+ else: -+ # In some cases the backing chain is not displayed by the domain definition -+ # Try to see if we have some of it in the volume definition. -+ vol_desc = ElementTree.fromstring(vol.XMLDesc()) -+ backing_path = vol_desc.find("./backingStore/path") -+ backing_format = vol_desc.find("./backingStore/format") -+ if backing_path is not None: -+ extra_properties["backing file"] = { -+ "file": backing_path.text -+ } -+ if backing_format is not None: -+ extra_properties["backing file"][ -+ "file format" -+ ] = backing_format.get("type") -+ except libvirt.libvirtError: -+ # The volume won't be found if the pool is not started, just output less infos -+ log.info( -+ "Couldn't extract all volume informations: pool is likely not running or refreshed" -+ ) - return (qemu_target, extra_properties) - - if disk_type == "file": -@@ -5908,12 +5917,7 @@ def _is_kvm_hyper(): - """ - Returns a bool whether or not this node is a KVM hypervisor - """ -- try: -- with salt.utils.files.fopen("/proc/modules") as fp_: -- if "kvm_" not in salt.utils.stringutils.to_unicode(fp_.read()): -- return False -- except OSError: -- # No /proc/modules? Are we on Windows? Or Solaris? -+ if not os.path.exists("/dev/kvm"): - return False - return "libvirtd" in __salt__["cmd.run"](__grains__["ps"]) - -@@ -6936,7 +6940,11 @@ def all_capabilities(**kwargs): - host_caps = ElementTree.fromstring(conn.getCapabilities()) - domains = [ - [ -- (guest.get("arch", {}).get("name", None), key) -+ ( -+ guest.get("arch", {}).get("name", None), -+ key, -+ guest.get("arch", {}).get("emulator", None), -+ ) - for key in guest.get("arch", {}).get("domains", {}).keys() - ] - for guest in [ -@@ -6954,10 +6962,10 @@ def all_capabilities(**kwargs): - "domains": [ - _parse_domain_caps( - ElementTree.fromstring( -- conn.getDomainCapabilities(None, arch, None, domain) -+ conn.getDomainCapabilities(emulator, arch, None, domain) - ) - ) -- for (arch, domain) in flattened -+ for (arch, domain, emulator) in flattened - ], - } - return result -@@ -7584,7 +7592,7 @@ def network_update( - if node.get("family", "ipv4") == "ipv4" - ] - for ip_node in ipv4_nodes: -- netmask = ip_node.attrib.pop("netmask") -+ netmask = ip_node.attrib.pop("netmask", None) - if netmask: - address = ipaddress.ip_network( - "{}/{}".format(ip_node.get("address"), netmask), strict=False -diff --git a/salt/templates/virt/libvirt_domain.jinja b/salt/templates/virt/libvirt_domain.jinja -index 4603dfd8de..6772b0db56 100644 ---- a/salt/templates/virt/libvirt_domain.jinja -+++ b/salt/templates/virt/libvirt_domain.jinja -@@ -285,7 +285,7 @@ - autoport='{{ yesno(not graphics.port and not graphics.tls_port) }}'> - - -- {%- if graphics.type == "spice" %} -+ {%- if graphics.type == "spice" and hypervisor in ["qemu", "kvm"] %} - - - -diff --git a/tests/pytests/unit/modules/virt/test_domain.py b/tests/pytests/unit/modules/virt/test_domain.py -index 3301b94d6d..9a1b2814f8 100644 ---- a/tests/pytests/unit/modules/virt/test_domain.py -+++ b/tests/pytests/unit/modules/virt/test_domain.py -@@ -140,13 +140,14 @@ def test_update_xen_disk_volumes(make_mock_vm, make_mock_storage_pool): - assert ret["definition"] - virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "block" == setxml.find(".//disk[3]").get("type") -- assert "/path/to/vdb/vdb1" == setxml.find(".//disk[3]/source").get("dev") -+ assert setxml.find(".//disk[3]").get("type") == "block" -+ assert setxml.find(".//disk[3]/source").get("dev") == "/path/to/vdb/vdb1" - - # Note that my_vm-file-data was not an existing volume before the update -- assert "file" == setxml.find(".//disk[4]").get("type") -- assert "/path/to/default/my_vm_file-data" == setxml.find(".//disk[4]/source").get( -- "file" -+ assert setxml.find(".//disk[4]").get("type") == "file" -+ assert ( -+ setxml.find(".//disk[4]/source").get("file") -+ == "/path/to/default/my_vm_file-data" - ) - - -@@ -191,6 +192,11 @@ def test_get_disks(make_mock_vm, make_mock_storage_pool): - -
- -+ -+ -+ -+ -+ - - - -@@ -204,11 +210,12 @@ def test_get_disks(make_mock_vm, make_mock_storage_pool): - - - """ -- domain_mock = make_mock_vm(vm_def) -+ make_mock_vm(vm_def) - - pool_mock = make_mock_storage_pool( - "default", "dir", ["srv01_system", "srv01_data", "vm05_system"] - ) -+ make_mock_storage_pool("stopped", "dir", []) - - # Append backing store to srv01_data volume XML description - srv1data_mock = pool_mock.storageVolLookupByName("srv01_data") -@@ -255,6 +262,7 @@ def test_get_disks(make_mock_vm, make_mock_storage_pool): - }, - }, - }, -+ "vdd": {"type": "disk", "file": "stopped/vm05_data", "file format": "qcow2"}, - "hda": { - "type": "cdrom", - "file format": "raw", -@@ -322,7 +330,7 @@ def test_get_disk_convert_volumes(make_mock_vm, make_mock_storage_pool): - subprocess_mock.Popen = popen_mock - - with patch.dict(virt.__dict__, {"subprocess": subprocess_mock}): -- assert { -+ assert virt.get_disks("srv01") == { - "vda": { - "type": "disk", - "file": "default/srv01_system", -@@ -345,7 +353,7 @@ def test_get_disk_convert_volumes(make_mock_vm, make_mock_storage_pool): - "disk size": 340525056, - "virtual size": 214748364800, - }, -- } == virt.get_disks("srv01") -+ } - - - def test_update_approx_mem(make_mock_vm): -@@ -388,7 +396,7 @@ def test_gen_hypervisor_features(): - hypervisor_features={"kvm-hint-dedicated": True}, - ) - root = ET.fromstring(xml_data) -- assert "on" == root.find("features/kvm/hint-dedicated").attrib["state"] -+ assert root.find("features/kvm/hint-dedicated").attrib["state"] == "on" - - - def test_update_hypervisor_features(make_mock_vm): -@@ -423,7 +431,7 @@ def test_update_hypervisor_features(make_mock_vm): - ret = virt.update("my_vm", hypervisor_features={"kvm-hint-dedicated": False}) - assert ret["definition"] - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "off" == setxml.find("features/kvm/hint-dedicated").get("state") -+ assert setxml.find("features/kvm/hint-dedicated").get("state") == "off" - - # Add the features - xml_def = """ -@@ -442,7 +450,7 @@ def test_update_hypervisor_features(make_mock_vm): - ret = virt.update("my_vm", hypervisor_features={"kvm-hint-dedicated": True}) - assert ret["definition"] - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "on" == setxml.find("features/kvm/hint-dedicated").get("state") -+ assert setxml.find("features/kvm/hint-dedicated").get("state") == "on" - - - def test_gen_clock(): -@@ -463,8 +471,8 @@ def test_gen_clock(): - clock={"adjustment": 3600, "utc": False}, - ) - root = ET.fromstring(xml_data) -- assert "localtime" == root.find("clock").get("offset") -- assert "3600" == root.find("clock").get("adjustment") -+ assert root.find("clock").get("offset") == "localtime" -+ assert root.find("clock").get("adjustment") == "3600" - - # Specific timezone - xml_data = virt._gen_xml( -@@ -480,8 +488,8 @@ def test_gen_clock(): - clock={"timezone": "CEST"}, - ) - root = ET.fromstring(xml_data) -- assert "timezone" == root.find("clock").get("offset") -- assert "CEST" == root.find("clock").get("timezone") -+ assert root.find("clock").get("offset") == "timezone" -+ assert root.find("clock").get("timezone") == "CEST" - - # UTC - xml_data = virt._gen_xml( -@@ -497,7 +505,7 @@ def test_gen_clock(): - clock={"utc": True}, - ) - root = ET.fromstring(xml_data) -- assert "utc" == root.find("clock").get("offset") -+ assert root.find("clock").get("offset") == "utc" - - # Timers - xml_data = virt._gen_xml( -@@ -524,14 +532,16 @@ def test_gen_clock(): - }, - ) - root = ET.fromstring(xml_data) -- assert "utc" == root.find("clock").get("offset") -- assert "3504000000" == root.find("clock/timer[@name='tsc']").get("frequency") -- assert "native" == root.find("clock/timer[@name='tsc']").get("mode") -- assert "catchup" == root.find("clock/timer[@name='rtc']").get("tickpolicy") -- assert {"slew": "4636", "threshold": "123", "limit": "2342"} == root.find( -- "clock/timer[@name='rtc']/catchup" -- ).attrib -- assert "no" == root.find("clock/timer[@name='hpet']").get("present") -+ assert root.find("clock").get("offset") == "utc" -+ assert root.find("clock/timer[@name='tsc']").get("frequency") == "3504000000" -+ assert root.find("clock/timer[@name='tsc']").get("mode") == "native" -+ assert root.find("clock/timer[@name='rtc']").get("tickpolicy") == "catchup" -+ assert root.find("clock/timer[@name='rtc']/catchup").attrib == { -+ "slew": "4636", -+ "threshold": "123", -+ "limit": "2342", -+ } -+ assert root.find("clock/timer[@name='hpet']").get("present") == "no" - - - def test_update_clock(make_mock_vm): -@@ -590,21 +600,23 @@ def test_update_clock(make_mock_vm): - ) - assert ret["definition"] - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "timezone" == setxml.find("clock").get("offset") -- assert "CEST" == setxml.find("clock").get("timezone") -- assert {"rtc", "hpet"} == {t.get("name") for t in setxml.findall("clock/timer")} -- assert "catchup" == setxml.find("clock/timer[@name='rtc']").get("tickpolicy") -- assert "wall" == setxml.find("clock/timer[@name='rtc']").get("track") -- assert {"slew": "4636", "threshold": "123", "limit": "2342"} == setxml.find( -- "clock/timer[@name='rtc']/catchup" -- ).attrib -- assert "yes" == setxml.find("clock/timer[@name='hpet']").get("present") -+ assert setxml.find("clock").get("offset") == "timezone" -+ assert setxml.find("clock").get("timezone") == "CEST" -+ assert {t.get("name") for t in setxml.findall("clock/timer")} == {"rtc", "hpet"} -+ assert setxml.find("clock/timer[@name='rtc']").get("tickpolicy") == "catchup" -+ assert setxml.find("clock/timer[@name='rtc']").get("track") == "wall" -+ assert setxml.find("clock/timer[@name='rtc']/catchup").attrib == { -+ "slew": "4636", -+ "threshold": "123", -+ "limit": "2342", -+ } -+ assert setxml.find("clock/timer[@name='hpet']").get("present") == "yes" - - # Revert to UTC - ret = virt.update("my_vm", clock={"utc": True, "adjustment": None, "timers": None}) - assert ret["definition"] - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert {"offset": "utc"} == setxml.find("clock").attrib -+ assert setxml.find("clock").attrib == {"offset": "utc"} - assert setxml.find("clock/timer") is None - - -@@ -630,7 +642,7 @@ def test_update_stop_on_reboot_reset(make_mock_vm): - assert ret["definition"] - virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "restart" == setxml.find("./on_reboot").text -+ assert setxml.find("./on_reboot").text == "restart" - - - def test_update_stop_on_reboot(make_mock_vm): -@@ -654,7 +666,7 @@ def test_update_stop_on_reboot(make_mock_vm): - assert ret["definition"] - virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "destroy" == setxml.find("./on_reboot").text -+ assert setxml.find("./on_reboot").text == "destroy" - - - def test_init_no_stop_on_reboot(make_capabilities): -@@ -667,7 +679,7 @@ def test_init_no_stop_on_reboot(make_capabilities): - virt.init("test_vm", 2, 2048, start=False) - virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "restart" == setxml.find("./on_reboot").text -+ assert setxml.find("./on_reboot").text == "restart" - - - def test_init_stop_on_reboot(make_capabilities): -@@ -680,7 +692,7 @@ def test_init_stop_on_reboot(make_capabilities): - virt.init("test_vm", 2, 2048, stop_on_reboot=True, start=False) - virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) -- assert "destroy" == setxml.find("./on_reboot").text -+ assert setxml.find("./on_reboot").text == "destroy" - - - def test_init_hostdev_usb(make_capabilities, make_mock_device): -@@ -722,8 +734,8 @@ def test_init_hostdev_usb(make_capabilities, make_mock_device): - - """ - ) -- assert expected_xml == strip_xml( -- ET.tostring(setxml.find("./devices/hostdev")) -+ assert ( -+ strip_xml(ET.tostring(setxml.find("./devices/hostdev"))) == expected_xml - ) - - -@@ -764,8 +776,8 @@ def test_init_hostdev_pci(make_capabilities, make_mock_device): - - """ - ) -- assert expected_xml == strip_xml( -- ET.tostring(setxml.find("./devices/hostdev")) -+ assert ( -+ strip_xml(ET.tostring(setxml.find("./devices/hostdev"))) == expected_xml - ) - - -@@ -938,11 +950,11 @@ def test_update_hostdev_changes(running, live, make_mock_device, make_mock_vm, t - ET.tostring(xmlutil.strip_spaces(node)) - for node in set_xml.findall("./devices/hostdev") - ] -- assert [usb_device_xml] == actual_hostdevs -+ assert actual_hostdevs == [usb_device_xml] - - if not test and live: - attach_xml = strip_xml(domain_mock.attachDevice.call_args[0][0]) -- assert usb_device_xml == attach_xml -+ assert attach_xml == usb_device_xml - - pci_device_xml = strip_xml( - """ -@@ -955,7 +967,7 @@ def test_update_hostdev_changes(running, live, make_mock_device, make_mock_vm, t - """ - ) - detach_xml = strip_xml(domain_mock.detachDevice.call_args[0][0]) -- assert pci_device_xml == detach_xml -+ assert detach_xml == pci_device_xml - else: - domain_mock.attachDevice.assert_not_called() - domain_mock.detachDevice.assert_not_called() -@@ -1012,14 +1024,16 @@ def test_diff_nics(): - """ - ).findall("interface") - ret = virt._diff_interface_lists(old_nics, new_nics) -- assert ["52:54:00:39:02:b1"] == [ -- nic.find("mac").get("address") for nic in ret["unchanged"] -+ assert [nic.find("mac").get("address") for nic in ret["unchanged"]] == [ -+ "52:54:00:39:02:b1" - ] -- assert ["52:54:00:39:02:b2", "52:54:00:39:02:b4"] == [ -- nic.find("mac").get("address") for nic in ret["new"] -+ assert [nic.find("mac").get("address") for nic in ret["new"]] == [ -+ "52:54:00:39:02:b2", -+ "52:54:00:39:02:b4", - ] -- assert ["52:54:00:39:02:b2", "52:54:00:39:02:b3"] == [ -- nic.find("mac").get("address") for nic in ret["deleted"] -+ assert [nic.find("mac").get("address") for nic in ret["deleted"]] == [ -+ "52:54:00:39:02:b2", -+ "52:54:00:39:02:b3", - ] - - -@@ -1066,8 +1080,9 @@ def test_diff_nics_live_nochange(): - """ - ) - ret = virt._diff_interface_lists(old_nics, new_nics) -- assert ["52:54:00:03:02:15", "52:54:00:ea:2e:89"] == [ -- nic.find("mac").get("address") for nic in ret["unchanged"] -+ assert [nic.find("mac").get("address") for nic in ret["unchanged"]] == [ -+ "52:54:00:03:02:15", -+ "52:54:00:ea:2e:89", - ] - - -@@ -1350,7 +1365,7 @@ def test_update_bootdev_unchanged(make_mock_vm, boot_dev): - """ - ) - ret = virt.update("my_vm", boot_dev=boot_dev) -- assert (boot_dev != "hd") == ret["definition"] -+ assert ret["definition"] == (boot_dev != "hd") - if boot_dev == "hd": - virt.libvirt.openAuth().defineXML.assert_not_called() - else: -@@ -2128,3 +2143,65 @@ def test_update_failure(make_mock_vm): - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - } -+ -+@pytest.mark.parametrize("hypervisor", ["kvm", "xen"]) -+def test_gen_xml_spice_default(hypervisor): -+ """ -+ Test virt._gen_xml() with default spice graphics device -+ """ -+ xml_data = virt._gen_xml( -+ virt.libvirt.openAuth.return_value, -+ "hello", -+ 1, -+ 512, -+ {}, -+ {}, -+ hypervisor, -+ "hvm", -+ "x86_64", -+ graphics={"type": "spice"}, -+ ) -+ root = ET.fromstring(xml_data) -+ assert root.find("devices/graphics").attrib["type"] == "spice" -+ assert root.find("devices/graphics").attrib["autoport"] == "yes" -+ assert root.find("devices/graphics").attrib["listen"] == "0.0.0.0" -+ assert root.find("devices/graphics/listen").attrib["type"] == "address" -+ assert root.find("devices/graphics/listen").attrib["address"] == "0.0.0.0" -+ if hypervisor == "kvm": -+ assert ( -+ root.find(".//channel[@type='spicevmc']/target").get("name") -+ == "com.redhat.spice.0" -+ ) -+ else: -+ assert root.find(".//channel[@type='spicevmc']") is None -+ -+ -+def test_gen_xml_spice(): -+ """ -+ Test virt._gen_xml() with spice graphics device -+ """ -+ xml_data = virt._gen_xml( -+ virt.libvirt.openAuth.return_value, -+ "hello", -+ 1, -+ 512, -+ {}, -+ {}, -+ "kvm", -+ "hvm", -+ "x86_64", -+ graphics={ -+ "type": "spice", -+ "port": 1234, -+ "tls_port": 5678, -+ "listen": {"type": "none"}, -+ }, -+ ) -+ root = ET.fromstring(xml_data) -+ assert root.find("devices/graphics").attrib["type"] == "spice" -+ assert root.find("devices/graphics").attrib["autoport"] == "no" -+ assert root.find("devices/graphics").attrib["port"] == "1234" -+ assert root.find("devices/graphics").attrib["tlsPort"] == "5678" -+ assert "listen" not in root.find("devices/graphics").attrib -+ assert root.find("devices/graphics/listen").attrib["type"] == "none" -+ assert "address" not in root.find("devices/graphics/listen").attrib -diff --git a/tests/pytests/unit/modules/virt/test_helpers.py b/tests/pytests/unit/modules/virt/test_helpers.py -index 4932d84ec4..e537b87aab 100644 ---- a/tests/pytests/unit/modules/virt/test_helpers.py -+++ b/tests/pytests/unit/modules/virt/test_helpers.py -@@ -13,12 +13,12 @@ def append_to_XMLDesc(mocked, fragment): - mocked.XMLDesc.return_value = ET.tostring(xml_doc) - - --def assert_xml_equals(expected, actual): -+def assert_xml_equals(actual, expected): - """ - Assert that two ElementTree nodes are equal - """ -- assert xmlutil.to_dict(xmlutil.strip_spaces(expected), True) == xmlutil.to_dict( -- xmlutil.strip_spaces(actual), True -+ assert xmlutil.to_dict(xmlutil.strip_spaces(actual), True) == xmlutil.to_dict( -+ xmlutil.strip_spaces(expected), True - ) - - -diff --git a/tests/pytests/unit/modules/virt/test_host.py b/tests/pytests/unit/modules/virt/test_host.py -index 555deb23bb..c5cadb8aa0 100644 ---- a/tests/pytests/unit/modules/virt/test_host.py -+++ b/tests/pytests/unit/modules/virt/test_host.py -@@ -1,5 +1,8 @@ -+import os.path -+ - import pytest - import salt.modules.virt as virt -+from tests.support.mock import MagicMock, patch - - from .conftest import loader_modules_config - -@@ -173,7 +176,7 @@ def test_node_devices(make_mock_device): - ] - virt.libvirt.openAuth().listAllDevices.return_value = mock_devs - -- assert [ -+ assert virt.node_devices() == [ - { - "name": "pci_1002_71c4", - "caps": "pci", -@@ -216,4 +219,20 @@ def test_node_devices(make_mock_device): - "state": "down", - "device name": "pci_0000_02_10_7", - }, -- ] == virt.node_devices() -+ ] -+ -+ -+@pytest.mark.parametrize( -+ "dev_kvm, libvirtd", [(True, True), (False, False), (True, False)] -+) -+def test_is_kvm(dev_kvm, libvirtd): -+ """ -+ Test the virt._is_kvm_hyper() function -+ """ -+ with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=dev_kvm)}): -+ processes = ["libvirtd"] if libvirtd else [] -+ with patch.dict(virt.__grains__, {"ps": MagicMock(return_value="foo")}): -+ with patch.dict( -+ virt.__salt__, {"cmd.run": MagicMock(return_value=processes)} -+ ): -+ assert virt._is_kvm_hyper() == (dev_kvm and libvirtd) -diff --git a/tests/pytests/unit/modules/virt/test_network.py b/tests/pytests/unit/modules/virt/test_network.py -index ba592694c0..5ab4e0a449 100644 ---- a/tests/pytests/unit/modules/virt/test_network.py -+++ b/tests/pytests/unit/modules/virt/test_network.py -@@ -19,10 +19,10 @@ def test_gen_xml(): - """ - xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch") - root = ET.fromstring(xml_data) -- assert "network" == root.find("name").text -- assert "main" == root.find("bridge").attrib["name"] -- assert "bridge" == root.find("forward").attrib["mode"] -- assert "openvswitch" == root.find("virtualport").attrib["type"] -+ assert root.find("name").text == "network" -+ assert root.find("bridge").attrib["name"] == "main" -+ assert root.find("forward").attrib["mode"] == "bridge" -+ assert root.find("virtualport").attrib["type"] == "openvswitch" - - - def test_gen_xml_nat(): -@@ -69,9 +69,9 @@ def test_gen_xml_nat(): - mtu=9000, - ) - root = ET.fromstring(xml_data) -- assert "network" == root.find("name").text -- assert "main" == root.find("bridge").attrib["name"] -- assert "nat" == root.find("forward").attrib["mode"] -+ assert root.find("name").text == "network" -+ assert root.find("bridge").attrib["name"] == "main" -+ assert root.find("forward").attrib["mode"] == "nat" - expected_ipv4 = ET.fromstring( - """ - -@@ -85,7 +85,7 @@ def test_gen_xml_nat(): - - """ - ) -- assert_xml_equals(expected_ipv4, root.find("./ip[@address='192.168.2.1']")) -+ assert_xml_equals(root.find("./ip[@address='192.168.2.1']"), expected_ipv4) - - expected_ipv6 = ET.fromstring( - """ -@@ -97,7 +97,7 @@ def test_gen_xml_nat(): - - """ - ) -- assert_xml_equals(expected_ipv6, root.find("./ip[@address='2001:db8:ca2:2::1']")) -+ assert_xml_equals(root.find("./ip[@address='2001:db8:ca2:2::1']"), expected_ipv6) - - actual_nat = ET.tostring(xmlutil.strip_spaces(root.find("./forward/nat"))) - expected_nat = strip_xml( -@@ -108,10 +108,10 @@ def test_gen_xml_nat(): - - """ - ) -- assert expected_nat == actual_nat -+ assert actual_nat == expected_nat - -- assert {"name": "acme.lab", "localOnly": "yes"} == root.find("./domain").attrib -- assert "9000" == root.find("mtu").get("size") -+ assert root.find("./domain").attrib == {"name": "acme.lab", "localOnly": "yes"} -+ assert root.find("mtu").get("size") == "9000" - - - def test_gen_xml_dns(): -@@ -172,7 +172,7 @@ def test_gen_xml_dns(): - - """ - ) -- assert_xml_equals(expected_xml, root.find("./dns")) -+ assert_xml_equals(root.find("./dns"), expected_xml) - - - def test_gen_xml_isolated(): -@@ -191,9 +191,11 @@ def test_gen_xml_passthrough_interfaces(): - "network", "virbr0", "passthrough", None, interfaces="eth10 eth11 eth12", - ) - root = ET.fromstring(xml_data) -- assert "passthrough" == root.find("forward").get("mode") -- assert ["eth10", "eth11", "eth12"] == [ -- n.get("dev") for n in root.findall("forward/interface") -+ assert root.find("forward").get("mode") == "passthrough" -+ assert [n.get("dev") for n in root.findall("forward/interface")] == [ -+ "eth10", -+ "eth11", -+ "eth12", - ] - - -@@ -213,7 +215,7 @@ def test_gen_xml_hostdev_addresses(): - - """ - ) -- assert_xml_equals(expected_forward, root.find("./forward")) -+ assert_xml_equals(root.find("./forward"), expected_forward) - - - def test_gen_xml_hostdev_pf(): -@@ -232,7 +234,7 @@ def test_gen_xml_hostdev_pf(): - """ - ) - actual_forward = ET.tostring(xmlutil.strip_spaces(root.find("./forward"))) -- assert expected_forward == actual_forward -+ assert actual_forward == expected_forward - - - def test_gen_xml_openvswitch(): -@@ -268,7 +270,7 @@ def test_gen_xml_openvswitch(): - - """ - ) -- assert_xml_equals(expected_xml, ET.fromstring(xml_data)) -+ assert_xml_equals(ET.fromstring(xml_data), expected_xml) - - - @pytest.mark.parametrize( -@@ -308,7 +310,7 @@ def test_define(make_mock_network, autostart, start): - """ - ) - define_mock = virt.libvirt.openAuth().networkDefineXML -- assert expected_xml == strip_xml(define_mock.call_args[0][0]) -+ assert strip_xml(define_mock.call_args[0][0]) == expected_xml - - if autostart: - mock_network.setAutostart.assert_called_with(1) -@@ -364,8 +366,11 @@ def test_update_nat_nochange(make_mock_network): - define_mock.assert_not_called() - - --@pytest.mark.parametrize("test", [True, False]) --def test_update_nat_change(make_mock_network, test): -+@pytest.mark.parametrize( -+ "test, netmask", -+ [(True, "netmask='255.255.255.0'"), (True, "prefix='24'"), (False, "prefix='24'")], -+) -+def test_update_nat_change(make_mock_network, test, netmask): - """ - Test updating a NAT network with changes - """ -@@ -378,13 +383,15 @@ def test_update_nat_change(make_mock_network, test): - - - -- -+ - - - - - -- """ -+ """.format( -+ netmask -+ ) - ) - assert virt.network_update( - "default", -@@ -417,7 +424,7 @@ def test_update_nat_change(make_mock_network, test): - - """ - ) -- assert expected_xml == strip_xml(define_mock.call_args[0][0]) -+ assert strip_xml(define_mock.call_args[0][0]) == expected_xml - - - @pytest.mark.parametrize("change", [True, False], ids=["changed", "unchanged"]) -@@ -438,11 +445,14 @@ def test_update_hostdev_pf(make_mock_network, change): - - """ - ) -- assert change == virt.network_update( -- "test-hostdev", -- None, -- "hostdev", -- physical_function="eth0" if not change else "eth1", -+ assert ( -+ virt.network_update( -+ "test-hostdev", -+ None, -+ "hostdev", -+ physical_function="eth0" if not change else "eth1", -+ ) -+ == change - ) - define_mock = virt.libvirt.openAuth().networkDefineXML - if change: -diff --git a/tests/pytests/unit/states/virt/test_domain.py b/tests/pytests/unit/states/virt/test_domain.py -index a4ae8c0694..c705785bf5 100644 ---- a/tests/pytests/unit/states/virt/test_domain.py -+++ b/tests/pytests/unit/states/virt/test_domain.py -@@ -21,14 +21,14 @@ def test_defined_no_change(test): - "virt.init": init_mock, - }, - ): -- assert { -+ assert virt.defined("myvm") == { - "name": "myvm", - "changes": {"myvm": {"definition": False}}, - "result": True, - "comment": "Domain myvm unchanged", -- } == virt.defined("myvm") -+ } - init_mock.assert_not_called() -- assert [domain_update_call("myvm", test=test)] == update_mock.call_args_list -+ assert update_mock.call_args_list == [domain_update_call("myvm", test=test)] - - - def test_defined_new_with_connection(test): -@@ -72,12 +72,7 @@ def test_defined_new_with_connection(test): - {"type": "tcp", "port": 22223, "protocol": "telnet"}, - {"type": "pty"}, - ] -- assert { -- "name": "myvm", -- "result": True if not test else None, -- "changes": {"myvm": {"definition": True}}, -- "comment": "Domain myvm defined", -- } == virt.defined( -+ assert virt.defined( - "myvm", - cpu=2, - mem=2048, -@@ -103,7 +98,12 @@ def test_defined_new_with_connection(test): - serials=serials, - consoles=consoles, - host_devices=["pci_0000_00_17_0"], -- ) -+ ) == { -+ "name": "myvm", -+ "result": True if not test else None, -+ "changes": {"myvm": {"definition": True}}, -+ "comment": "Domain myvm defined", -+ } - if not test: - init_mock.assert_called_with( - "myvm", -@@ -160,16 +160,16 @@ def test_defined_update(test): - "initrd": "/root/f8-i386-initrd", - "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", - } -- assert { -+ assert virt.defined("myvm", cpu=2, boot=boot,) == { - "name": "myvm", - "changes": {"myvm": {"definition": True, "cpu": True}}, - "result": True if not test else None, - "comment": "Domain myvm updated", -- } == virt.defined("myvm", cpu=2, boot=boot,) -+ } - init_mock.assert_not_called() -- assert [ -+ assert update_mock.call_args_list == [ - domain_update_call("myvm", cpu=2, test=test, boot=boot) -- ] == update_mock.call_args_list -+ ] - - - def test_defined_update_error(test): -@@ -189,7 +189,7 @@ def test_defined_update_error(test): - "virt.init": init_mock, - }, - ): -- assert { -+ assert virt.defined("myvm", cpu=2, boot_dev="cdrom hd") == { - "name": "myvm", - "changes": { - "myvm": { -@@ -200,7 +200,7 @@ def test_defined_update_error(test): - }, - "result": True if not test else None, - "comment": "Domain myvm updated with live update(s) failures", -- } == virt.defined("myvm", cpu=2, boot_dev="cdrom hd") -+ } - init_mock.assert_not_called() - update_mock.assert_called_with( - "myvm", -@@ -245,16 +245,16 @@ def test_defined_update_definition_error(test): - "virt.init": init_mock, - }, - ): -- assert { -+ assert virt.defined("myvm", cpu=2) == { - "name": "myvm", - "changes": {}, - "result": False, - "comment": "error message", -- } == virt.defined("myvm", cpu=2) -+ } - init_mock.assert_not_called() -- assert [ -+ assert update_mock.call_args_list == [ - domain_update_call("myvm", cpu=2, test=test) -- ] == update_mock.call_args_list -+ ] - - - @pytest.mark.parametrize("running", ["running", "shutdown"]) -@@ -279,12 +279,12 @@ def test_running_no_change(test, running): - if running == "shutdown": - changes["started"] = True - comment = "Domain myvm started" -- assert { -+ assert virt.running("myvm") == { - "name": "myvm", - "result": True, - "changes": {"myvm": changes}, - "comment": comment, -- } == virt.running("myvm") -+ } - if running == "shutdown" and not test: - start_mock.assert_called() - else: -@@ -326,12 +326,7 @@ def test_running_define(test): - "listen": {"type": "address", "address": "192.168.0.1"}, - } - -- assert { -- "name": "myvm", -- "result": True if not test else None, -- "changes": {"myvm": {"definition": True, "started": True}}, -- "comment": "Domain myvm defined and started", -- } == virt.running( -+ assert virt.running( - "myvm", - cpu=2, - mem=2048, -@@ -353,7 +348,12 @@ def test_running_define(test): - connection="someconnection", - username="libvirtuser", - password="supersecret", -- ) -+ ) == { -+ "name": "myvm", -+ "result": True if not test else None, -+ "changes": {"myvm": {"definition": True, "started": True}}, -+ "comment": "Domain myvm defined and started", -+ } - if not test: - init_mock.assert_called_with( - "myvm", -@@ -412,12 +412,12 @@ def test_running_start_error(): - "virt.list_domains": MagicMock(return_value=["myvm"]), - }, - ): -- assert { -+ assert virt.running("myvm") == { - "name": "myvm", - "changes": {"myvm": {"definition": False}}, - "result": False, - "comment": "libvirt error msg", -- } == virt.running("myvm") -+ } - - - @pytest.mark.parametrize("running", ["running", "shutdown"]) -@@ -441,14 +441,14 @@ def test_running_update(test, running): - changes = {"myvm": {"definition": True, "cpu": True}} - if running == "shutdown": - changes["myvm"]["started"] = True -- assert { -+ assert virt.running("myvm", cpu=2) == { - "name": "myvm", - "changes": changes, - "result": True if not test else None, - "comment": "Domain myvm updated" - if running == "running" - else "Domain myvm updated and started", -- } == virt.running("myvm", cpu=2) -+ } - if running == "shutdown" and not test: - start_mock.assert_called() - else: -@@ -470,12 +470,12 @@ def test_running_definition_error(): - "virt.list_domains": MagicMock(return_value=["myvm"]), - }, - ): -- assert { -+ assert virt.running("myvm", cpu=3) == { - "name": "myvm", - "changes": {}, - "result": False, - "comment": "error message", -- } == virt.running("myvm", cpu=3) -+ } - - - def test_running_update_error(): -@@ -494,7 +494,7 @@ def test_running_update_error(): - "virt.list_domains": MagicMock(return_value=["myvm"]), - }, - ): -- assert { -+ assert virt.running("myvm", cpu=2) == { - "name": "myvm", - "changes": { - "myvm": { -@@ -505,7 +505,7 @@ def test_running_update_error(): - }, - "result": True, - "comment": "Domain myvm updated with live update(s) failures", -- } == virt.running("myvm", cpu=2) -+ } - update_mock.assert_called_with( - "myvm", - cpu=2, -@@ -552,14 +552,14 @@ def test_stopped(test, running): - if running == "running": - changes = {"stopped": [{"domain": "myvm", "shutdown": True}]} - comment = "Machine has been shut down" -- assert { -+ assert virt.stopped( -+ "myvm", connection="myconnection", username="user", password="secret", -+ ) == { - "name": "myvm", - "changes": changes, - "comment": comment, - "result": True if not test or running == "shutdown" else None, -- } == virt.stopped( -- "myvm", connection="myconnection", username="user", password="secret", -- ) -+ } - if not test and running == "running": - shutdown_mock.assert_called_with( - "myvm", -@@ -586,12 +586,12 @@ def test_stopped_error(): - ), - }, - ): -- assert { -+ assert virt.stopped("myvm") == { - "name": "myvm", - "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, - "result": False, - "comment": "No changes had happened", -- } == virt.stopped("myvm") -+ } - - - def test_stopped_not_existing(test): -@@ -603,12 +603,12 @@ def test_stopped_not_existing(test): - with patch.dict( - virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}, - ): -- assert { -+ assert virt.stopped("myvm") == { - "name": "myvm", - "changes": {}, - "comment": "No changes had happened", - "result": False, -- } == virt.stopped("myvm") -+ } - - - @pytest.mark.parametrize("running", ["running", "shutdown"]) -@@ -631,14 +631,14 @@ def test_powered_off(test, running): - if running == "running": - changes = {"unpowered": [{"domain": "myvm", "stop": True}]} - comment = "Machine has been powered off" -- assert { -+ assert virt.powered_off( -+ "myvm", connection="myconnection", username="user", password="secret", -+ ) == { - "name": "myvm", - "result": True if not test or running == "shutdown" else None, - "changes": changes, - "comment": comment, -- } == virt.powered_off( -- "myvm", connection="myconnection", username="user", password="secret", -- ) -+ } - if not test and running == "running": - stop_mock.assert_called_with( - "myvm", -@@ -666,12 +666,12 @@ def test_powered_off_error(): - ), - }, - ): -- assert { -+ assert virt.powered_off("myvm") == { - "name": "myvm", - "result": False, - "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, - "comment": "No changes had happened", -- } == virt.powered_off("myvm") -+ } - - - def test_powered_off_not_existing(): -@@ -686,12 +686,12 @@ def test_powered_off_not_existing(): - ret.update( - {"changes": {}, "result": False, "comment": "No changes had happened"} - ) -- assert { -+ assert virt.powered_off("myvm") == { - "name": "myvm", - "changes": {}, - "result": False, - "comment": "No changes had happened", -- } == virt.powered_off("myvm") -+ } - - - def test_snapshot(test): -@@ -707,18 +707,18 @@ def test_snapshot(test): - "virt.snapshot": snapshot_mock, - }, - ): -- assert { -- "name": "myvm", -- "result": True if not test else None, -- "changes": {"saved": [{"domain": "myvm", "snapshot": True}]}, -- "comment": "Snapshot has been taken", -- } == virt.snapshot( -+ assert virt.snapshot( - "myvm", - suffix="snap", - connection="myconnection", - username="user", - password="secret", -- ) -+ ) == { -+ "name": "myvm", -+ "result": True if not test else None, -+ "changes": {"saved": [{"domain": "myvm", "snapshot": True}]}, -+ "comment": "Snapshot has been taken", -+ } - if not test: - snapshot_mock.assert_called_with( - "myvm", -@@ -745,12 +745,12 @@ def test_snapshot_error(): - ), - }, - ): -- assert { -+ assert virt.snapshot("myvm") == { - "name": "myvm", - "result": False, - "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, - "comment": "No changes had happened", -- } == virt.snapshot("myvm") -+ } - - - def test_snapshot_not_existing(test): -@@ -761,12 +761,12 @@ def test_snapshot_not_existing(test): - with patch.dict( - virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} - ): -- assert { -+ assert virt.snapshot("myvm") == { - "name": "myvm", - "changes": {}, - "result": False, - "comment": "No changes had happened", -- } == virt.snapshot("myvm") -+ } - - - def test_rebooted(test): -@@ -782,14 +782,14 @@ def test_rebooted(test): - "virt.reboot": reboot_mock, - }, - ): -- assert { -+ assert virt.rebooted( -+ "myvm", connection="myconnection", username="user", password="secret", -+ ) == { - "name": "myvm", - "result": True if not test else None, - "changes": {"rebooted": [{"domain": "myvm", "reboot": True}]}, - "comment": "Machine has been rebooted", -- } == virt.rebooted( -- "myvm", connection="myconnection", username="user", password="secret", -- ) -+ } - if not test: - reboot_mock.assert_called_with( - "myvm", -@@ -816,12 +816,12 @@ def test_rebooted_error(): - ), - }, - ): -- assert { -+ assert virt.rebooted("myvm") == { - "name": "myvm", - "result": False, - "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, - "comment": "No changes had happened", -- } == virt.rebooted("myvm") -+ } - - - def test_rebooted_not_existing(test): -@@ -832,9 +832,9 @@ def test_rebooted_not_existing(test): - with patch.dict( - virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} - ): -- assert { -+ assert virt.rebooted("myvm") == { - "name": "myvm", - "changes": {}, - "result": False, - "comment": "No changes had happened", -- } == virt.rebooted("myvm") -+ } -diff --git a/tests/pytests/unit/states/virt/test_network.py b/tests/pytests/unit/states/virt/test_network.py -index 668eee0c64..a68acfa236 100644 ---- a/tests/pytests/unit/states/virt/test_network.py -+++ b/tests/pytests/unit/states/virt/test_network.py -@@ -19,12 +19,7 @@ def test_network_defined_not_existing(test): - "virt.network_define": define_mock, - }, - ): -- assert { -- "name": "mynet", -- "changes": {"mynet": "Network defined"}, -- "result": None if test else True, -- "comment": "Network mynet defined", -- } == virt.network_defined( -+ assert virt.network_defined( - "mynet", - "br2", - "bridge", -@@ -58,7 +53,12 @@ def test_network_defined_not_existing(test): - connection="myconnection", - username="user", - password="secret", -- ) -+ ) == { -+ "name": "mynet", -+ "changes": {"mynet": "Network defined"}, -+ "result": None if test else True, -+ "comment": "Network mynet defined", -+ } - if not test: - define_mock.assert_called_with( - "mynet", -@@ -117,16 +117,16 @@ def test_network_defined_no_change(test): - "virt.network_update": update_mock, - }, - ): -- assert { -+ assert virt.network_defined("mynet", "br2", "bridge") == { - "name": "mynet", - "changes": {}, - "result": True, - "comment": "Network mynet unchanged", -- } == virt.network_defined("mynet", "br2", "bridge") -+ } - define_mock.assert_not_called() -- assert [ -+ assert update_mock.call_args_list == [ - network_update_call("mynet", "br2", "bridge", test=True) -- ] == update_mock.call_args_list -+ ] - - - def test_network_defined_change(test): -@@ -148,12 +148,7 @@ def test_network_defined_change(test): - "virt.network_set_autostart": autostart_mock, - }, - ): -- assert { -- "name": "mynet", -- "changes": {"mynet": "Network updated, autostart flag changed"}, -- "result": None if test else True, -- "comment": "Network mynet updated, autostart flag changed", -- } == virt.network_defined( -+ assert virt.network_defined( - "mynet", - "br2", - "bridge", -@@ -187,7 +182,12 @@ def test_network_defined_change(test): - connection="myconnection", - username="user", - password="secret", -- ) -+ ) == { -+ "name": "mynet", -+ "changes": {"mynet": "Network updated, autostart flag changed"}, -+ "result": None if test else True, -+ "comment": "Network mynet updated, autostart flag changed", -+ } - define_mock.assert_not_called() - expected_update_kwargs = { - "vport": "openvswitch", -@@ -226,7 +226,7 @@ def test_network_defined_change(test): - ) - ] - if test: -- assert calls == update_mock.call_args_list -+ assert update_mock.call_args_list == calls - autostart_mock.assert_not_called() - else: - calls.append( -@@ -234,7 +234,7 @@ def test_network_defined_change(test): - "mynet", "br2", "bridge", **expected_update_kwargs, test=False - ) - ) -- assert calls == update_mock.call_args_list -+ assert update_mock.call_args_list == calls - autostart_mock.assert_called_with( - "mynet", - state="off", -@@ -258,12 +258,12 @@ def test_network_defined_error(test): - ) - }, - ): -- assert { -+ assert virt.network_defined("mynet", "br2", "bridge") == { - "name": "mynet", - "changes": {}, - "result": False, - "comment": "Some error", -- } == virt.network_defined("mynet", "br2", "bridge") -+ } - define_mock.assert_not_called() - - -@@ -285,12 +285,7 @@ def test_network_running_not_existing(test): - "virt.network_start": start_mock, - }, - ): -- assert { -- "name": "mynet", -- "changes": {"mynet": "Network defined and started"}, -- "comment": "Network mynet defined and started", -- "result": None if test else True, -- } == virt.network_running( -+ assert virt.network_running( - "mynet", - "br2", - "bridge", -@@ -324,7 +319,12 @@ def test_network_running_not_existing(test): - connection="myconnection", - username="user", - password="secret", -- ) -+ ) == { -+ "name": "mynet", -+ "changes": {"mynet": "Network defined and started"}, -+ "comment": "Network mynet defined and started", -+ "result": None if test else True, -+ } - if not test: - define_mock.assert_called_with( - "mynet", -@@ -390,15 +390,15 @@ def test_network_running_nochange(test): - "virt.network_update": update_mock, - }, - ): -- assert { -+ assert virt.network_running("mynet", "br2", "bridge") == { - "name": "mynet", - "changes": {}, - "comment": "Network mynet unchanged and is running", - "result": None if test else True, -- } == virt.network_running("mynet", "br2", "bridge") -- assert [ -+ } -+ assert update_mock.call_args_list == [ - network_update_call("mynet", "br2", "bridge", test=True) -- ] == update_mock.call_args_list -+ ] - - - def test_network_running_stopped(test): -@@ -420,20 +420,20 @@ def test_network_running_stopped(test): - "virt.network_update": update_mock, - }, - ): -- assert { -- "name": "mynet", -- "changes": {"mynet": "Network started"}, -- "comment": "Network mynet unchanged and started", -- "result": None if test else True, -- } == virt.network_running( -+ assert virt.network_running( - "mynet", - "br2", - "bridge", - connection="myconnection", - username="user", - password="secret", -- ) -- assert [ -+ ) == { -+ "name": "mynet", -+ "changes": {"mynet": "Network started"}, -+ "comment": "Network mynet unchanged and started", -+ "result": None if test else True, -+ } -+ assert update_mock.call_args_list == [ - network_update_call( - "mynet", - "br2", -@@ -443,7 +443,7 @@ def test_network_running_stopped(test): - password="secret", - test=True, - ) -- ] == update_mock.call_args_list -+ ] - if not test: - start_mock.assert_called_with( - "mynet", -@@ -468,9 +468,9 @@ def test_network_running_error(test): - ), - }, - ): -- assert { -+ assert virt.network_running("mynet", "br2", "bridge") == { - "name": "mynet", - "changes": {}, - "comment": "Some error", - "result": False, -- } == virt.network_running("mynet", "br2", "bridge") -+ } -diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py -index dd42ce4441..ec11b56d98 100644 ---- a/tests/unit/modules/test_virt.py -+++ b/tests/unit/modules/test_virt.py -@@ -520,65 +520,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): - root.find("devices/graphics/listen").attrib["address"], "myhost" - ) - -- def test_gen_xml_spice_default(self): -- """ -- Test virt._gen_xml() with default spice graphics device -- """ -- diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") -- nicp = virt._nic_profile("default", "kvm") -- xml_data = virt._gen_xml( -- self.mock_conn, -- "hello", -- 1, -- 512, -- diskp, -- nicp, -- "kvm", -- "hvm", -- "x86_64", -- graphics={"type": "spice"}, -- ) -- root = ET.fromstring(xml_data) -- self.assertEqual(root.find("devices/graphics").attrib["type"], "spice") -- self.assertEqual(root.find("devices/graphics").attrib["autoport"], "yes") -- self.assertEqual(root.find("devices/graphics").attrib["listen"], "0.0.0.0") -- self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address") -- self.assertEqual( -- root.find("devices/graphics/listen").attrib["address"], "0.0.0.0" -- ) -- -- def test_gen_xml_spice(self): -- """ -- Test virt._gen_xml() with spice graphics device -- """ -- diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") -- nicp = virt._nic_profile("default", "kvm") -- xml_data = virt._gen_xml( -- self.mock_conn, -- "hello", -- 1, -- 512, -- diskp, -- nicp, -- "kvm", -- "hvm", -- "x86_64", -- graphics={ -- "type": "spice", -- "port": 1234, -- "tls_port": 5678, -- "listen": {"type": "none"}, -- }, -- ) -- root = ET.fromstring(xml_data) -- self.assertEqual(root.find("devices/graphics").attrib["type"], "spice") -- self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no") -- self.assertEqual(root.find("devices/graphics").attrib["port"], "1234") -- self.assertEqual(root.find("devices/graphics").attrib["tlsPort"], "5678") -- self.assertFalse("listen" in root.find("devices/graphics").attrib) -- self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "none") -- self.assertFalse("address" in root.find("devices/graphics/listen").attrib) -- - def test_gen_xml_memory(self): - """ - Test virt._gen_xml() with advanced memory settings -@@ -5140,6 +5081,10 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): - {"qemu", "kvm"}, {domainCaps["domain"] for domainCaps in caps["domains"]}, - ) - -+ self.mock_conn.getDomainCapabilities.assert_called_with( -+ "/usr/bin/qemu-system-x86_64", "x86_64", None, "kvm" -+ ) -+ - def test_network_tag(self): - """ - Test virt._get_net_xml() with VLAN tag --- -2.33.0 - - diff --git a/x509-fixes-111.patch b/x509-fixes-111.patch index 29fffbe..56eeac8 100644 --- a/x509-fixes-111.patch +++ b/x509-fixes-111.patch @@ -1,6 +1,6 @@ -From e16881c36b535c4798d0bfda089efd9323957fec Mon Sep 17 00:00:00 2001 -From: Florian Bergmann -Date: Fri, 14 Sep 2018 10:30:39 +0200 +From da47028898edb69290f989d9f99973969d6a8652 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 16:38:17 +0100 Subject: [PATCH] X509 fixes (#111) * Return proper content type for the x509 certificate @@ -37,59 +37,17 @@ Fix for log checking in x509 test We are logging in debug and not in trace mode here. --- - salt/modules/publish.py | 17 +++--- - salt/modules/x509.py | 100 ++++++++++++++++---------------- - salt/states/x509.py | 74 +++++++++++++++++++++-- - tests/unit/modules/test_x509.py | 6 +- - 4 files changed, 131 insertions(+), 66 deletions(-) + salt/modules/publish.py | 2 + + salt/modules/x509.py | 93 ++++++++++++++++----------------- + salt/states/x509.py | 74 ++++++++++++++++++++++++-- + tests/unit/modules/test_x509.py | 6 +-- + 4 files changed, 121 insertions(+), 54 deletions(-) diff --git a/salt/modules/publish.py b/salt/modules/publish.py -index 0926f3fa13..6d56c4d08e 100644 +index f9b7e8b168..651119906e 100644 --- a/salt/modules/publish.py +++ b/salt/modules/publish.py -@@ -1,15 +1,10 @@ --# -*- coding: utf-8 -*- - """ - Publish a command from a minion to a target - """ --from __future__ import absolute_import, print_function, unicode_literals - - import logging -- --# Import python libs - import time - --# Import salt libs - import salt.crypt - import salt.payload - import salt.transport.client -@@ -93,13 +88,13 @@ def _publish( - matching_master_uris = [ - master - for master in __opts__["master_uri_list"] -- if "//{0}:".format(via_master) in master -+ if "//{}:".format(via_master) in master - ] - - if not matching_master_uris: - raise SaltInvocationError( -- "Could not find match for {0} in \ -- list of configured masters {1} when using `via_master` option".format( -+ "Could not find match for {} in \ -+ list of configured masters {} when using `via_master` option".format( - via_master, __opts__["master_uri_list"] - ) - ) -@@ -143,7 +138,7 @@ def _publish( - try: - peer_data = channel.send(load) - except SaltReqTimeoutError: -- return "'{0}' publish timed out".format(fun) -+ return "'{}' publish timed out".format(fun) - if not peer_data: - return {} - # CLI args are passed as strings, re-cast to keep time.sleep happy -@@ -198,6 +193,8 @@ def _publish( +@@ -196,6 +196,8 @@ def _publish( else: return ret @@ -98,14 +56,8 @@ index 0926f3fa13..6d56c4d08e 100644 def publish( tgt, fun, arg=None, tgt_type="glob", returner="", timeout=5, via_master=None -@@ -347,4 +344,4 @@ def runner(fun, arg=None, timeout=5): - try: - return channel.send(load) - except SaltReqTimeoutError: -- return "'{0}' runner publish timed out".format(fun) -+ return "'{}' runner publish timed out".format(fun) diff --git a/salt/modules/x509.py b/salt/modules/x509.py -index 43a134625f..d8e6ae3baa 100644 +index 0909bace48..261b794295 100644 --- a/salt/modules/x509.py +++ b/salt/modules/x509.py @@ -30,16 +30,13 @@ from salt.utils.odict import OrderedDict @@ -186,7 +138,7 @@ index 43a134625f..d8e6ae3baa 100644 for nid_num, nid_name, val in sorted(ret_list): ret[nid_name] = val return ret -@@ -536,8 +535,8 @@ def get_pem_entries(glob_path): +@@ -537,8 +536,8 @@ def get_pem_entries(glob_path): if os.path.isfile(path): try: ret[path] = get_pem_entry(text=path) @@ -197,7 +149,7 @@ index 43a134625f..d8e6ae3baa 100644 return ret -@@ -615,8 +614,8 @@ def read_certificates(glob_path): +@@ -616,8 +615,8 @@ def read_certificates(glob_path): if os.path.isfile(path): try: ret[path] = read_certificate(certificate=path) @@ -208,7 +160,7 @@ index 43a134625f..d8e6ae3baa 100644 return ret -@@ -646,10 +645,9 @@ def read_csr(csr): +@@ -647,10 +646,9 @@ def read_csr(csr): "Subject": _parse_subject(csr.get_subject()), "Subject Hash": _dec2hex(csr.get_subject().as_hash()), "Public Key Hash": hashlib.sha1(csr.get_pubkey().get_modulus()).hexdigest(), @@ -220,7 +172,7 @@ index 43a134625f..d8e6ae3baa 100644 return ret -@@ -959,7 +957,7 @@ def create_crl( +@@ -960,7 +958,7 @@ def create_crl( # pyOpenSSL Note due to current limitations in pyOpenSSL it is impossible # to specify a digest For signing the CRL. This will hopefully be fixed # soon: https://github.com/pyca/pyopenssl/pull/161 @@ -229,18 +181,7 @@ index 43a134625f..d8e6ae3baa 100644 raise salt.exceptions.SaltInvocationError( "Could not load OpenSSL module, OpenSSL unavailable" ) -@@ -1033,7 +1031,9 @@ def create_crl( - crltext = crl.export(**export_kwargs) - except (TypeError, ValueError): - log.warning( -- "Error signing crl with specified digest. Are you using pyopenssl 0.15 or newer? The default md5 digest will be used." -+ "Error signing crl with specified digest. " -+ "Are you using pyopenssl 0.15 or newer? " -+ "The default md5 digest will be used." - ) - export_kwargs.pop("digest", None) - crltext = crl.export(**export_kwargs) -@@ -1109,6 +1109,7 @@ def get_signing_policy(signing_policy_name): +@@ -1111,6 +1109,7 @@ def get_signing_policy(signing_policy_name): signing_policy = _get_signing_policy(signing_policy_name) if not signing_policy: return "Signing policy {} does not exist.".format(signing_policy_name) @@ -248,7 +189,7 @@ index 43a134625f..d8e6ae3baa 100644 if isinstance(signing_policy, list): dict_ = {} for item in signing_policy: -@@ -1125,7 +1126,7 @@ def get_signing_policy(signing_policy_name): +@@ -1127,7 +1126,7 @@ def get_signing_policy(signing_policy_name): signing_policy["signing_cert"], "CERTIFICATE" ) except KeyError: @@ -257,17 +198,7 @@ index 43a134625f..d8e6ae3baa 100644 return signing_policy -@@ -1734,7 +1735,8 @@ def create_csr(path=None, text=False, **kwargs): - if "private_key" not in kwargs and "public_key" in kwargs: - kwargs["private_key"] = kwargs["public_key"] - log.warning( -- "OpenSSL no longer allows working with non-signed CSRs. A private_key must be specified. Attempting to use public_key as private_key" -+ "OpenSSL no longer allows working with non-signed CSRs. " -+ "A private_key must be specified. Attempting to use public_key as private_key" - ) - - if "private_key" not in kwargs: -@@ -1758,7 +1760,8 @@ def create_csr(path=None, text=False, **kwargs): +@@ -1761,7 +1760,8 @@ def create_csr(path=None, text=False, **kwargs): ) ) @@ -277,7 +208,7 @@ index 43a134625f..d8e6ae3baa 100644 if entry in kwargs: setattr(subject, entry, kwargs[entry]) -@@ -1794,7 +1797,6 @@ def create_csr(path=None, text=False, **kwargs): +@@ -1797,7 +1797,6 @@ def create_csr(path=None, text=False, **kwargs): extstack.push(ext) csr.add_extensions(extstack) @@ -285,7 +216,7 @@ index 43a134625f..d8e6ae3baa 100644 csr.sign( _get_private_key_obj( kwargs["private_key"], passphrase=kwargs["private_key_passphrase"] -@@ -1802,10 +1804,11 @@ def create_csr(path=None, text=False, **kwargs): +@@ -1805,10 +1804,11 @@ def create_csr(path=None, text=False, **kwargs): kwargs["algorithm"], ) @@ -301,7 +232,7 @@ index 43a134625f..d8e6ae3baa 100644 def verify_private_key(private_key, public_key, passphrase=None): -@@ -1830,7 +1833,7 @@ def verify_private_key(private_key, public_key, passphrase=None): +@@ -1833,7 +1833,7 @@ def verify_private_key(private_key, public_key, passphrase=None): salt '*' x509.verify_private_key private_key=/etc/pki/myca.key \\ public_key=/etc/pki/myca.crt """ @@ -310,7 +241,7 @@ index 43a134625f..d8e6ae3baa 100644 def verify_signature( -@@ -1886,7 +1889,10 @@ def verify_crl(crl, cert): +@@ -1889,7 +1889,10 @@ def verify_crl(crl, cert): salt '*' x509.verify_crl crl=/etc/pki/myca.crl cert=/etc/pki/myca.crt """ if not salt.utils.path.which("openssl"): @@ -322,7 +253,7 @@ index 43a134625f..d8e6ae3baa 100644 crltext = _text_or_file(crl) crltext = get_pem_entry(crltext, pem_type="X509 CRL") crltempfile = tempfile.NamedTemporaryFile(delete=True) -@@ -1908,10 +1914,7 @@ def verify_crl(crl, cert): +@@ -1911,10 +1914,7 @@ def verify_crl(crl, cert): crltempfile.close() certtempfile.close() @@ -334,7 +265,7 @@ index 43a134625f..d8e6ae3baa 100644 def expired(certificate): -@@ -1949,8 +1952,9 @@ def expired(certificate): +@@ -1952,8 +1952,9 @@ def expired(certificate): ret["expired"] = True else: ret["expired"] = False @@ -346,7 +277,7 @@ index 43a134625f..d8e6ae3baa 100644 return ret -@@ -1973,6 +1977,7 @@ def will_expire(certificate, days): +@@ -1976,6 +1977,7 @@ def will_expire(certificate, days): salt '*' x509.will_expire "/etc/pki/mycert.crt" days=30 """ @@ -354,7 +285,7 @@ index 43a134625f..d8e6ae3baa 100644 ret = {} if os.path.isfile(certificate): -@@ -1986,14 +1991,11 @@ def will_expire(certificate, days): +@@ -1989,14 +1991,11 @@ def will_expire(certificate, days): _expiration_date = cert.get_not_after().get_datetime() ret["cn"] = _parse_subject(cert.get_subject())["CN"] @@ -376,7 +307,7 @@ index 43a134625f..d8e6ae3baa 100644 return ret diff --git a/salt/states/x509.py b/salt/states/x509.py -index 80e1ecdd6d..3a54ad3ba3 100644 +index b3d2f978bd..16811bcfb8 100644 --- a/salt/states/x509.py +++ b/salt/states/x509.py @@ -177,11 +177,12 @@ import os @@ -402,7 +333,7 @@ index 80e1ecdd6d..3a54ad3ba3 100644 def _revoked_to_list(revs): -@@ -683,7 +684,70 @@ def certificate_managed(name, days_remaining=90, append_certs=None, **kwargs): +@@ -682,7 +683,70 @@ def certificate_managed(name, days_remaining=90, append_certs=None, **kwargs): "Old": invalid_reason, "New": "Certificate will be valid and up to date", } @@ -474,7 +405,7 @@ index 80e1ecdd6d..3a54ad3ba3 100644 contents = __salt__["x509.create_certificate"](text=True, **kwargs) # Check the module actually returned a cert and not an error message as a string -@@ -879,6 +943,8 @@ def pem_managed(name, text, backup=False, **kwargs): +@@ -878,6 +942,8 @@ def pem_managed(name, text, backup=False, **kwargs): Any arguments supported by :py:func:`file.managed ` are supported. """ file_args, kwargs = _get_file_args(name, **kwargs) @@ -485,7 +416,7 @@ index 80e1ecdd6d..3a54ad3ba3 100644 return __states__["file.managed"](**file_args) diff --git a/tests/unit/modules/test_x509.py b/tests/unit/modules/test_x509.py -index 20ca0d679a..8fa799437d 100644 +index 8f4c433b1a..3105290a2c 100644 --- a/tests/unit/modules/test_x509.py +++ b/tests/unit/modules/test_x509.py @@ -118,9 +118,9 @@ class X509TestCase(TestCase, LoaderModuleMockMixin): @@ -502,6 +433,6 @@ index 20ca0d679a..8fa799437d 100644 @skipIf(not HAS_M2CRYPTO, "Skipping, M2Crypto is unavailable") def test_get_pem_entry(self): -- -2.33.0 +2.34.1 diff --git a/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch b/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch index bec6004..6984769 100644 --- a/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch +++ b/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch @@ -1,4 +1,4 @@ -From 071139f7f1456ac6de6543418d848a01bce62c29 Mon Sep 17 00:00:00 2001 +From 48306a830d37e64b5275f48e25c315e658ee37e6 Mon Sep 17 00:00:00 2001 From: Alberto Planas Date: Mon, 5 Oct 2020 16:24:16 +0200 Subject: [PATCH] zypperpkg: ignore retcode 104 for search() @@ -6,14 +6,14 @@ Subject: [PATCH] zypperpkg: ignore retcode 104 for search() --- salt/modules/zypperpkg.py | 28 ++++++--- - tests/unit/modules/test_zypperpkg.py | 93 +++++++++++++++++++++------- - 2 files changed, 93 insertions(+), 28 deletions(-) + tests/unit/modules/test_zypperpkg.py | 89 +++++++++++++++++++++------- + 2 files changed, 90 insertions(+), 27 deletions(-) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py -index 3ee21ddbe5..1777bec031 100644 +index c7228bf712..4af29652d9 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py -@@ -92,6 +92,8 @@ class _Zypper: +@@ -99,6 +99,8 @@ class _Zypper: } LOCK_EXIT_CODE = 7 @@ -22,7 +22,7 @@ index 3ee21ddbe5..1777bec031 100644 XML_DIRECTIVES = ["-x", "--xmlout"] # ZYPPER_LOCK is not affected by --root ZYPPER_LOCK = "/var/run/zypp.pid" -@@ -122,6 +124,7 @@ class _Zypper: +@@ -129,6 +131,7 @@ class _Zypper: self.__no_raise = False self.__refresh = False self.__ignore_repo_failure = False @@ -30,7 +30,7 @@ index 3ee21ddbe5..1777bec031 100644 self.__systemd_scope = False self.__root = None -@@ -141,6 +144,9 @@ class _Zypper: +@@ -148,6 +151,9 @@ class _Zypper: # Ignore exit code for 106 (repo is not available) if "no_repo_failure" in kwargs: self.__ignore_repo_failure = kwargs["no_repo_failure"] @@ -40,7 +40,7 @@ index 3ee21ddbe5..1777bec031 100644 if "systemd_scope" in kwargs: self.__systemd_scope = kwargs["systemd_scope"] if "root" in kwargs: -@@ -299,6 +305,10 @@ class _Zypper: +@@ -306,6 +312,10 @@ class _Zypper: if self.__root: self.__cmd.extend(["--root", self.__root]) @@ -51,7 +51,7 @@ index 3ee21ddbe5..1777bec031 100644 self.__cmd.extend(args) kwargs["output_loglevel"] = "trace" kwargs["python_shell"] = False -@@ -436,9 +446,11 @@ class Wildcard: +@@ -447,9 +457,11 @@ class Wildcard: Get available versions of the package. :return: """ @@ -66,7 +66,7 @@ index 3ee21ddbe5..1777bec031 100644 if not solvables: raise CommandExecutionError( "No packages found matching '{}'".format(self.name) -@@ -1043,7 +1055,7 @@ def list_repo_pkgs(*args, **kwargs): +@@ -1054,7 +1066,7 @@ def list_repo_pkgs(*args, **kwargs): root = kwargs.get("root") or None for node in ( @@ -75,7 +75,7 @@ index 3ee21ddbe5..1777bec031 100644 .xml.call("se", "-s", *targets) .getElementsByTagName("solvable") ): -@@ -2422,7 +2434,9 @@ def owner(*paths, **kwargs): +@@ -2431,7 +2443,9 @@ def owner(*paths, **kwargs): def _get_visible_patterns(root=None): """Get all available patterns in the repo that are visible.""" patterns = {} @@ -86,7 +86,7 @@ index 3ee21ddbe5..1777bec031 100644 for element in search_patterns.getElementsByTagName("solvable"): installed = element.getAttribute("status") == "installed" patterns[element.getAttribute("name")] = { -@@ -2619,7 +2633,7 @@ def search(criteria, refresh=False, **kwargs): +@@ -2628,7 +2642,7 @@ def search(criteria, refresh=False, **kwargs): cmd.append(criteria) solvables = ( @@ -95,7 +95,7 @@ index 3ee21ddbe5..1777bec031 100644 .nolock.noraise.xml.call(*cmd) .getElementsByTagName("solvable") ) -@@ -2871,7 +2885,7 @@ def _get_patches(installed_only=False, root=None): +@@ -2880,7 +2894,7 @@ def _get_patches(installed_only=False, root=None): """ patches = {} for element in ( @@ -105,28 +105,19 @@ index 3ee21ddbe5..1777bec031 100644 .getElementsByTagName("solvable") ): diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py -index 919dbb9737..0ba5595d65 100644 +index 47fca906a7..671adc2779 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py -@@ -3,6 +3,8 @@ - """ - - -+import configparser -+import io - import os - from xml.dom import minidom - -@@ -14,7 +16,7 @@ from salt.exceptions import CommandExecutionError - from salt.ext import six - from salt.ext.six.moves import configparser +@@ -14,7 +14,7 @@ import salt.utils.files + import salt.utils.pkg + from salt.exceptions import CommandExecutionError from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, Mock, call, patch +from tests.support.mock import MagicMock, Mock, call, mock_open, patch from tests.support.unit import TestCase -@@ -27,7 +29,10 @@ class ZyppCallMock: +@@ -27,7 +27,10 @@ class ZyppCallMock: def __call__(self, *args, **kwargs): # If the call is for a configuration modifier, we return self @@ -138,16 +129,7 @@ index 919dbb9737..0ba5595d65 100644 return self return MagicMock(return_value=self.__return_value)() -@@ -1516,7 +1521,7 @@ Repository 'DUMMY' not found by its alias, number, or URI. - """ - repos_cfg = configparser.ConfigParser() - for cfg in ["zypper-repo-1.cfg", "zypper-repo-2.cfg"]: -- repos_cfg.readfp(six.moves.StringIO(get_test_data(cfg))) -+ repos_cfg.readfp(io.StringIO(get_test_data(cfg))) - - for alias in repos_cfg.sections(): - r_info = zypper._get_repo_info(alias, repos_cfg=repos_cfg) -@@ -1782,8 +1787,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1801,8 +1804,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -159,7 +141,7 @@ index 919dbb9737..0ba5595d65 100644 wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "*" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ -@@ -1805,8 +1811,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1824,8 +1828,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -171,7 +153,7 @@ index 919dbb9737..0ba5595d65 100644 wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "16.2.*-2*" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ -@@ -1827,8 +1834,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1846,8 +1851,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -183,7 +165,7 @@ index 919dbb9737..0ba5595d65 100644 wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "16.2.5*" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ -@@ -1848,8 +1856,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1867,8 +1873,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -195,7 +177,7 @@ index 919dbb9737..0ba5595d65 100644 wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "*.1" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ -@@ -1870,8 +1879,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1889,8 +1896,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -207,7 +189,7 @@ index 919dbb9737..0ba5595d65 100644 assert zypper.Wildcard(_zpr)("libzypp", "16.2.4*") == "16.2.4-19.5" assert zypper.Wildcard(_zpr)("libzypp", "16.2*") == "16.2.5-25.1" assert zypper.Wildcard(_zpr)("libzypp", "*6-*") == "17.2.6-27.9.1" -@@ -1890,8 +1900,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1909,8 +1917,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -220,7 +202,7 @@ index 919dbb9737..0ba5595d65 100644 assert zypper.Wildcard(_zpr)("libzypp", None) is None def test_wildcard_to_query_typecheck(self): -@@ -1907,8 +1919,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1926,8 +1936,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -232,7 +214,7 @@ index 919dbb9737..0ba5595d65 100644 assert isinstance(zypper.Wildcard(_zpr)("libzypp", "*.1"), str) def test_wildcard_to_query_condition_preservation(self): -@@ -1924,8 +1937,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1943,8 +1954,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -244,7 +226,7 @@ index 919dbb9737..0ba5595d65 100644 for op in zypper.Wildcard.Z_OP: assert zypper.Wildcard(_zpr)( -@@ -1951,8 +1965,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. +@@ -1970,8 +1982,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. """ @@ -257,7 +239,7 @@ index 919dbb9737..0ba5595d65 100644 with self.assertRaises(CommandExecutionError): for op in [">>", "==", "<<", "+"]: zypper.Wildcard(_zpr)("libzypp", "{}*.1".format(op)) -@@ -2044,3 +2060,38 @@ pattern() = package-c""" +@@ -2063,3 +2077,38 @@ pattern() = package-c""" with patch("salt.modules.zypperpkg.__zypper__", zypper_mock): assert zypper.services_need_restart() == expected zypper_mock(root=None).nolock.call.assert_called_with("ps", "-sss") @@ -297,6 +279,6 @@ index 919dbb9737..0ba5595d65 100644 + env={"ZYPP_READONLY_HACK": "1"}, + ) -- -2.33.0 +2.34.1