From 9ce4d6f2a0ae5e7d099b7ecac9990ef2d5c0fc89f40d7454bd3a17ecd7b1cb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Fri, 26 Apr 2019 10:09:06 +0000 Subject: [PATCH 1/6] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:258 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=133 --- _lastrevision | 2 +- ...ce_ping_timeout-and-batch_presence_p.patch | 46 ++++ ....volume_infos-and-virt.volume_delete.patch | 84 +++--- ...in-parallel-to-avoid-blockings-bsc-1.patch | 77 ++++++ ...zypper-with-more-than-one-no-refresh.patch | 14 +- fix-async-batch-race-conditions.patch | 253 ++++++++++++++++++ mount-fix-extra-t-parameter.patch | 30 +++ salt.changes | 23 ++ salt.spec | 25 +- 9 files changed, 508 insertions(+), 46 deletions(-) create mode 100644 add-batch_presence_ping_timeout-and-batch_presence_p.patch create mode 100644 calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch create mode 100644 fix-async-batch-race-conditions.patch create mode 100644 mount-fix-extra-t-parameter.patch diff --git a/_lastrevision b/_lastrevision index 4543d1d..80c5c4f 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -65afa65b0d69f90c1cd716474cdddcdc98751274 \ No newline at end of file +8bef2df02d5c189cef341f81679fda05b6c02930 \ No newline at end of file diff --git a/add-batch_presence_ping_timeout-and-batch_presence_p.patch b/add-batch_presence_ping_timeout-and-batch_presence_p.patch new file mode 100644 index 0000000..4ac8fc8 --- /dev/null +++ b/add-batch_presence_ping_timeout-and-batch_presence_p.patch @@ -0,0 +1,46 @@ +From 902a3527415807448be0aa28a651374a189d102c Mon Sep 17 00:00:00 2001 +From: Marcelo Chiaradia +Date: Thu, 4 Apr 2019 13:57:38 +0200 +Subject: [PATCH] Add 'batch_presence_ping_timeout' and + 'batch_presence_ping_gather_job_timeout' parameters for synchronous batching + +--- + salt/cli/batch.py | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/salt/cli/batch.py b/salt/cli/batch.py +index 4bd07f584a..ce239215cb 100644 +--- a/salt/cli/batch.py ++++ b/salt/cli/batch.py +@@ -83,6 +83,9 @@ def batch_get_opts( + if key not in opts: + opts[key] = val + ++ opts['batch_presence_ping_timeout'] = kwargs.get('batch_presence_ping_timeout', opts['timeout']) ++ opts['batch_presence_ping_gather_job_timeout'] = kwargs.get('batch_presence_ping_gather_job_timeout', opts['gather_job_timeout']) ++ + return opts + + +@@ -119,7 +122,7 @@ class Batch(object): + args = [self.opts['tgt'], + 'test.ping', + [], +- self.opts['timeout'], ++ self.opts.get('batch_presence_ping_timeout', self.opts['timeout']), + ] + + selected_target_option = self.opts.get('selected_target_option', None) +@@ -130,7 +133,7 @@ class Batch(object): + + self.pub_kwargs['yield_pub_data'] = True + ping_gen = self.local.cmd_iter(*args, +- gather_job_timeout=self.opts['gather_job_timeout'], ++ gather_job_timeout=self.opts.get('batch_presence_ping_gather_job_timeout', self.opts['gather_job_timeout']), + **self.pub_kwargs) + + # Broadcast to targets +-- +2.20.1 + + diff --git a/add-virt.volume_infos-and-virt.volume_delete.patch b/add-virt.volume_infos-and-virt.volume_delete.patch index c2d5622..a583d2e 100644 --- a/add-virt.volume_infos-and-virt.volume_delete.patch +++ b/add-virt.volume_infos-and-virt.volume_delete.patch @@ -1,4 +1,4 @@ -From 2536ee56bd0060c024994f97388f9975ccbe1ee1 Mon Sep 17 00:00:00 2001 +From 5e202207d02d2bf4860cc5487ed19f9d835993d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= Date: Fri, 15 Feb 2019 17:28:00 +0100 Subject: [PATCH] Add virt.volume_infos() and virt.volume_delete() @@ -11,15 +11,15 @@ names of the virtual machines using the volumes of file type. virt.volume_delete() allows removing a given volume. --- - salt/modules/virt.py | 113 ++++++++++++++++++++ - tests/unit/modules/test_virt.py | 184 ++++++++++++++++++++++++++++++++ - 2 files changed, 297 insertions(+) + salt/modules/virt.py | 126 +++++++++++++++++++++ + tests/unit/modules/test_virt.py | 195 ++++++++++++++++++++++++++++++++ + 2 files changed, 321 insertions(+) diff --git a/salt/modules/virt.py b/salt/modules/virt.py -index 0921122a8a..4a301f289c 100644 +index 0921122a8a..17039444c4 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py -@@ -4988,3 +4988,116 @@ def pool_list_volumes(name, **kwargs): +@@ -4988,3 +4988,129 @@ def pool_list_volumes(name, **kwargs): return pool.listVolumes() finally: conn.close() @@ -34,6 +34,19 @@ index 0921122a8a..4a301f289c 100644 + return pool_obj.storageVolLookupByName(vol) + + ++def _is_valid_volume(vol): ++ ''' ++ Checks whether a volume is valid for further use since those may have disappeared since ++ the last pool refresh. ++ ''' ++ try: ++ # Getting info on an invalid volume raises error ++ vol.info() ++ return True ++ except libvirt.libvirtError as err: ++ return False ++ ++ +def _get_all_volumes_paths(conn): + ''' + Extract the path and backing stores path of all volumes. @@ -42,17 +55,17 @@ index 0921122a8a..4a301f289c 100644 + ''' + volumes = [vol for l in [obj.listAllVolumes() for obj in conn.listAllStoragePools()] for vol in l] + return {vol.path(): [path.text for path in ElementTree.fromstring(vol.XMLDesc()).findall('.//backingStore/path')] -+ for vol in volumes} ++ for vol in volumes if _is_valid_volume(vol)} + + -+def volume_infos(pool, volume, **kwargs): ++def volume_infos(pool=None, volume=None, **kwargs): + ''' + Provide details on a storage volume. If no volume name is provided, the infos + all the volumes contained in the pool are provided. If no pool is provided, + the infos of the volumes of all pools are output. + -+ :param pool: libvirt storage pool name -+ :param volume: name of the volume to get infos from ++ :param pool: libvirt storage pool name (default: ``None``) ++ :param volume: name of the volume to get infos from (default: ``None``) + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults @@ -102,7 +115,7 @@ index 0921122a8a..4a301f289c 100644 + pools = [obj for obj in conn.listAllStoragePools() if pool is None or obj.name() == pool] + vols = {pool_obj.name(): {vol.name(): _volume_extract_infos(vol) + for vol in pool_obj.listAllVolumes() -+ if volume is None or vol.name() == volume} ++ if (volume is None or vol.name() == volume) and _is_valid_volume(vol)} + for pool_obj in pools} + return {pool_name: volumes for (pool_name, volumes) in vols.items() if volumes} + except libvirt.libvirtError as err: @@ -137,10 +150,10 @@ index 0921122a8a..4a301f289c 100644 + finally: + conn.close() diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py -index bd34962a6a..55005f1d04 100644 +index bd34962a6a..14e51e1e2a 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py -@@ -2698,3 +2698,187 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): +@@ -2698,3 +2698,198 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.storagePoolLookupByName.return_value = mock_pool # pylint: enable=no-member self.assertEqual(names, virt.pool_list_volumes('default')) @@ -203,6 +216,13 @@ index bd34962a6a..55005f1d04 100644 + 'name': 'pool1', + 'volumes': [ + { ++ 'key': '/key/of/vol0bad', ++ 'name': 'vol0bad', ++ 'path': '/path/to/vol0bad.qcow2', ++ 'info': None, ++ 'backingStore': None ++ }, ++ { + 'key': '/key/of/vol1', + 'name': 'vol1', + 'path': '/path/to/vol1.qcow2', @@ -230,23 +250,27 @@ index bd34962a6a..55005f1d04 100644 + mock_volume.name.return_value = vol_data['name'] + mock_volume.key.return_value = vol_data['key'] + mock_volume.path.return_value = '/path/to/{0}.qcow2'.format(vol_data['name']) -+ mock_volume.info.return_value = vol_data['info'] -+ backing_store = ''' -+ -+ qcow2 -+ {0} -+ -+ '''.format(vol_data['backingStore']) if vol_data['backingStore'] else '' -+ mock_volume.XMLDesc.return_value = ''' -+ -+ {0} -+ -+ qcow2 -+ /path/to/{0}.qcow2 -+ -+ {1} -+ -+ '''.format(vol_data['name'], backing_store) ++ if vol_data['info']: ++ mock_volume.info.return_value = vol_data['info'] ++ backing_store = ''' ++ ++ qcow2 ++ {0} ++ ++ '''.format(vol_data['backingStore']) if vol_data['backingStore'] else '' ++ mock_volume.XMLDesc.return_value = ''' ++ ++ {0} ++ ++ qcow2 ++ /path/to/{0}.qcow2 ++ ++ {1} ++ ++ '''.format(vol_data['name'], backing_store) ++ else: ++ mock_volume.info.side_effect = self.mock_libvirt.libvirtError('No such volume') ++ mock_volume.XMLDesc.side_effect = self.mock_libvirt.libvirtError('No such volume') + mock_volumes.append(mock_volume) + # pylint: enable=no-member + mock_pool.listAllVolumes.return_value = mock_volumes # pylint: disable=no-member diff --git a/calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch b/calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch new file mode 100644 index 0000000..685c77b --- /dev/null +++ b/calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch @@ -0,0 +1,77 @@ +From 722b9395a6489da7626e6a388c78bf8e8812190e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Fri, 12 Apr 2019 16:47:03 +0100 +Subject: [PATCH] Calculate FQDNs in parallel to avoid blockings (bsc#1129079) + +Fix pylint issue +--- + salt/grains/core.py | 31 ++++++++++++++++++++++++++----- + 1 file changed, 26 insertions(+), 5 deletions(-) + +diff --git a/salt/grains/core.py b/salt/grains/core.py +index 05a9d5035d..796458939d 100644 +--- a/salt/grains/core.py ++++ b/salt/grains/core.py +@@ -20,11 +20,14 @@ import platform + import logging + import locale + import uuid ++import time + import zlib + from errno import EACCES, EPERM + import datetime + import warnings + ++from multiprocessing.dummy import Pool as ThreadPool ++ + # pylint: disable=import-error + try: + import dateutil.tz +@@ -2200,13 +2203,10 @@ def fqdns(): + grains = {} + fqdns = set() + +- addresses = salt.utils.network.ip_addrs(include_loopback=False, interface_data=_get_interfaces()) +- addresses.extend(salt.utils.network.ip_addrs6(include_loopback=False, interface_data=_get_interfaces())) +- err_message = 'Exception during resolving address: %s' +- for ip in addresses: ++ def _lookup_fqdn(ip): + try: + name, aliaslist, addresslist = socket.gethostbyaddr(ip) +- fqdns.update([socket.getfqdn(name)] + [als for als in aliaslist if salt.utils.network.is_fqdn(als)]) ++ return [socket.getfqdn(name)] + [als for als in aliaslist if salt.utils.network.is_fqdn(als)] + except socket.herror as err: + if err.errno == 0: + # No FQDN for this IP address, so we don't need to know this all the time. +@@ -2216,6 +2216,27 @@ def fqdns(): + except (socket.error, socket.gaierror, socket.timeout) as err: + log.error(err_message, err) + ++ start = time.time() ++ ++ addresses = salt.utils.network.ip_addrs(include_loopback=False, interface_data=_get_interfaces()) ++ addresses.extend(salt.utils.network.ip_addrs6(include_loopback=False, interface_data=_get_interfaces())) ++ err_message = 'Exception during resolving address: %s' ++ ++ # Create a ThreadPool to process the underlying calls to 'socket.gethostbyaddr' in parallel. ++ # This avoid blocking the execution when the "fqdn" is not defined for certains IP addresses, which was causing ++ # that "socket.timeout" was reached multiple times secuencially, blocking execution for several seconds. ++ pool = ThreadPool(8) ++ results = pool.map(_lookup_fqdn, addresses) ++ pool.close() ++ pool.join() ++ ++ for item in results: ++ if item: ++ fqdns.update(item) ++ ++ elapsed = time.time() - start ++ log.debug('Elapsed time getting FQDNs: {} seconds'.format(elapsed)) ++ + return {"fqdns": sorted(list(fqdns))} + + +-- +2.17.1 + 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 index aaa65bf..f8a3ebe 100644 --- a/don-t-call-zypper-with-more-than-one-no-refresh.patch +++ b/don-t-call-zypper-with-more-than-one-no-refresh.patch @@ -1,4 +1,4 @@ -From 1c3f8f32d475701e8b7fab64b8cb9dcd44b587d4 Mon Sep 17 00:00:00 2001 +From 5e0fe08c6afd75a7d65d6ccd6cf6b4b197fb1064 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 @@ -11,23 +11,23 @@ passed twice. Make sure we won't hit this. 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py -index c442337c58..bab9e22dec 100644 +index 92e7052020..7ac0df26c6 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py -@@ -291,7 +291,7 @@ class _Zypper(object): +@@ -282,7 +282,7 @@ class _Zypper(object): 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]) + + self.__cmd.extend(args) diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py -index e7474ff777..9d109a431d 100644 +index f586c23fd0..5c5091a570 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py -@@ -141,7 +141,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): +@@ -138,7 +138,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): self.assertEqual(zypper.__zypper__.call('foo'), stdout_xml_snippet) self.assertEqual(len(sniffer.calls), 1) diff --git a/fix-async-batch-race-conditions.patch b/fix-async-batch-race-conditions.patch new file mode 100644 index 0000000..6be04b5 --- /dev/null +++ b/fix-async-batch-race-conditions.patch @@ -0,0 +1,253 @@ +From 33c5e10c2912f584243d29c764c2c6cca86edf4a Mon Sep 17 00:00:00 2001 +From: Mihai Dinca +Date: Thu, 11 Apr 2019 15:57:59 +0200 +Subject: [PATCH] Fix async batch race conditions + +Close batching when there is no next batch +--- + salt/cli/batch_async.py | 80 +++++++++++++++--------------- + tests/unit/cli/test_batch_async.py | 35 ++++++------- + 2 files changed, 54 insertions(+), 61 deletions(-) + +diff --git a/salt/cli/batch_async.py b/salt/cli/batch_async.py +index 3160d46d8b..9c20b2fc6e 100644 +--- a/salt/cli/batch_async.py ++++ b/salt/cli/batch_async.py +@@ -37,14 +37,14 @@ class BatchAsync(object): + - tag: salt/batch//start + - data: { + "available_minions": self.minions, +- "down_minions": self.down_minions ++ "down_minions": targeted_minions - presence_ping_minions + } + + When the batch ends, an `done` event is fired: + - tag: salt/batch//done + - data: { + "available_minions": self.minions, +- "down_minions": self.down_minions, ++ "down_minions": targeted_minions - presence_ping_minions + "done_minions": self.done_minions, + "timedout_minions": self.timedout_minions + } +@@ -67,7 +67,7 @@ class BatchAsync(object): + self.eauth = batch_get_eauth(clear_load['kwargs']) + self.metadata = clear_load['kwargs'].get('metadata', {}) + self.minions = set() +- self.down_minions = set() ++ self.targeted_minions = set() + self.timedout_minions = set() + self.done_minions = set() + self.active = set() +@@ -108,8 +108,7 @@ class BatchAsync(object): + minion = data['id'] + if op == 'ping_return': + self.minions.add(minion) +- self.down_minions.remove(minion) +- if not self.down_minions: ++ if self.targeted_minions == self.minions: + self.event.io_loop.spawn_callback(self.start_batch) + elif op == 'find_job_return': + self.find_job_returned.add(minion) +@@ -120,9 +119,6 @@ class BatchAsync(object): + # call later so that we maybe gather more returns + self.event.io_loop.call_later(self.batch_delay, self.schedule_next) + +- if self.initialized and self.done_minions == self.minions.difference(self.timedout_minions): +- self.end_batch() +- + def _get_next(self): + to_run = self.minions.difference( + self.done_minions).difference( +@@ -135,16 +131,13 @@ class BatchAsync(object): + return set(list(to_run)[:next_batch_size]) + + @tornado.gen.coroutine +- def check_find_job(self, minions): +- did_not_return = minions.difference(self.find_job_returned) +- if did_not_return: +- for minion in did_not_return: +- if minion in self.find_job_returned: +- self.find_job_returned.remove(minion) +- if minion in self.active: +- self.active.remove(minion) +- self.timedout_minions.add(minion) +- running = minions.difference(did_not_return).difference(self.done_minions).difference(self.timedout_minions) ++ def check_find_job(self, batch_minions): ++ timedout_minions = batch_minions.difference(self.find_job_returned).difference(self.done_minions) ++ self.timedout_minions = self.timedout_minions.union(timedout_minions) ++ self.active = self.active.difference(self.timedout_minions) ++ running = batch_minions.difference(self.done_minions).difference(self.timedout_minions) ++ if timedout_minions: ++ self.event.io_loop.call_later(self.batch_delay, self.schedule_next) + if running: + self.event.io_loop.add_callback(self.find_job, running) + +@@ -183,7 +176,7 @@ class BatchAsync(object): + jid=self.ping_jid, + metadata=self.metadata, + **self.eauth) +- self.down_minions = set(ping_return['minions']) ++ self.targeted_minions = set(ping_return['minions']) + + @tornado.gen.coroutine + def start_batch(self): +@@ -192,36 +185,43 @@ class BatchAsync(object): + self.initialized = True + data = { + "available_minions": self.minions, +- "down_minions": self.down_minions, ++ "down_minions": self.targeted_minions.difference(self.minions), + "metadata": self.metadata + } + self.event.fire_event(data, "salt/batch/{0}/start".format(self.batch_jid)) + yield self.schedule_next() + + def end_batch(self): +- data = { +- "available_minions": self.minions, +- "down_minions": self.down_minions, +- "done_minions": self.done_minions, +- "timedout_minions": self.timedout_minions, +- "metadata": self.metadata +- } +- self.event.fire_event(data, "salt/batch/{0}/done".format(self.batch_jid)) +- self.event.remove_event_handler(self.__event_handler) ++ left = self.minions.symmetric_difference(self.done_minions.union(self.timedout_minions)) ++ if not left: ++ data = { ++ "available_minions": self.minions, ++ "down_minions": self.targeted_minions.difference(self.minions), ++ "done_minions": self.done_minions, ++ "timedout_minions": self.timedout_minions, ++ "metadata": self.metadata ++ } ++ self.event.fire_event(data, "salt/batch/{0}/done".format(self.batch_jid)) ++ self.event.remove_event_handler(self.__event_handler) + + @tornado.gen.coroutine + def schedule_next(self): + next_batch = self._get_next() + if next_batch: +- yield self.local.run_job_async( +- next_batch, +- self.opts['fun'], +- self.opts['arg'], +- 'list', +- raw=self.opts.get('raw', False), +- ret=self.opts.get('return', ''), +- gather_job_timeout=self.opts['gather_job_timeout'], +- jid=self.batch_jid, +- metadata=self.metadata) +- self.event.io_loop.call_later(self.opts['timeout'], self.find_job, set(next_batch)) + self.active = self.active.union(next_batch) ++ try: ++ yield self.local.run_job_async( ++ next_batch, ++ self.opts['fun'], ++ self.opts['arg'], ++ 'list', ++ raw=self.opts.get('raw', False), ++ ret=self.opts.get('return', ''), ++ gather_job_timeout=self.opts['gather_job_timeout'], ++ jid=self.batch_jid, ++ metadata=self.metadata) ++ self.event.io_loop.call_later(self.opts['timeout'], self.find_job, set(next_batch)) ++ except Exception as ex: ++ self.active = self.active.difference(next_batch) ++ else: ++ self.end_batch() +diff --git a/tests/unit/cli/test_batch_async.py b/tests/unit/cli/test_batch_async.py +index f65b6a06c3..d519157d92 100644 +--- a/tests/unit/cli/test_batch_async.py ++++ b/tests/unit/cli/test_batch_async.py +@@ -75,8 +75,8 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + self.batch.local.run_job_async.call_args[0], + ('*', 'test.ping', [], 'glob') + ) +- # assert down_minions == all minions matched by tgt +- self.assertEqual(self.batch.down_minions, set(['foo', 'bar'])) ++ # assert targeted_minions == all minions matched by tgt ++ self.assertEqual(self.batch.targeted_minions, set(['foo', 'bar'])) + + @tornado.testing.gen_test + def test_batch_start_on_gather_job_timeout(self): +@@ -121,7 +121,10 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + self.assertEqual(len(self.batch.schedule_next.mock_calls), 1) + + def test_batch_fire_done_event(self): ++ self.batch.targeted_minions = {'foo', 'baz', 'bar'} + self.batch.minions = set(['foo', 'bar']) ++ self.batch.done_minions = {'foo'} ++ self.batch.timedout_minions = {'bar'} + self.batch.event = MagicMock() + self.batch.metadata = {'mykey': 'myvalue'} + self.batch.end_batch() +@@ -130,9 +133,9 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + ( + { + 'available_minions': set(['foo', 'bar']), +- 'done_minions': set(), +- 'down_minions': set(), +- 'timedout_minions': set(), ++ 'done_minions': self.batch.done_minions, ++ 'down_minions': {'baz'}, ++ 'timedout_minions': self.batch.timedout_minions, + 'metadata': self.batch.metadata + }, + "salt/batch/1235/done" +@@ -212,7 +215,7 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + self.assertEqual(self.batch._get_next(), set()) + + def test_batch__event_handler_ping_return(self): +- self.batch.down_minions = {'foo'} ++ self.batch.targeted_minions = {'foo'} + self.batch.event = MagicMock( + unpack=MagicMock(return_value=('salt/job/1234/ret/foo', {'id': 'foo'}))) + self.batch.start() +@@ -222,7 +225,7 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + self.assertEqual(self.batch.done_minions, set()) + + def test_batch__event_handler_call_start_batch_when_all_pings_return(self): +- self.batch.down_minions = {'foo'} ++ self.batch.targeted_minions = {'foo'} + self.batch.event = MagicMock( + unpack=MagicMock(return_value=('salt/job/1234/ret/foo', {'id': 'foo'}))) + self.batch.start() +@@ -232,7 +235,7 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + (self.batch.start_batch,)) + + def test_batch__event_handler_not_call_start_batch_when_not_all_pings_return(self): +- self.batch.down_minions = {'foo', 'bar'} ++ self.batch.targeted_minions = {'foo', 'bar'} + self.batch.event = MagicMock( + unpack=MagicMock(return_value=('salt/job/1234/ret/foo', {'id': 'foo'}))) + self.batch.start() +@@ -260,20 +263,10 @@ class AsyncBatchTestCase(AsyncTestCase, TestCase): + self.assertEqual(self.batch.find_job_returned, {'foo'}) + + @tornado.testing.gen_test +- def test_batch__event_handler_end_batch(self): +- self.batch.event = MagicMock( +- unpack=MagicMock(return_value=('salt/job/not-my-jid/ret/foo', {'id': 'foo'}))) +- future = tornado.gen.Future() +- future.set_result({'minions': ['foo', 'bar', 'baz']}) +- self.batch.local.run_job_async.return_value = future +- self.batch.start() +- self.batch.initialized = True +- self.assertEqual(self.batch.down_minions, {'foo', 'bar', 'baz'}) ++ def test_batch_schedule_next_end_batch_when_no_next(self): + self.batch.end_batch = MagicMock() +- self.batch.minions = {'foo', 'bar', 'baz'} +- self.batch.done_minions = {'foo', 'bar'} +- self.batch.timedout_minions = {'baz'} +- self.batch._BatchAsync__event_handler(MagicMock()) ++ self.batch._get_next = MagicMock(return_value={}) ++ self.batch.schedule_next() + self.assertEqual(len(self.batch.end_batch.mock_calls), 1) + + @tornado.testing.gen_test +-- +2.20.1 + + diff --git a/mount-fix-extra-t-parameter.patch b/mount-fix-extra-t-parameter.patch new file mode 100644 index 0000000..a775996 --- /dev/null +++ b/mount-fix-extra-t-parameter.patch @@ -0,0 +1,30 @@ +From 215d8d9c8f872b510a1c3fbb19ab4e91bc96bb64 Mon Sep 17 00:00:00 2001 +From: Alberto Planas +Date: Thu, 28 Feb 2019 15:45:28 +0100 +Subject: [PATCH] mount: fix extra -t parameter + +If 'fstype' parameter is not set in Linux environments, salt will +build a mount command with an empty -t value, making the command +fail. +--- + salt/modules/mount.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/mount.py b/salt/modules/mount.py +index 4ba370e5b3..e807b1729e 100644 +--- a/salt/modules/mount.py ++++ b/salt/modules/mount.py +@@ -1218,7 +1218,8 @@ def mount(name, device, mkmnt=False, fstype='', opts='defaults', user=None, util + if fstype: + args += ' -v {0}'.format(fstype) + else: +- args += ' -t {0}'.format(fstype) ++ if fstype: ++ args += ' -t {0}'.format(fstype) + cmd = 'mount {0} {1} {2} '.format(args, device, name) + out = __salt__['cmd.run_all'](cmd, runas=user, python_shell=False) + if out['retcode']: +-- +2.20.1 + + diff --git a/salt.changes b/salt.changes index 00f5cae..48d54ff 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,26 @@ +------------------------------------------------------------------- +Fri Apr 26 10:00:01 UTC 2019 - mdinca@suse.de + +- Fix batch/batch-async related issues +- Calculate FQDNs in parallel to avoid blockings (bsc#1129079) +- Incorporate virt.volume_info fixes (PR#131) +- Re-adds patch because of increased offset due to previous patch removal +- Removing patch to add root parameter to zypper module +- Fix for -t parameter in mount module + +- Added: + * mount-fix-extra-t-parameter.patch + * add-batch_presence_ping_timeout-and-batch_presence_p.patch + * fix-async-batch-race-conditions.patch + * calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch + +- Modified: + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * add-virt.volume_infos-and-virt.volume_delete.patch + +- Removed: + * zypper-add-root-configuration-parameter.patch + ------------------------------------------------------------------- Thu Feb 28 16:18:38 UTC 2019 - Jochen Breuer diff --git a/salt.spec b/salt.spec index 743737d..66d4443 100644 --- a/salt.spec +++ b/salt.spec @@ -141,22 +141,28 @@ Patch37: return-the-expected-powerpc-os-arch-bsc-1117995.patch Patch38: remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51119 Patch39: fix-issue-2068-test.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/50125 -Patch40: zypper-add-root-configuration-parameter.patch # PATCH_FIX_OPENSUSE: Temporary fix allowing "id_" and "force" params while upstrem figures it out -Patch41: temporary-fix-extend-the-whitelist-of-allowed-comman.patch +Patch40: temporary-fix-extend-the-whitelist-of-allowed-comman.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51382 -Patch42: don-t-call-zypper-with-more-than-one-no-refresh.patch +Patch41: don-t-call-zypper-with-more-than-one-no-refresh.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/50109 # PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/121 -Patch43: add-virt.all_capabilities.patch +Patch42: add-virt.all_capabilities.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51691 -Patch44: add-virt.volume_infos-and-virt.volume_delete.patch +Patch43: add-virt.volume_infos-and-virt.volume_delete.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51384 -Patch45: include-aliases-in-the-fqdns-grains.patch +Patch44: include-aliases-in-the-fqdns-grains.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/50546 # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51863 -Patch46: async-batch-implementation.patch +Patch45: async-batch-implementation.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51905 +Patch46: mount-fix-extra-t-parameter.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52527 +Patch47: calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch +#PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/139 +Patch48: fix-async-batch-race-conditions.patch +#PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/141 +Patch49: add-batch_presence_ping_timeout-and-batch_presence_p.patch # BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -661,6 +667,9 @@ cp %{S:5} ./.travis.yml %patch44 -p1 %patch45 -p1 %patch46 -p1 +%patch47 -p1 +%patch48 -p1 +%patch49 -p1 %build %if 0%{?build_py2} From 39e9bfc82d76f3446ec338c52482ae4a597690ffdb5050fad04377c93158c74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Fri, 26 Apr 2019 12:39:20 +0000 Subject: [PATCH 2/6] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:260 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=134 --- _lastrevision | 2 +- salt.changes | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/_lastrevision b/_lastrevision index 80c5c4f..3bc96d5 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -8bef2df02d5c189cef341f81679fda05b6c02930 \ No newline at end of file +bf25c6e7dd034e44a3d16de37485c1914d6a93f5 \ No newline at end of file diff --git a/salt.changes b/salt.changes index 48d54ff..1051813 100644 --- a/salt.changes +++ b/salt.changes @@ -1,6 +1,7 @@ ------------------------------------------------------------------- Fri Apr 26 10:00:01 UTC 2019 - mdinca@suse.de +- Update to 2019.2.0 complete (FATE#327138, bsc#1133523) - Fix batch/batch-async related issues - Calculate FQDNs in parallel to avoid blockings (bsc#1129079) - Incorporate virt.volume_info fixes (PR#131) From 5bab2422bad1def24b69c77e11f7fe1011f871792487e360847e3b363af302f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Fri, 26 Apr 2019 13:19:34 +0000 Subject: [PATCH 3/6] Remove unused patch OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=135 --- zypper-add-root-configuration-parameter.patch | 2147 ----------------- 1 file changed, 2147 deletions(-) delete mode 100644 zypper-add-root-configuration-parameter.patch diff --git a/zypper-add-root-configuration-parameter.patch b/zypper-add-root-configuration-parameter.patch deleted file mode 100644 index 69e513d..0000000 --- a/zypper-add-root-configuration-parameter.patch +++ /dev/null @@ -1,2147 +0,0 @@ -From e20116f09c1f68238008c13a0517a8d36a7be56a Mon Sep 17 00:00:00 2001 -From: Alberto Planas -Date: Wed, 17 Oct 2018 11:58:04 +0200 -Subject: [PATCH] zypper: add root configuration parameter - -Fix typo in comment - -lowpkg: add parameter to change root directory - -The CLI rpm command allows the --root parameter to change the -expected location where the rpm database can be found. - -This patch add a new optional parameter in the public interface -to allow the set of the new root location. - -Update the tests to use the extra parameter. - -Add root parameter into the zypper module - -The zypper CLI provides a way to change the path where zypper expect -to find the required configuration files and repositories. - -This feature is useful to bootstrap chroot environments, inspect -repositories and packages from locally mounted devices, or help -during the installation of a new OS from the SUSE family. - -This patch add the root optional parameter for each command in the -public interface, and fix the tests. - -pkg: Transfer optional parameters to lower levels. - -pkgrepo: Transfer optional parameters to lower levels. - -zypper: fix the reset after the call - -_Zypper class take note when a .call() is done, to clean up the data -when we access to some attribute. - -This produces a bug when two calls are one after another an we set -some attributes via the __call__ method, as whatever is set will be -cleared after the first attribute is accessed. - -For example: - -zypper.attrib.call(..) -zypper(root=root).otherattrib.call(..) - -The first call will set __called as True, and the reset of the inner -state of zypper will be cleared when otherattrib is accessed, -cleanning the status for __root. - -This patch makes sure to clean the status also during the __call__ -method, avoiding the cleanning when the attribute is accessed. - -zypper: add no_recommends parameter - -Add no_recommends parameter to install and upgrade actions. ---- - salt/modules/rpm_lowpkg.py | 101 +++++-- - salt/modules/zypperpkg.py | 390 ++++++++++++++++++-------- - salt/states/pkg.py | 28 +- - salt/states/pkgrepo.py | 14 +- - tests/unit/modules/test_rpm_lowpkg.py | 92 +++++- - tests/unit/modules/test_zypperpkg.py | 45 +-- - tests/unit/states/test_pkg.py | 7 +- - 7 files changed, 488 insertions(+), 189 deletions(-) - -diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py -index 893ae4f817..e577c4391a 100644 ---- a/salt/modules/rpm_lowpkg.py -+++ b/salt/modules/rpm_lowpkg.py -@@ -76,7 +76,7 @@ def bin_pkg_info(path, saltenv='base'): - minion so that it can be examined. - - saltenv : base -- Salt fileserver envrionment from which to retrieve the package. Ignored -+ Salt fileserver environment from which to retrieve the package. Ignored - if ``path`` is a local file path on the minion. - - CLI Example: -@@ -128,12 +128,15 @@ def bin_pkg_info(path, saltenv='base'): - return ret - - --def list_pkgs(*packages): -+def list_pkgs(*packages, **kwargs): - ''' - List the packages currently installed in a dict:: - - {'': ''} - -+ root -+ use root as top level directory (default: "/") -+ - CLI Example: - - .. code-block:: bash -@@ -141,8 +144,11 @@ def list_pkgs(*packages): - salt '*' lowpkg.list_pkgs - ''' - pkgs = {} -- cmd = ['rpm', '-q' if packages else '-qa', -- '--queryformat', r'%{NAME} %{VERSION}\n'] -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ cmd.extend(['-q' if packages else '-qa', -+ '--queryformat', r'%{NAME} %{VERSION}\n']) - if packages: - cmd.extend(packages) - out = __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False) -@@ -158,6 +164,9 @@ def verify(*packages, **kwargs): - ''' - Runs an rpm -Va on a system, and returns the results in a dict - -+ root -+ use root as top level directory (default: "/") -+ - Files with an attribute of config, doc, ghost, license or readme in the - package header can be ignored using the ``ignore_types`` keyword argument - -@@ -199,6 +208,8 @@ def verify(*packages, **kwargs): - verify_options = [x.strip() for x in six.text_type(verify_options).split(',')] - - cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) - cmd.extend(['--' + x for x in verify_options]) - if packages: - cmd.append('-V') -@@ -258,6 +269,9 @@ def modified(*packages, **flags): - - .. versionadded:: 2015.5.0 - -+ root -+ use root as top level directory (default: "/") -+ - CLI examples: - - .. code-block:: bash -@@ -266,10 +280,12 @@ def modified(*packages, **flags): - salt '*' lowpkg.modified httpd postfix - salt '*' lowpkg.modified - ''' -- ret = __salt__['cmd.run_all']( -- ['rpm', '-Va'] + list(packages), -- output_loglevel='trace', -- python_shell=False) -+ cmd = ['rpm'] -+ if flags.get('root'): -+ cmd.extend(['--root', flags.pop('root')]) -+ cmd.append('-Va') -+ cmd.extend(packages) -+ ret = __salt__['cmd.run_all'](cmd, output_loglevel='trace', python_shell=False) - - data = {} - -@@ -324,12 +340,15 @@ def modified(*packages, **flags): - return filtered_data - - --def file_list(*packages): -+def file_list(*packages, **kwargs): - ''' - List the files that belong to a package. Not specifying any packages will - return a list of _every_ file on the system's rpm database (not generally - recommended). - -+ root -+ use root as top level directory (default: "/") -+ - CLI Examples: - - .. code-block:: bash -@@ -338,12 +357,15 @@ def file_list(*packages): - salt '*' lowpkg.file_list httpd postfix - salt '*' lowpkg.file_list - ''' -- if not packages: -- cmd = ['rpm', '-qla'] -- else: -- cmd = ['rpm', '-ql'] -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ -+ cmd.append('-ql' if packages else '-qla') -+ if packages: - # Can't concatenate a tuple, must do a list.extend() - cmd.extend(packages) -+ - ret = __salt__['cmd.run']( - cmd, - output_loglevel='trace', -@@ -351,12 +373,15 @@ def file_list(*packages): - return {'errors': [], 'files': ret} - - --def file_dict(*packages): -+def file_dict(*packages, **kwargs): - ''' - List the files that belong to a package, sorted by group. Not specifying - any packages will return a list of _every_ file on the system's rpm - database (not generally recommended). - -+ root -+ use root as top level directory (default: "/") -+ - CLI Examples: - - .. code-block:: bash -@@ -368,8 +393,11 @@ def file_dict(*packages): - errors = [] - ret = {} - pkgs = {} -- cmd = ['rpm', '-q' if packages else '-qa', -- '--queryformat', r'%{NAME} %{VERSION}\n'] -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ cmd.extend(['-q' if packages else '-qa', -+ '--queryformat', r'%{NAME} %{VERSION}\n']) - if packages: - cmd.extend(packages) - out = __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False) -@@ -380,8 +408,10 @@ def file_dict(*packages): - comps = line.split() - pkgs[comps[0]] = {'version': comps[1]} - for pkg in pkgs: -- files = [] -- cmd = ['rpm', '-ql', pkg] -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ cmd.extend(['-ql', pkg]) - out = __salt__['cmd.run']( - ['rpm', '-ql', pkg], - output_loglevel='trace', -@@ -390,7 +420,7 @@ def file_dict(*packages): - return {'errors': errors, 'packages': ret} - - --def owner(*paths): -+def owner(*paths, **kwargs): - ''' - Return the name of the package that owns the file. Multiple file paths can - be passed. If a single path is passed, a string will be returned, -@@ -400,6 +430,9 @@ def owner(*paths): - If the file is not owned by a package, or is not present on the minion, - then an empty string will be returned for that path. - -+ root -+ use root as top level directory (default: "/") -+ - CLI Examples: - - .. code-block:: bash -@@ -411,7 +444,10 @@ def owner(*paths): - return '' - ret = {} - for path in paths: -- cmd = ['rpm', '-qf', '--queryformat', '%{name}', path] -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ cmd.extend(['-qf', '--queryformat', '%{name}', path]) - ret[path] = __salt__['cmd.run_stdout'](cmd, - output_loglevel='trace', - python_shell=False) -@@ -471,6 +507,9 @@ def info(*packages, **kwargs): - :param all_versions: - Return information for all installed versions of the packages - -+ :param root: -+ use root as top level directory (default: "/") -+ - :return: - - CLI example: -@@ -493,7 +532,14 @@ def info(*packages, **kwargs): - else: - size_tag = '%{SIZE}' - -- cmd = packages and "rpm -q {0}".format(' '.join(packages)) or "rpm -qa" -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ if packages: -+ cmd.append('-q') -+ cmd.extend(packages) -+ else: -+ cmd.append('-qa') - - # Construct query format - attr_map = { -@@ -544,6 +590,7 @@ def info(*packages, **kwargs): - query.append(attr_map['description']) - query.append("-----\\n") - -+ cmd = ' '.join(cmd) - call = __salt__['cmd.run_all'](cmd + (" --queryformat '{0}'".format(''.join(query))), - output_loglevel='trace', env={'TZ': 'UTC'}, clean_env=True) - if call['retcode'] != 0: -@@ -744,10 +791,13 @@ def version_cmp(ver1, ver2, ignore_epoch=False): - return salt.utils.versions.version_cmp(ver1, ver2, ignore_epoch=False) - - --def checksum(*paths): -+def checksum(*paths, **kwargs): - ''' - Return if the signature of a RPM file is valid. - -+ root -+ use root as top level directory (default: "/") -+ - CLI Example: - - .. code-block:: bash -@@ -760,9 +810,14 @@ def checksum(*paths): - if not paths: - raise CommandExecutionError("No package files has been specified.") - -+ cmd = ['rpm'] -+ if kwargs.get('root'): -+ cmd.extend(['--root', kwargs['root']]) -+ cmd.extend(['-K', '--quiet']) - for package_file in paths: -+ cmd_ = cmd + [package_file] - ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and -- not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file], -+ not __salt__['cmd.retcode'](cmd_, - ignore_retcode=True, - output_loglevel='trace', - python_shell=False)) -diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py -index 92e7052020..c442337c58 100644 ---- a/salt/modules/zypperpkg.py -+++ b/salt/modules/zypperpkg.py -@@ -99,6 +99,7 @@ class _Zypper(object): - - LOCK_EXIT_CODE = 7 - XML_DIRECTIVES = ['-x', '--xmlout'] -+ # ZYPPER_LOCK is not affected by --root - ZYPPER_LOCK = '/var/run/zypp.pid' - TAG_RELEASED = 'zypper/released' - TAG_BLOCKED = 'zypper/blocked' -@@ -107,7 +108,6 @@ class _Zypper(object): - ''' - Constructor - ''' -- self.__called = False - self._reset() - - def _reset(self): -@@ -129,6 +129,10 @@ class _Zypper(object): - self.__refresh = False - self.__ignore_repo_failure = False - self.__systemd_scope = False -+ self.__root = None -+ -+ # Call status -+ self.__called = False - - def __call__(self, *args, **kwargs): - ''' -@@ -136,11 +140,17 @@ class _Zypper(object): - :param kwargs: - :return: - ''' -+ # Reset after the call -+ if self.__called: -+ self._reset() -+ - # Ignore exit code for 106 (repo is not available) - if 'no_repo_failure' in kwargs: - self.__ignore_repo_failure = kwargs['no_repo_failure'] - if 'systemd_scope' in kwargs: - self.__systemd_scope = kwargs['systemd_scope'] -+ if 'root' in kwargs: -+ self.__root = kwargs['root'] - return self - - def __getattr__(self, item): -@@ -153,7 +163,6 @@ class _Zypper(object): - # Reset after the call - if self.__called: - self._reset() -- self.__called = False - - if item == 'xml': - self.__xml = True -@@ -284,6 +293,8 @@ class _Zypper(object): - self.__cmd.append('--xmlout') - if not self.__refresh: - self.__cmd.append('--no-refresh') -+ if self.__root: -+ self.__cmd.extend(['--root', self.__root]) - - self.__cmd.extend(args) - kwargs['output_loglevel'] = 'trace' -@@ -442,7 +453,7 @@ def _clean_cache(): - __context__.pop(cache_name, None) - - --def list_upgrades(refresh=True, **kwargs): -+def list_upgrades(refresh=True, root=None, **kwargs): - ''' - List all available package upgrades on this system - -@@ -451,6 +462,9 @@ def list_upgrades(refresh=True, **kwargs): - If set to False it depends on zypper if a refresh is - executed. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -458,7 +472,7 @@ def list_upgrades(refresh=True, **kwargs): - salt '*' pkg.list_upgrades - ''' - if refresh: -- refresh_db() -+ refresh_db(root) - - ret = dict() - cmd = ['list-updates'] -@@ -467,7 +481,7 @@ def list_upgrades(refresh=True, **kwargs): - if not isinstance(repo_name, six.string_types): - repo_name = six.text_type(repo_name) - cmd.extend(['--repo', repo_name]) -- for update_node in __zypper__.nolock.xml.call(*cmd).getElementsByTagName('update'): -+ for update_node in __zypper__(root=root).nolock.xml.call(*cmd).getElementsByTagName('update'): - if update_node.getAttribute('kind') == 'package': - ret[update_node.getAttribute('name')] = update_node.getAttribute('edition') - -@@ -504,6 +518,9 @@ def info_installed(*names, **kwargs): - :param all_versions: - Include information for all versions of the packages installed on the minion. - -+ :param root: -+ Operate on a different root directory. -+ - CLI example: - - .. code-block:: bash -@@ -544,6 +561,9 @@ def info_available(*names, **kwargs): - If set to False it depends on zypper if a refresh is - executed or not. - -+ root -+ operate on a different root directory. -+ - CLI example: - - .. code-block:: bash -@@ -558,9 +578,11 @@ def info_available(*names, **kwargs): - else: - names = sorted(list(set(names))) - -+ root = kwargs.get('root', None) -+ - # Refresh db before extracting the latest package - if kwargs.get('refresh', True): -- refresh_db() -+ refresh_db(root) - - pkg_info = [] - batch = names[:] -@@ -569,7 +591,8 @@ def info_available(*names, **kwargs): - # Run in batches - while batch: - pkg_info.extend(re.split(r"Information for package*", -- __zypper__.nolock.call('info', '-t', 'package', *batch[:batch_size]))) -+ __zypper__(root=root).nolock.call('info', '-t', 'package', -+ *batch[:batch_size]))) - batch = batch[batch_size:] - - for pkg_data in pkg_info: -@@ -629,6 +652,9 @@ def latest_version(*names, **kwargs): - If set to False it depends on zypper if a refresh is - executed or not. - -+ root -+ operate on a different root directory. -+ - CLI example: - - .. code-block:: bash -@@ -671,6 +697,9 @@ def upgrade_available(name, **kwargs): - If set to False it depends on zypper if a refresh is - executed or not. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -687,6 +716,9 @@ def version(*names, **kwargs): - installed. If more than one package name is specified, a dict of - name/version pairs is returned. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -719,7 +751,7 @@ def version_cmp(ver1, ver2, ignore_epoch=False): - return __salt__['lowpkg.version_cmp'](ver1, ver2, ignore_epoch=ignore_epoch) - - --def list_pkgs(versions_as_list=False, **kwargs): -+def list_pkgs(versions_as_list=False, root=None, **kwargs): - ''' - List the packages currently installed as a dict. By default, the dict - contains versions as a comma separated string:: -@@ -731,6 +763,9 @@ def list_pkgs(versions_as_list=False, **kwargs): - - {'': ['', '']} - -+ root: -+ operate on a different root directory. -+ - attr: - If a list of package attributes is specified, returned value will - contain them in addition to version, eg.:: -@@ -770,10 +805,14 @@ def list_pkgs(versions_as_list=False, **kwargs): - - contextkey = 'pkg.list_pkgs' - -+ # TODO(aplanas): this cached value depends on the parameters - if contextkey not in __context__: - ret = {} -- cmd = ['rpm', '-qa', '--queryformat', -- salt.utils.pkg.rpm.QUERYFORMAT.replace('%{REPOID}', '(none)') + '\n'] -+ cmd = ['rpm'] -+ if root: -+ cmd.extend(['--root', root]) -+ cmd.extend(['-qa', '--queryformat', -+ salt.utils.pkg.rpm.QUERYFORMAT.replace('%{REPOID}', '(none)') + '\n']) - output = __salt__['cmd.run'](cmd, - python_shell=False, - output_loglevel='trace') -@@ -859,6 +898,9 @@ def list_repo_pkgs(*args, **kwargs): - When ``True``, the return data for each package will be organized by - repository. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash -@@ -891,7 +933,8 @@ def list_repo_pkgs(*args, **kwargs): - return True - return False - -- for node in __zypper__.xml.call('se', '-s', *targets).getElementsByTagName('solvable'): -+ root = kwargs.get('root') or None -+ for node in __zypper__(root=root).xml.call('se', '-s', *targets).getElementsByTagName('solvable'): - pkginfo = dict(node.attributes.items()) - try: - if pkginfo['kind'] != 'package': -@@ -933,23 +976,27 @@ def list_repo_pkgs(*args, **kwargs): - return byrepo_ret - - --def _get_configured_repos(): -+def _get_configured_repos(root=None): - ''' - Get all the info about repositories from the configurations. - ''' - -+ repos = os.path.join(root, os.path.relpath(REPOS, os.path.sep)) if root else REPOS - repos_cfg = configparser.ConfigParser() -- repos_cfg.read([REPOS + '/' + fname for fname in os.listdir(REPOS) if fname.endswith(".repo")]) -+ if os.path.exists(repos): -+ repos_cfg.read([repos + '/' + fname for fname in os.listdir(repos) if fname.endswith(".repo")]) -+ else: -+ log.error('Repositories not found in {}'.format(repos)) - - return repos_cfg - - --def _get_repo_info(alias, repos_cfg=None): -+def _get_repo_info(alias, repos_cfg=None, root=None): - ''' - Get one repo meta-data. - ''' - try: -- meta = dict((repos_cfg or _get_configured_repos()).items(alias)) -+ meta = dict((repos_cfg or _get_configured_repos(root=root)).items(alias)) - meta['alias'] = alias - for key, val in six.iteritems(meta): - if val in ['0', '1']: -@@ -961,51 +1008,60 @@ def _get_repo_info(alias, repos_cfg=None): - return {} - - --def get_repo(repo, **kwargs): # pylint: disable=unused-argument -+def get_repo(repo, root=None, **kwargs): # pylint: disable=unused-argument - ''' - Display a repo. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash - - salt '*' pkg.get_repo alias - ''' -- return _get_repo_info(repo) -+ return _get_repo_info(repo, root=root) - - --def list_repos(): -+def list_repos(root=None): - ''' - Lists all repos. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash - - salt '*' pkg.list_repos - ''' -- repos_cfg = _get_configured_repos() -+ repos_cfg = _get_configured_repos(root=root) - all_repos = {} - for alias in repos_cfg.sections(): -- all_repos[alias] = _get_repo_info(alias, repos_cfg=repos_cfg) -+ all_repos[alias] = _get_repo_info(alias, repos_cfg=repos_cfg, root=root) - - return all_repos - - --def del_repo(repo): -+def del_repo(repo, root=None): - ''' - Delete a repo. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash - - salt '*' pkg.del_repo alias - ''' -- repos_cfg = _get_configured_repos() -+ repos_cfg = _get_configured_repos(root=root) - for alias in repos_cfg.sections(): - if alias == repo: -- doc = __zypper__.xml.call('rr', '--loose-auth', '--loose-query', alias) -+ doc = __zypper__(root=root).xml.call('rr', '--loose-auth', '--loose-query', alias) - msg = doc.getElementsByTagName('message') - if doc.getElementsByTagName('progress') and msg: - return { -@@ -1044,6 +1100,9 @@ def mod_repo(repo, **kwargs): - If set to True, automatically trust and import public GPG key for - the repository. - -+ root -+ operate on a different root directory. -+ - Key/Value pairs may also be removed from a repo's configuration by setting - a key to a blank value. Bear in mind that a name cannot be deleted, and a - URL can only be deleted if a ``mirrorlist`` is specified (or vice versa). -@@ -1056,7 +1115,8 @@ def mod_repo(repo, **kwargs): - salt '*' pkg.mod_repo alias url= mirrorlist=http://host.com/ - ''' - -- repos_cfg = _get_configured_repos() -+ root = kwargs.get('root') or None -+ repos_cfg = _get_configured_repos(root=root) - added = False - - # An attempt to add new one? -@@ -1076,7 +1136,7 @@ def mod_repo(repo, **kwargs): - - # Is there already such repo under different alias? - for alias in repos_cfg.sections(): -- repo_meta = _get_repo_info(alias, repos_cfg=repos_cfg) -+ repo_meta = _get_repo_info(alias, repos_cfg=repos_cfg, root=root) - - # Complete user URL, in case it is not - new_url = _urlparse(url) -@@ -1098,17 +1158,17 @@ def mod_repo(repo, **kwargs): - ) - - # Add new repo -- __zypper__.xml.call('ar', url, repo) -+ __zypper__(root=root).xml.call('ar', url, repo) - - # Verify the repository has been added -- repos_cfg = _get_configured_repos() -+ repos_cfg = _get_configured_repos(root=root) - if repo not in repos_cfg.sections(): - raise CommandExecutionError( - 'Failed add new repository \'{0}\' for unspecified reason. ' - 'Please check zypper logs.'.format(repo)) - added = True - -- repo_info = _get_repo_info(repo) -+ repo_info = _get_repo_info(repo, root=root) - if ( - not added and 'baseurl' in kwargs and - not (kwargs['baseurl'] == repo_info['baseurl']) -@@ -1117,8 +1177,8 @@ def mod_repo(repo, **kwargs): - # we need to remove the repository and add it again with the new baseurl - repo_info.update(kwargs) - repo_info.setdefault('cache', False) -- del_repo(repo) -- return mod_repo(repo, **repo_info) -+ del_repo(repo, root=root) -+ return mod_repo(repo, root=root, **repo_info) - - # Modify added or existing repo according to the options - cmd_opt = [] -@@ -1151,7 +1211,7 @@ def mod_repo(repo, **kwargs): - - if cmd_opt: - cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo] -- __zypper__.refreshable.xml.call(*cmd_opt) -+ __zypper__(root=root).refreshable.xml.call(*cmd_opt) - - comment = None - if call_refresh: -@@ -1159,23 +1219,26 @@ def mod_repo(repo, **kwargs): - # --gpg-auto-import-keys is not doing anything - # so we need to specifically refresh here with --gpg-auto-import-keys - refresh_opts = global_cmd_opt + ['refresh'] + [repo] -- __zypper__.xml.call(*refresh_opts) -+ __zypper__(root=root).xml.call(*refresh_opts) - elif not added and not cmd_opt: - comment = 'Specified arguments did not result in modification of repo' - -- repo = get_repo(repo) -+ repo = get_repo(repo, root=root) - if comment: - repo['comment'] = comment - - return repo - - --def refresh_db(): -+def refresh_db(root=None): - ''' - Force a repository refresh by calling ``zypper refresh --force``, return a dict:: - - {'': Bool} - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -1185,7 +1248,7 @@ def refresh_db(): - # Remove rtag file to keep multiple refreshes from happening in pkg states - salt.utils.pkg.clear_rtag(__opts__) - ret = {} -- out = __zypper__.refreshable.call('refresh', '--force') -+ out = __zypper__(root=root).refreshable.call('refresh', '--force') - - for line in out.splitlines(): - if not line: -@@ -1213,6 +1276,8 @@ def install(name=None, - skip_verify=False, - version=None, - ignore_repo_failure=False, -+ no_recommends=False, -+ root=None, - **kwargs): - ''' - .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 -@@ -1301,6 +1366,12 @@ def install(name=None, - Zypper returns error code 106 if one of the repositories are not available for various reasons. - In case to set strict check, this parameter needs to be set to True. Default: False. - -+ no_recommends -+ Do not install recommended packages, only required ones. -+ -+ root -+ operate on a different root directory. -+ - diff_attr: - If a list of package attributes is specified, returned value will - contain them, eg.:: -@@ -1340,7 +1411,7 @@ def install(name=None, - 'arch': ''}}} - ''' - if refresh: -- refresh_db() -+ refresh_db(root) - - try: - pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](name, pkgs, sources, **kwargs) -@@ -1350,7 +1421,7 @@ def install(name=None, - if pkg_params is None or len(pkg_params) == 0: - return {} - -- version_num = Wildcard(__zypper__)(name, version) -+ version_num = Wildcard(__zypper__(root=root))(name, version) - - if version_num: - if pkgs is None and sources is None: -@@ -1375,7 +1446,7 @@ def install(name=None, - targets.append(target) - elif pkg_type == 'advisory': - targets = [] -- cur_patches = list_patches() -+ cur_patches = list_patches(root=root) - for advisory_id in pkg_params: - if advisory_id not in cur_patches: - raise CommandExecutionError('Advisory id "{0}" not found'.format(advisory_id)) -@@ -1385,7 +1456,7 @@ def install(name=None, - targets = pkg_params - - diff_attr = kwargs.get("diff_attr") -- old = list_pkgs(attr=diff_attr) if not downloadonly else list_downloaded() -+ old = list_pkgs(attr=diff_attr, root=root) if not downloadonly else list_downloaded(root) - downgrades = [] - if fromrepo: - fromrepoopt = ['--force', '--force-resolution', '--from', fromrepo] -@@ -1404,6 +1475,8 @@ def install(name=None, - cmd_install.append('--download-only') - if fromrepo: - cmd_install.extend(fromrepoopt) -+ if no_recommends: -+ cmd_install.append('--no-recommends') - - errors = [] - if pkg_type == 'advisory': -@@ -1415,7 +1488,7 @@ def install(name=None, - while targets: - cmd = cmd_install + targets[:500] - targets = targets[500:] -- for line in __zypper__(no_repo_failure=ignore_repo_failure, systemd_scope=systemd_scope).call(*cmd).splitlines(): -+ for line in __zypper__(no_repo_failure=ignore_repo_failure, systemd_scope=systemd_scope, root=root).call(*cmd).splitlines(): - match = re.match(r"^The selected package '([^']+)'.+has lower version", line) - if match: - downgrades.append(match.group(1)) -@@ -1423,10 +1496,10 @@ def install(name=None, - while downgrades: - cmd = cmd_install + ['--force'] + downgrades[:500] - downgrades = downgrades[500:] -- __zypper__(no_repo_failure=ignore_repo_failure).call(*cmd) -+ __zypper__(no_repo_failure=ignore_repo_failure, root=root).call(*cmd) - - _clean_cache() -- new = list_pkgs(attr=diff_attr) if not downloadonly else list_downloaded() -+ new = list_pkgs(attr=diff_attr, root=root) if not downloadonly else list_downloaded(root) - ret = salt.utils.data.compare_dicts(old, new) - - if errors: -@@ -1446,6 +1519,8 @@ def upgrade(refresh=True, - fromrepo=None, - novendorchange=False, - skip_verify=False, -+ no_recommends=False, -+ root=None, - **kwargs): # pylint: disable=unused-argument - ''' - .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 -@@ -1485,6 +1560,12 @@ def upgrade(refresh=True, - skip_verify - Skip the GPG verification check (e.g., ``--no-gpg-checks``) - -+ no_recommends -+ Do not install recommended packages, only required ones. -+ -+ root -+ Operate on a different root directory. -+ - Returns a dictionary containing the changes: - - .. code-block:: python -@@ -1507,7 +1588,7 @@ def upgrade(refresh=True, - cmd_update.insert(0, '--no-gpg-checks') - - if refresh: -- refresh_db() -+ refresh_db(root) - - if dryrun: - cmd_update.append('--dry-run') -@@ -1526,16 +1607,20 @@ def upgrade(refresh=True, - else: - log.warning('Disabling vendor changes is not supported on this Zypper version') - -+ if no_recommends: -+ cmd_update.append('--no-recommends') -+ log.info('Disabling recommendations') -+ - if dryrun: - # Creates a solver test case for debugging. - log.info('Executing debugsolver and performing a dry-run dist-upgrade') -- __zypper__(systemd_scope=_systemd_scope()).noraise.call(*cmd_update + ['--debug-solver']) -+ __zypper__(systemd_scope=_systemd_scope(), root=root).noraise.call(*cmd_update + ['--debug-solver']) - -- old = list_pkgs() -+ old = list_pkgs(root=root) - -- __zypper__(systemd_scope=_systemd_scope()).noraise.call(*cmd_update) -+ __zypper__(systemd_scope=_systemd_scope(), root=root).noraise.call(*cmd_update) - _clean_cache() -- new = list_pkgs() -+ new = list_pkgs(root=root) - ret = salt.utils.data.compare_dicts(old, new) - - if __zypper__.exit_code not in __zypper__.SUCCESS_EXIT_CODES: -@@ -1556,7 +1641,7 @@ def upgrade(refresh=True, - return ret - - --def _uninstall(name=None, pkgs=None): -+def _uninstall(name=None, pkgs=None, root=None): - ''' - Remove and purge do identical things but with different Zypper commands, - this function performs the common logic. -@@ -1566,7 +1651,7 @@ def _uninstall(name=None, pkgs=None): - except MinionError as exc: - raise CommandExecutionError(exc) - -- old = list_pkgs() -+ old = list_pkgs(root=root) - targets = [] - for target in pkg_params: - # Check if package version set to be removed is actually installed: -@@ -1582,11 +1667,11 @@ def _uninstall(name=None, pkgs=None): - - errors = [] - while targets: -- __zypper__(systemd_scope=systemd_scope).call('remove', *targets[:500]) -+ __zypper__(systemd_scope=systemd_scope, root=root).call('remove', *targets[:500]) - targets = targets[500:] - - _clean_cache() -- ret = salt.utils.data.compare_dicts(old, list_pkgs()) -+ ret = salt.utils.data.compare_dicts(old, list_pkgs(root=root)) - - if errors: - raise CommandExecutionError( -@@ -1623,7 +1708,7 @@ def normalize_name(name): - return name - - --def remove(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument -+def remove(name=None, pkgs=None, root=None, **kwargs): # pylint: disable=unused-argument - ''' - .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 - On minions running systemd>=205, `systemd-run(1)`_ is now used to -@@ -1651,6 +1736,9 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument - A list of packages to delete. Must be passed as a python list. The - ``name`` parameter will be ignored if this option is passed. - -+ root -+ Operate on a different root directory. -+ - .. versionadded:: 0.16.0 - - -@@ -1664,10 +1752,10 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument - salt '*' pkg.remove ,, - salt '*' pkg.remove pkgs='["foo", "bar"]' - ''' -- return _uninstall(name=name, pkgs=pkgs) -+ return _uninstall(name=name, pkgs=pkgs, root=root) - - --def purge(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument -+def purge(name=None, pkgs=None, root=None, **kwargs): # pylint: disable=unused-argument - ''' - .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 - On minions running systemd>=205, `systemd-run(1)`_ is now used to -@@ -1696,6 +1784,9 @@ def purge(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument - A list of packages to delete. Must be passed as a python list. The - ``name`` parameter will be ignored if this option is passed. - -+ root -+ Operate on a different root directory. -+ - .. versionadded:: 0.16.0 - - -@@ -1709,13 +1800,16 @@ def purge(name=None, pkgs=None, **kwargs): # pylint: disable=unused-argument - salt '*' pkg.purge ,, - salt '*' pkg.purge pkgs='["foo", "bar"]' - ''' -- return _uninstall(name=name, pkgs=pkgs) -+ return _uninstall(name=name, pkgs=pkgs, root=root) - - --def list_locks(): -+def list_locks(root=None): - ''' - List current package locks. - -+ root -+ operate on a different root directory. -+ - Return a dict containing the locked package with attributes:: - - {'': {'case_sensitive': '', -@@ -1729,8 +1823,9 @@ def list_locks(): - salt '*' pkg.list_locks - ''' - locks = {} -- if os.path.exists(LOCKS): -- with salt.utils.files.fopen(LOCKS) as fhr: -+ _locks = os.path.join(root, os.path.relpath(LOCKS, os.path.sep)) if root else LOCKS -+ try: -+ with salt.utils.files.fopen(_locks) as fhr: - items = salt.utils.stringutils.to_unicode(fhr.read()).split('\n\n') - for meta in [item.split('\n') for item in items]: - lock = {} -@@ -1739,15 +1834,22 @@ def list_locks(): - lock.update(dict([tuple([i.strip() for i in element.split(':', 1)]), ])) - if lock.get('solvable_name'): - locks[lock.pop('solvable_name')] = lock -+ except IOError: -+ pass -+ except Exception: -+ log.warning('Detected a problem when accessing {}'.format(_locks)) - - return locks - - --def clean_locks(): -+def clean_locks(root=None): - ''' - Remove unused locks that do not currently (with regard to repositories - used) lock any package. - -+ root -+ Operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -1756,10 +1858,11 @@ def clean_locks(): - ''' - LCK = "removed" - out = {LCK: 0} -- if not os.path.exists("/etc/zypp/locks"): -+ locks = os.path.join(root, os.path.relpath(LOCKS, os.path.sep)) if root else LOCKS -+ if not os.path.exists(locks): - return out - -- for node in __zypper__.xml.call('cl').getElementsByTagName("message"): -+ for node in __zypper__(root=root).xml.call('cl').getElementsByTagName("message"): - text = node.childNodes[0].nodeValue.lower() - if text.startswith(LCK): - out[LCK] = text.split(" ")[1] -@@ -1772,6 +1875,9 @@ def unhold(name=None, pkgs=None, **kwargs): - ''' - Remove specified package lock. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -1781,12 +1887,13 @@ def unhold(name=None, pkgs=None, **kwargs): - salt '*' pkg.remove_lock pkgs='["foo", "bar"]' - ''' - ret = {} -+ root = kwargs.get('root') - if (not name and not pkgs) or (name and pkgs): - raise CommandExecutionError('Name or packages must be specified.') - elif name: - pkgs = [name] - -- locks = list_locks() -+ locks = list_locks(root) - try: - pkgs = list(__salt__['pkg_resource.parse_targets'](pkgs)[0].keys()) - except MinionError as exc: -@@ -1803,12 +1910,12 @@ def unhold(name=None, pkgs=None, **kwargs): - ret[pkg]['comment'] = 'Package {0} unable to be unheld.'.format(pkg) - - if removed: -- __zypper__.call('rl', *removed) -+ __zypper__(root=root).call('rl', *removed) - - return ret - - --def remove_lock(packages, **kwargs): # pylint: disable=unused-argument -+def remove_lock(packages, root=None, **kwargs): # pylint: disable=unused-argument - ''' - Remove specified package lock. - -@@ -1821,7 +1928,7 @@ def remove_lock(packages, **kwargs): # pylint: disable=unused-argument - salt '*' pkg.remove_lock pkgs='["foo", "bar"]' - ''' - salt.utils.versions.warn_until('Sodium', 'This function is deprecated. Please use unhold() instead.') -- locks = list_locks() -+ locks = list_locks(root) - try: - packages = list(__salt__['pkg_resource.parse_targets'](packages)[0].keys()) - except MinionError as exc: -@@ -1836,7 +1943,7 @@ def remove_lock(packages, **kwargs): # pylint: disable=unused-argument - missing.append(pkg) - - if removed: -- __zypper__.call('rl', *removed) -+ __zypper__(root=root).call('rl', *removed) - - return {'removed': len(removed), 'not_found': missing} - -@@ -1859,12 +1966,13 @@ def hold(name=None, pkgs=None, **kwargs): - :return: - ''' - ret = {} -+ root = kwargs.get('root') - if (not name and not pkgs) or (name and pkgs): - raise CommandExecutionError('Name or packages must be specified.') - elif name: - pkgs = [name] - -- locks = list_locks() -+ locks = list_locks(root=root) - added = [] - try: - pkgs = list(__salt__['pkg_resource.parse_targets'](pkgs)[0].keys()) -@@ -1880,15 +1988,18 @@ def hold(name=None, pkgs=None, **kwargs): - ret[pkg]['comment'] = 'Package {0} is already set to be held.'.format(pkg) - - if added: -- __zypper__.call('al', *added) -+ __zypper__(root=root).call('al', *added) - - return ret - - --def add_lock(packages, **kwargs): # pylint: disable=unused-argument -+def add_lock(packages, root=None, **kwargs): # pylint: disable=unused-argument - ''' - Add a package lock. Specify packages to lock by exact name. - -+ root -+ operate on a different root directory. -+ - CLI Example: - - .. code-block:: bash -@@ -1898,7 +2009,7 @@ def add_lock(packages, **kwargs): # pylint: disable=unused-argument - salt '*' pkg.add_lock pkgs='["foo", "bar"]' - ''' - salt.utils.versions.warn_until('Sodium', 'This function is deprecated. Please use hold() instead.') -- locks = list_locks() -+ locks = list_locks(root) - added = [] - try: - packages = list(__salt__['pkg_resource.parse_targets'](packages)[0].keys()) -@@ -1910,7 +2021,7 @@ def add_lock(packages, **kwargs): # pylint: disable=unused-argument - added.append(pkg) - - if added: -- __zypper__.call('al', *added) -+ __zypper__(root=root).call('al', *added) - - return {'added': len(added), 'packages': added} - -@@ -1920,7 +2031,9 @@ def verify(*names, **kwargs): - Runs an rpm -Va on a system, and returns the results in a dict - - Files with an attribute of config, doc, ghost, license or readme in the -- package header can be ignored using the ``ignore_types`` keyword argument -+ package header can be ignored using the ``ignore_types`` keyword argument. -+ -+ The root parameter can also be passed via the keyword argument. - - CLI Example: - -@@ -1934,12 +2047,14 @@ def verify(*names, **kwargs): - return __salt__['lowpkg.verify'](*names, **kwargs) - - --def file_list(*packages): -+def file_list(*packages, **kwargs): - ''' - List the files that belong to a package. Not specifying any packages will - return a list of *every* file on the system's rpm database (not generally - recommended). - -+ The root parameter can also be passed via the keyword argument. -+ - CLI Examples: - - .. code-block:: bash -@@ -1948,15 +2063,17 @@ def file_list(*packages): - salt '*' pkg.file_list httpd postfix - salt '*' pkg.file_list - ''' -- return __salt__['lowpkg.file_list'](*packages) -+ return __salt__['lowpkg.file_list'](*packages, **kwargs) - - --def file_dict(*packages): -+def file_dict(*packages, **kwargs): - ''' - List the files that belong to a package, grouped by package. Not - specifying any packages will return a list of *every* file on the system's - rpm database (not generally recommended). - -+ The root parameter can also be passed via the keyword argument. -+ - CLI Examples: - - .. code-block:: bash -@@ -1965,7 +2082,7 @@ def file_dict(*packages): - salt '*' pkg.file_list httpd postfix - salt '*' pkg.file_list - ''' -- return __salt__['lowpkg.file_dict'](*packages) -+ return __salt__['lowpkg.file_dict'](*packages, **kwargs) - - - def modified(*packages, **flags): -@@ -2004,6 +2121,9 @@ def modified(*packages, **flags): - capabilities - Include only files where capabilities differ or not. Note: supported only on newer RPM versions. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash -@@ -2017,7 +2137,7 @@ def modified(*packages, **flags): - return __salt__['lowpkg.modified'](*packages, **flags) - - --def owner(*paths): -+def owner(*paths, **kwargs): - ''' - Return the name of the package that owns the file. Multiple file paths can - be passed. If a single path is passed, a string will be returned, -@@ -2027,6 +2147,8 @@ def owner(*paths): - If the file is not owned by a package, or is not present on the minion, - then an empty string will be returned for that path. - -+ The root parameter can also be passed via the keyword argument. -+ - CLI Examples: - - .. code-block:: bash -@@ -2034,15 +2156,15 @@ def owner(*paths): - salt '*' pkg.owner /usr/bin/apachectl - salt '*' pkg.owner /usr/bin/apachectl /etc/httpd/conf/httpd.conf - ''' -- return __salt__['lowpkg.owner'](*paths) -+ return __salt__['lowpkg.owner'](*paths, **kwargs) - - --def _get_patterns(installed_only=None): -+def _get_patterns(installed_only=None, root=None): - ''' - List all known patterns in repos. - ''' - patterns = {} -- for element in __zypper__.nolock.xml.call('se', '-t', 'pattern').getElementsByTagName('solvable'): -+ for element in __zypper__(root=root).nolock.xml.call('se', '-t', 'pattern').getElementsByTagName('solvable'): - installed = element.getAttribute('status') == 'installed' - if (installed_only and installed) or not installed_only: - patterns[element.getAttribute('name')] = { -@@ -2053,7 +2175,7 @@ def _get_patterns(installed_only=None): - return patterns - - --def list_patterns(refresh=False): -+def list_patterns(refresh=False, root=None): - ''' - List all known patterns from available repos. - -@@ -2062,6 +2184,9 @@ def list_patterns(refresh=False): - If set to False (default) it depends on zypper if a refresh is - executed. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash -@@ -2069,27 +2194,30 @@ def list_patterns(refresh=False): - salt '*' pkg.list_patterns - ''' - if refresh: -- refresh_db() -+ refresh_db(root) - -- return _get_patterns() -+ return _get_patterns(root=root) - - --def list_installed_patterns(): -+def list_installed_patterns(root=None): - ''' - List installed patterns on the system. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash - - salt '*' pkg.list_installed_patterns - ''' -- return _get_patterns(installed_only=True) -+ return _get_patterns(installed_only=True, root=root) - - - def search(criteria, refresh=False, **kwargs): - ''' -- List known packags, available to the system. -+ List known packages, available to the system. - - refresh - force a refresh if set to True. -@@ -2137,6 +2265,9 @@ def search(criteria, refresh=False, **kwargs): - details (bool) - Show version and repository - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash -@@ -2157,8 +2288,11 @@ def search(criteria, refresh=False, **kwargs): - 'not_installed_only': '-u', - 'details': '--details' - } -+ -+ root = kwargs.get('root', None) -+ - if refresh: -- refresh_db() -+ refresh_db(root) - - cmd = ['search'] - if kwargs.get('match') == 'exact': -@@ -2173,7 +2307,7 @@ def search(criteria, refresh=False, **kwargs): - cmd.append(ALLOWED_SEARCH_OPTIONS.get(opt)) - - cmd.append(criteria) -- solvables = __zypper__.nolock.noraise.xml.call(*cmd).getElementsByTagName('solvable') -+ solvables = __zypper__(root=root).nolock.noraise.xml.call(*cmd).getElementsByTagName('solvable') - if not solvables: - raise CommandExecutionError( - 'No packages found matching \'{0}\''.format(criteria) -@@ -2202,7 +2336,7 @@ def _get_first_aggregate_text(node_list): - return '\n'.join(out) - - --def list_products(all=False, refresh=False): -+def list_products(all=False, refresh=False, root=None): - ''' - List all available or installed SUSE products. - -@@ -2214,6 +2348,9 @@ def list_products(all=False, refresh=False): - If set to False (default) it depends on zypper if a refresh is - executed. - -+ root -+ operate on a different root directory. -+ - Includes handling for OEM products, which read the OEM productline file - and overwrite the release value. - -@@ -2225,10 +2362,12 @@ def list_products(all=False, refresh=False): - salt '*' pkg.list_products all=True - ''' - if refresh: -- refresh_db() -+ refresh_db(root) - - ret = list() -- OEM_PATH = "/var/lib/suseRegister/OEM" -+ OEM_PATH = '/var/lib/suseRegister/OEM' -+ if root: -+ OEM_PATH = os.path.join(root, os.path.relpath(OEM_PATH, os.path.sep)) - cmd = list() - if not all: - cmd.append('--disable-repos') -@@ -2236,7 +2375,7 @@ def list_products(all=False, refresh=False): - if not all: - cmd.append('-i') - -- product_list = __zypper__.nolock.xml.call(*cmd).getElementsByTagName('product-list') -+ product_list = __zypper__(root=root).nolock.xml.call(*cmd).getElementsByTagName('product-list') - if not product_list: - return ret # No products found - -@@ -2278,6 +2417,9 @@ def download(*packages, **kwargs): - If set to False (default) it depends on zypper if a refresh is - executed. - -+ root -+ operate on a different root directory. -+ - CLI example: - - .. code-block:: bash -@@ -2288,12 +2430,14 @@ def download(*packages, **kwargs): - if not packages: - raise SaltInvocationError('No packages specified') - -+ root = kwargs.get('root', None) -+ - refresh = kwargs.get('refresh', False) - if refresh: -- refresh_db() -+ refresh_db(root) - - pkg_ret = {} -- for dld_result in __zypper__.xml.call('download', *packages).getElementsByTagName("download-result"): -+ for dld_result in __zypper__(root=root).xml.call('download', *packages).getElementsByTagName("download-result"): - repo = dld_result.getElementsByTagName("repository")[0] - path = dld_result.getElementsByTagName("localfile")[0].getAttribute("path") - pkg_info = { -@@ -2304,7 +2448,7 @@ def download(*packages, **kwargs): - key = _get_first_aggregate_text( - dld_result.getElementsByTagName('name') - ) -- if __salt__['lowpkg.checksum'](pkg_info['path']): -+ if __salt__['lowpkg.checksum'](pkg_info['path'], root=root): - pkg_ret[key] = pkg_info - - if pkg_ret: -@@ -2318,12 +2462,15 @@ def download(*packages, **kwargs): - ) - - --def list_downloaded(): -+def list_downloaded(root=None): - ''' - .. versionadded:: 2017.7.0 - - List prefetched packages downloaded by Zypper in the local disk. - -+ root -+ operate on a different root directory. -+ - CLI example: - - .. code-block:: bash -@@ -2331,6 +2478,8 @@ def list_downloaded(): - salt '*' pkg.list_downloaded - ''' - CACHE_DIR = '/var/cache/zypp/packages/' -+ if root: -+ CACHE_DIR = os.path.join(root, os.path.relpath(CACHE_DIR, os.path.sep)) - - ret = {} - for root, dirnames, filenames in salt.utils.path.os_walk(CACHE_DIR): -@@ -2347,12 +2496,14 @@ def list_downloaded(): - return ret - - --def diff(*paths): -+def diff(*paths, **kwargs): - ''' - Return a formatted diff between current files and original in a package. - NOTE: this function includes all files (configuration and not), but does - not work on binary content. - -+ The root parameter can also be passed via the keyword argument. -+ - :param path: Full path to the installed file - :return: Difference string or raises and exception if examined file is binary. - -@@ -2366,7 +2517,7 @@ def diff(*paths): - - pkg_to_paths = {} - for pth in paths: -- pth_pkg = __salt__['lowpkg.owner'](pth) -+ pth_pkg = __salt__['lowpkg.owner'](pth, **kwargs) - if not pth_pkg: - ret[pth] = os.path.exists(pth) and 'Not managed' or 'N/A' - else: -@@ -2375,7 +2526,7 @@ def diff(*paths): - pkg_to_paths[pth_pkg].append(pth) - - if pkg_to_paths: -- local_pkgs = __salt__['pkg.download'](*pkg_to_paths.keys()) -+ local_pkgs = __salt__['pkg.download'](*pkg_to_paths.keys(), **kwargs) - for pkg, files in six.iteritems(pkg_to_paths): - for path in files: - ret[path] = __salt__['lowpkg.diff']( -@@ -2386,12 +2537,12 @@ def diff(*paths): - return ret - - --def _get_patches(installed_only=False): -+def _get_patches(installed_only=False, root=None): - ''' - List all known patches in repos. - ''' - patches = {} -- for element in __zypper__.nolock.xml.call('se', '-t', 'patch').getElementsByTagName('solvable'): -+ for element in __zypper__(root=root).nolock.xml.call('se', '-t', 'patch').getElementsByTagName('solvable'): - installed = element.getAttribute('status') == 'installed' - if (installed_only and installed) or not installed_only: - patches[element.getAttribute('name')] = { -@@ -2402,7 +2553,7 @@ def _get_patches(installed_only=False): - return patches - - --def list_patches(refresh=False): -+def list_patches(refresh=False, root=None): - ''' - .. versionadded:: 2017.7.0 - -@@ -2413,6 +2564,9 @@ def list_patches(refresh=False): - If set to False (default) it depends on zypper if a refresh is - executed. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash -@@ -2420,33 +2574,39 @@ def list_patches(refresh=False): - salt '*' pkg.list_patches - ''' - if refresh: -- refresh_db() -+ refresh_db(root) - -- return _get_patches() -+ return _get_patches(root=root) - - --def list_installed_patches(): -+def list_installed_patches(root=None): - ''' - .. versionadded:: 2017.7.0 - - List installed advisory patches on the system. - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash - - salt '*' pkg.list_installed_patches - ''' -- return _get_patches(installed_only=True) -+ return _get_patches(installed_only=True, root=root) - - --def list_provides(**kwargs): -+def list_provides(root=None, **kwargs): - ''' - .. versionadded:: 2018.3.0 - - List package provides of installed packages as a dict. - {'': ['', '', ...]} - -+ root -+ operate on a different root directory. -+ - CLI Examples: - - .. code-block:: bash -@@ -2455,7 +2615,10 @@ def list_provides(**kwargs): - ''' - ret = __context__.get('pkg.list_provides') - if not ret: -- cmd = ['rpm', '-qa', '--queryformat', '%{PROVIDES}_|-%{NAME}\n'] -+ cmd = ['rpm'] -+ if root: -+ cmd.extend(['--root', root]) -+ cmd.extend(['-qa', '--queryformat', '%{PROVIDES}_|-%{NAME}\n']) - ret = dict() - for line in __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False).splitlines(): - provide, realname = line.split('_|-') -@@ -2471,7 +2634,7 @@ def list_provides(**kwargs): - return ret - - --def resolve_capabilities(pkgs, refresh, **kwargs): -+def resolve_capabilities(pkgs, refresh=False, root=None, **kwargs): - ''' - .. versionadded:: 2018.3.0 - -@@ -2485,6 +2648,9 @@ def resolve_capabilities(pkgs, refresh, **kwargs): - If set to False (default) it depends on zypper if a refresh is - executed. - -+ root -+ operate on a different root directory. -+ - resolve_capabilities - If this option is set to True the input will be checked if - a package with this name exists. If not, this function will -@@ -2500,7 +2666,7 @@ def resolve_capabilities(pkgs, refresh, **kwargs): - salt '*' pkg.resolve_capabilities resolve_capabilities=True w3m_ssl - ''' - if refresh: -- refresh_db() -+ refresh_db(root) - - ret = list() - for pkg in pkgs: -@@ -2513,12 +2679,12 @@ def resolve_capabilities(pkgs, refresh, **kwargs): - - if kwargs.get('resolve_capabilities', False): - try: -- search(name, match='exact') -+ search(name, root=root, match='exact') - except CommandExecutionError: - # no package this such a name found - # search for a package which provides this name - try: -- result = search(name, provides=True, match='exact') -+ result = search(name, root=root, provides=True, match='exact') - if len(result) == 1: - name = next(iter(result.keys())) - elif len(result) > 1: -diff --git a/salt/states/pkg.py b/salt/states/pkg.py -index 0aca1e0af8..22a97fe98c 100644 ---- a/salt/states/pkg.py -+++ b/salt/states/pkg.py -@@ -241,7 +241,7 @@ def _fulfills_version_spec(versions, oper, desired_version, - return False - - --def _find_unpurge_targets(desired): -+def _find_unpurge_targets(desired, **kwargs): - ''' - Find packages which are marked to be purged but can't yet be removed - because they are dependencies for other installed packages. These are the -@@ -250,7 +250,7 @@ def _find_unpurge_targets(desired): - ''' - return [ - x for x in desired -- if x in __salt__['pkg.list_pkgs'](purge_desired=True) -+ if x in __salt__['pkg.list_pkgs'](purge_desired=True, **kwargs) - ] - - -@@ -265,7 +265,7 @@ def _find_download_targets(name=None, - Inspect the arguments to pkg.downloaded and discover what packages need to - be downloaded. Return a dict of packages to download. - ''' -- cur_pkgs = __salt__['pkg.list_downloaded']() -+ cur_pkgs = __salt__['pkg.list_downloaded'](**kwargs) - if pkgs: - to_download = _repack_pkgs(pkgs, normalize=normalize) - -@@ -383,7 +383,7 @@ def _find_advisory_targets(name=None, - Inspect the arguments to pkg.patch_installed and discover what advisory - patches need to be installed. Return a dict of advisory patches to install. - ''' -- cur_patches = __salt__['pkg.list_installed_patches']() -+ cur_patches = __salt__['pkg.list_installed_patches'](**kwargs) - if advisory_ids: - to_download = advisory_ids - else: -@@ -587,7 +587,7 @@ def _find_install_targets(name=None, - 'minion log.'.format('pkgs' if pkgs - else 'sources')} - -- to_unpurge = _find_unpurge_targets(desired) -+ to_unpurge = _find_unpurge_targets(desired, **kwargs) - else: - if salt.utils.platform.is_windows(): - pkginfo = _get_package_info(name, saltenv=kwargs['saltenv']) -@@ -607,7 +607,7 @@ def _find_install_targets(name=None, - else: - desired = {name: version} - -- to_unpurge = _find_unpurge_targets(desired) -+ to_unpurge = _find_unpurge_targets(desired, **kwargs) - - # FreeBSD pkg supports `openjdk` and `java/openjdk7` package names - origin = bool(re.search('/', name)) -@@ -766,7 +766,8 @@ def _find_install_targets(name=None, - verify_result = __salt__['pkg.verify']( - package_name, - ignore_types=ignore_types, -- verify_options=verify_options -+ verify_options=verify_options, -+ **kwargs - ) - except (CommandExecutionError, SaltInvocationError) as exc: - failed_verify = exc.strerror -@@ -795,7 +796,9 @@ def _find_install_targets(name=None, - verify_result = __salt__['pkg.verify']( - package_name, - ignore_types=ignore_types, -- verify_options=verify_options) -+ verify_options=verify_options, -+ **kwargs -+ ) - except (CommandExecutionError, SaltInvocationError) as exc: - failed_verify = exc.strerror - continue -@@ -1910,7 +1913,8 @@ def installed( - # have caught invalid arguments earlier. - verify_result = __salt__['pkg.verify'](reinstall_pkg, - ignore_types=ignore_types, -- verify_options=verify_options) -+ verify_options=verify_options, -+ **kwargs) - if verify_result: - failed.append(reinstall_pkg) - altered_files[reinstall_pkg] = verify_result -@@ -2098,7 +2102,7 @@ def downloaded(name, - 'package(s): {0}'.format(exc) - return ret - -- new_pkgs = __salt__['pkg.list_downloaded']() -+ new_pkgs = __salt__['pkg.list_downloaded'](**kwargs) - ok, failed = _verify_install(targets, new_pkgs, ignore_epoch=ignore_epoch) - - if failed: -@@ -2974,7 +2978,7 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs): - pkgs, refresh = _resolve_capabilities(pkgs, refresh=refresh, **kwargs) - try: - packages = __salt__['pkg.list_upgrades'](refresh=refresh, **kwargs) -- expected = {pkgname: {'new': pkgver, 'old': __salt__['pkg.version'](pkgname)} -+ expected = {pkgname: {'new': pkgver, 'old': __salt__['pkg.version'](pkgname, **kwargs)} - for pkgname, pkgver in six.iteritems(packages)} - if isinstance(pkgs, list): - packages = [pkg for pkg in packages if pkg in pkgs] -@@ -3156,7 +3160,7 @@ def group_installed(name, skip=None, include=None, **kwargs): - .format(name, exc)) - return ret - -- failed = [x for x in targets if x not in __salt__['pkg.list_pkgs']()] -+ failed = [x for x in targets if x not in __salt__['pkg.list_pkgs'](**kwargs)] - if failed: - ret['comment'] = ( - 'Failed to install the following packages: {0}' -diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py -index 4d5e9eea92..6d8e94aa18 100644 ---- a/salt/states/pkgrepo.py -+++ b/salt/states/pkgrepo.py -@@ -393,10 +393,7 @@ def managed(name, ppa=None, **kwargs): - kwargs.pop(kwarg, None) - - try: -- pre = __salt__['pkg.get_repo']( -- repo, -- ppa_auth=kwargs.get('ppa_auth', None) -- ) -+ pre = __salt__['pkg.get_repo'](repo=repo, **kwargs) - except CommandExecutionError as exc: - ret['result'] = False - ret['comment'] = \ -@@ -512,10 +509,7 @@ def managed(name, ppa=None, **kwargs): - return ret - - try: -- post = __salt__['pkg.get_repo']( -- repo, -- ppa_auth=kwargs.get('ppa_auth', None) -- ) -+ post = __salt__['pkg.get_repo'](repo=repo, **kwargs) - if pre: - for kwarg in sanitizedkwargs: - if post.get(kwarg) != pre.get(kwarg): -@@ -608,9 +602,7 @@ def absent(name, **kwargs): - return ret - - try: -- repo = __salt__['pkg.get_repo']( -- name, ppa_auth=kwargs.get('ppa_auth', None) -- ) -+ repo = __salt__['pkg.get_repo'](name, **kwargs) - except CommandExecutionError as exc: - ret['result'] = False - ret['comment'] = \ -diff --git a/tests/unit/modules/test_rpm_lowpkg.py b/tests/unit/modules/test_rpm_lowpkg.py -index 0a2359ccb2..dc9f52c572 100644 ---- a/tests/unit/modules/test_rpm_lowpkg.py -+++ b/tests/unit/modules/test_rpm_lowpkg.py -@@ -20,6 +20,11 @@ from tests.support.mock import ( - import salt.modules.rpm_lowpkg as rpm - - -+def _called_with_root(mock): -+ cmd = ' '.join(mock.call_args[0][0]) -+ return cmd.startswith('rpm --root /') -+ -+ - @skipIf(NO_MOCK, NO_MOCK_REASON) - class RpmTestCase(TestCase, LoaderModuleMockMixin): - ''' -@@ -28,7 +33,7 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - def setup_loader_modules(self): - return {rpm: {'rpm': MagicMock(return_value=MagicMock)}} - -- # 'list_pkgs' function tests: 1 -+ # 'list_pkgs' function tests: 2 - - def test_list_pkgs(self): - ''' -@@ -37,13 +42,24 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - 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: 1 -+ # '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 -+ Test if it runs an rpm -Va on a system, and returns the -+ results in a dict - ''' - mock = MagicMock(return_value={'stdout': '', - 'stderr': '', -@@ -51,8 +67,22 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - '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: 1 -+ # 'file_list' function tests: 2 - - def test_file_list(self): - ''' -@@ -62,8 +92,20 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - with patch.dict(rpm.__salt__, {'cmd.run': mock}): - self.assertDictEqual(rpm.file_list('httpd'), - {'errors': [], 'files': []}) -+ self.assertFalse(_called_with_root(mock)) - -- # 'file_dict' function tests: 1 -+ 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): - ''' -@@ -73,6 +115,16 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - 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 - -@@ -86,6 +138,7 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - 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'} -@@ -94,8 +147,22 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - 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)) - -- # 'checksum' function tests: 1 -+ 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): - ''' -@@ -110,6 +177,17 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): - 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)) - - def test_version_cmp_rpm(self): - ''' -diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py -index f586c23fd0..e7474ff777 100644 ---- a/tests/unit/modules/test_zypperpkg.py -+++ b/tests/unit/modules/test_zypperpkg.py -@@ -40,6 +40,9 @@ class ZyppCallMock(object): - return self - - def __call__(self, *args, **kwargs): -+ # If the call is for a configuration modifier, we return self -+ if any(i in kwargs for i in ('no_repo_failure', 'systemd_scope', 'root')): -+ return self - return MagicMock(return_value=self.__return_value)() - - -@@ -925,7 +928,7 @@ Repository 'DUMMY' not found by its alias, number, or URI. - 'pico': '0.1.1', - } - -- def __call__(self): -+ def __call__(self, root=None): - pkgs = self._pkgs.copy() - for target in self._packages: - if self._pkgs.get(target): -@@ -991,10 +994,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. - with zypper_patcher: - zypper.mod_repo(name, **{'url': url}) - self.assertEqual( -- zypper.__zypper__.xml.call.call_args_list, -+ zypper.__zypper__(root=None).xml.call.call_args_list, - [call('ar', url, name)] - ) -- self.assertTrue(zypper.__zypper__.refreshable.xml.call.call_count == 0) -+ self.assertTrue(zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0) - - def test_repo_noadd_nomod_noref(self): - ''' -@@ -1016,8 +1019,8 @@ Repository 'DUMMY' not found by its alias, number, or URI. - self.assertEqual( - out['comment'], - 'Specified arguments did not result in modification of repo') -- self.assertTrue(zypper.__zypper__.xml.call.call_count == 0) -- self.assertTrue(zypper.__zypper__.refreshable.xml.call.call_count == 0) -+ self.assertTrue(zypper.__zypper__(root=None).xml.call.call_count == 0) -+ self.assertTrue(zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0) - - def test_repo_noadd_modbaseurl_ref(self): - ''' -@@ -1045,9 +1048,11 @@ Repository 'DUMMY' not found by its alias, number, or URI. - 'priority': 1, - 'cache': False, - 'keeppackages': False, -- 'type': 'rpm-md'} -- self.assertTrue(zypper.mod_repo.call_count == 2) -- self.assertTrue(zypper.mod_repo.mock_calls[1] == call(name, **expected_params)) -+ 'type': 'rpm-md', -+ 'root': None, -+ } -+ self.assertEqual(zypper.mod_repo.call_count, 2) -+ self.assertEqual(zypper.mod_repo.mock_calls[1], call(name, **expected_params)) - - def test_repo_add_mod_noref(self): - ''' -@@ -1063,10 +1068,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. - with zypper_patcher: - zypper.mod_repo(name, **{'url': url, 'refresh': True}) - self.assertEqual( -- zypper.__zypper__.xml.call.call_args_list, -+ zypper.__zypper__(root=None).xml.call.call_args_list, - [call('ar', url, name)] - ) -- zypper.__zypper__.refreshable.xml.call.assert_called_once_with( -+ zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( - 'mr', '--refresh', name - ) - -@@ -1085,8 +1090,8 @@ Repository 'DUMMY' not found by its alias, number, or URI. - 'salt.modules.zypperpkg', **self.zypper_patcher_config) - with zypper_patcher: - zypper.mod_repo(name, **{'url': url, 'refresh': True}) -- self.assertTrue(zypper.__zypper__.xml.call.call_count == 0) -- zypper.__zypper__.refreshable.xml.call.assert_called_once_with( -+ self.assertTrue(zypper.__zypper__(root=None).xml.call.call_count == 0) -+ zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( - 'mr', '--refresh', name - ) - -@@ -1105,13 +1110,13 @@ Repository 'DUMMY' not found by its alias, number, or URI. - with zypper_patcher: - zypper.mod_repo(name, **{'url': url, 'gpgautoimport': True}) - self.assertEqual( -- zypper.__zypper__.xml.call.call_args_list, -+ zypper.__zypper__(root=None).xml.call.call_args_list, - [ - call('ar', url, name), - call('--gpg-auto-import-keys', 'refresh', name) - ] - ) -- self.assertTrue(zypper.__zypper__.refreshable.xml.call.call_count == 0) -+ self.assertTrue(zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0) - - def test_repo_noadd_nomod_ref(self): - ''' -@@ -1132,10 +1137,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. - with zypper_patcher: - zypper.mod_repo(name, **{'url': url, 'gpgautoimport': True}) - self.assertEqual( -- zypper.__zypper__.xml.call.call_args_list, -+ zypper.__zypper__(root=None).xml.call.call_args_list, - [call('--gpg-auto-import-keys', 'refresh', name)] - ) -- self.assertTrue(zypper.__zypper__.refreshable.xml.call.call_count == 0) -+ self.assertTrue(zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0) - - def test_repo_add_mod_ref(self): - ''' -@@ -1156,13 +1161,13 @@ Repository 'DUMMY' not found by its alias, number, or URI. - **{'url': url, 'refresh': True, 'gpgautoimport': True} - ) - self.assertEqual( -- zypper.__zypper__.xml.call.call_args_list, -+ zypper.__zypper__(root=None).xml.call.call_args_list, - [ - call('ar', url, name), - call('--gpg-auto-import-keys', 'refresh', name) - ] - ) -- zypper.__zypper__.refreshable.xml.call.assert_called_once_with( -+ zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( - '--gpg-auto-import-keys', 'mr', '--refresh', name - ) - -@@ -1188,10 +1193,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. - **{'url': url, 'refresh': True, 'gpgautoimport': True} - ) - self.assertEqual( -- zypper.__zypper__.xml.call.call_args_list, -+ zypper.__zypper__(root=None).xml.call.call_args_list, - [call('--gpg-auto-import-keys', 'refresh', name)] - ) -- zypper.__zypper__.refreshable.xml.call.assert_called_once_with( -+ zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( - '--gpg-auto-import-keys', 'mr', '--refresh', name - ) - -diff --git a/tests/unit/states/test_pkg.py b/tests/unit/states/test_pkg.py -index 42fe6c6867..d30e064167 100644 ---- a/tests/unit/states/test_pkg.py -+++ b/tests/unit/states/test_pkg.py -@@ -46,7 +46,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): - pkgname: pkgver['new'] for pkgname, pkgver in six.iteritems(self.pkgs) - }) - upgrade = MagicMock(return_value=self.pkgs) -- version = MagicMock(side_effect=lambda pkgname: self.pkgs[pkgname]['old']) -+ version = MagicMock(side_effect=lambda pkgname, **_: self.pkgs[pkgname]['old']) - - with patch.dict(pkg.__salt__, - {'pkg.list_upgrades': list_upgrades, -@@ -55,7 +55,6 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): - - # Run state with test=false - with patch.dict(pkg.__opts__, {'test': False}): -- - ret = pkg.uptodate('dummy', test=True) - self.assertTrue(ret['result']) - self.assertDictEqual(ret['changes'], self.pkgs) -@@ -81,7 +80,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): - pkgname: pkgver['new'] for pkgname, pkgver in six.iteritems(self.pkgs) - }) - upgrade = MagicMock(return_value=self.pkgs) -- version = MagicMock(side_effect=lambda pkgname: pkgs[pkgname]['old']) -+ version = MagicMock(side_effect=lambda pkgname, **_: pkgs[pkgname]['old']) - - with patch.dict(pkg.__salt__, - {'pkg.list_upgrades': list_upgrades, -@@ -160,7 +159,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): - pkgname: pkgver['new'] for pkgname, pkgver in six.iteritems(self.pkgs) - }) - upgrade = MagicMock(return_value={}) -- version = MagicMock(side_effect=lambda pkgname: pkgs[pkgname]['old']) -+ version = MagicMock(side_effect=lambda pkgname, **_: pkgs[pkgname]['old']) - - with patch.dict(pkg.__salt__, - {'pkg.list_upgrades': list_upgrades, --- -2.20.1 - - From 5f0701bf52d2adb2bec268cb426fa1b2633c4d7dd418a65c914cdd73172bfe62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Tue, 30 Apr 2019 12:10:21 +0000 Subject: [PATCH 4/6] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:262 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=136 --- _lastrevision | 2 +- ...tches-as-installed-when-not-all-the-.patch | 45 ++++++++++++++++++ salt.changes | 12 +++++ salt.spec | 6 +++ ...rom-multiprocessing.pool-to-avoid-le.patch | 47 +++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 do-not-report-patches-as-installed-when-not-all-the-.patch create mode 100644 use-threadpool-from-multiprocessing.pool-to-avoid-le.patch diff --git a/_lastrevision b/_lastrevision index 3bc96d5..263e7a1 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -bf25c6e7dd034e44a3d16de37485c1914d6a93f5 \ No newline at end of file +a2ebac5641c371563ae0521639d3ff2f3aed22c1 \ No newline at end of file diff --git a/do-not-report-patches-as-installed-when-not-all-the-.patch b/do-not-report-patches-as-installed-when-not-all-the-.patch new file mode 100644 index 0000000..0854c3a --- /dev/null +++ b/do-not-report-patches-as-installed-when-not-all-the-.patch @@ -0,0 +1,45 @@ +From 769c9e85499bc9912b050fff7d3105690f1d7c7b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 13 Mar 2019 16:14:07 +0000 +Subject: [PATCH] Do not report patches as installed when not all the + related packages are installed (bsc#1128061) + +Co-authored-by: Mihai Dinca +--- + salt/modules/yumpkg.py | 18 ++++++++++++------ + 1 file changed, 12 insertions(+), 6 deletions(-) + +diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py +index 4f26a41670..5ec3835574 100644 +--- a/salt/modules/yumpkg.py ++++ b/salt/modules/yumpkg.py +@@ -3212,12 +3212,18 @@ def _get_patches(installed_only=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() +- if inst != 'i' and installed_only: +- continue +- patches[advisory_id] = { +- 'installed': True if inst == 'i' else False, +- 'summary': pkg +- } ++ if not advisory_id in patches: ++ patches[advisory_id] = { ++ 'installed': True if inst == 'i' else False, ++ 'summary': [pkg] ++ } ++ else: ++ patches[advisory_id]['summary'].append(pkg) ++ if inst != 'i': ++ patches[advisory_id]['installed'] = False ++ ++ if installed_only: ++ patches = {k: v for k, v in patches.items() if v['installed']} + return patches + + +-- +2.20.1 + + diff --git a/salt.changes b/salt.changes index 1051813..d9ec62a 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,15 @@ +------------------------------------------------------------------- +Tue Apr 30 11:51:59 UTC 2019 - psuarezhernandez@suse.com + +- Use ThreadPool from multiprocessing.pool to avoid leakings + when calculating FQDNs +- Do not report patches as installed on RHEL systems when not all + the related packages are installed (bsc#1128061) + +- Added: + * use-threadpool-from-multiprocessing.pool-to-avoid-le.patch + * do-not-report-patches-as-installed-when-not-all-the-.patch + ------------------------------------------------------------------- Fri Apr 26 10:00:01 UTC 2019 - mdinca@suse.de diff --git a/salt.spec b/salt.spec index 66d4443..8555f39 100644 --- a/salt.spec +++ b/salt.spec @@ -163,6 +163,10 @@ Patch47: calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch Patch48: fix-async-batch-race-conditions.patch #PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/141 Patch49: add-batch_presence_ping_timeout-and-batch_presence_p.patch +#PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/52657 +Patch50: do-not-report-patches-as-installed-when-not-all-the-.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52527 +Patch51: use-threadpool-from-multiprocessing.pool-to-avoid-le.patch # BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -670,6 +674,8 @@ cp %{S:5} ./.travis.yml %patch47 -p1 %patch48 -p1 %patch49 -p1 +%patch50 -p1 +%patch51 -p1 %build %if 0%{?build_py2} diff --git a/use-threadpool-from-multiprocessing.pool-to-avoid-le.patch b/use-threadpool-from-multiprocessing.pool-to-avoid-le.patch new file mode 100644 index 0000000..39783b4 --- /dev/null +++ b/use-threadpool-from-multiprocessing.pool-to-avoid-le.patch @@ -0,0 +1,47 @@ +From cd8e175738f7742fbb7c9e9d329039371bc0e579 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 30 Apr 2019 10:51:42 +0100 +Subject: [PATCH] Use ThreadPool from multiprocessing.pool to avoid + leakings + +--- + salt/grains/core.py | 14 +++++++++----- + 1 file changed, 9 insertions(+), 5 deletions(-) + +diff --git a/salt/grains/core.py b/salt/grains/core.py +index 796458939d..fec7b204bc 100644 +--- a/salt/grains/core.py ++++ b/salt/grains/core.py +@@ -26,7 +26,7 @@ from errno import EACCES, EPERM + import datetime + import warnings + +-from multiprocessing.dummy import Pool as ThreadPool ++from multiprocessing.pool import ThreadPool + + # pylint: disable=import-error + try: +@@ -2225,10 +2225,14 @@ def fqdns(): + # Create a ThreadPool to process the underlying calls to 'socket.gethostbyaddr' in parallel. + # This avoid blocking the execution when the "fqdn" is not defined for certains IP addresses, which was causing + # that "socket.timeout" was reached multiple times secuencially, blocking execution for several seconds. +- pool = ThreadPool(8) +- results = pool.map(_lookup_fqdn, addresses) +- pool.close() +- pool.join() ++ ++ try: ++ pool = ThreadPool(8) ++ results = pool.map(_lookup_fqdn, addresses) ++ pool.close() ++ pool.join() ++ except Exception as exc: ++ log.error("Exception while creating a ThreadPool for resolving FQDNs: %s", exc) + + for item in results: + if item: +-- +2.17.1 + + From 71dd85c742adb301864b6b365a7eb7c2720508857e3c9c0df4403433f2d67562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mihai=20Dinc=C4=83?= Date: Thu, 2 May 2019 16:27:35 +0000 Subject: [PATCH 5/6] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:263 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=137 --- _lastrevision | 2 +- fix-syndic-start-issue.patch | 26 ++++++++++++++++++++++++++ salt.changes | 9 +++++++++ salt.spec | 5 ++++- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 fix-syndic-start-issue.patch diff --git a/_lastrevision b/_lastrevision index 263e7a1..9f38e34 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -a2ebac5641c371563ae0521639d3ff2f3aed22c1 \ No newline at end of file +19d76462de94cefedc905c8c5d24635c7c8e9b40 \ No newline at end of file diff --git a/fix-syndic-start-issue.patch b/fix-syndic-start-issue.patch new file mode 100644 index 0000000..7447fc9 --- /dev/null +++ b/fix-syndic-start-issue.patch @@ -0,0 +1,26 @@ +From 0b15fe1ecc3ed468714a5a8d84787ab23ac6144e Mon Sep 17 00:00:00 2001 +From: Mihai Dinca +Date: Thu, 2 May 2019 10:50:17 +0200 +Subject: [PATCH] Fix syndic start issue + +--- + salt/utils/event.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/utils/event.py b/salt/utils/event.py +index d2700bd2a0..160cba9bde 100644 +--- a/salt/utils/event.py ++++ b/salt/utils/event.py +@@ -879,7 +879,7 @@ class SaltEvent(object): + self.subscriber.callbacks.add(event_handler) + if not self.subscriber.reading: + # This will handle reconnects +- self.subscriber.read_async() ++ return self.subscriber.read_async() + + def __del__(self): + # skip exceptions in destroy-- since destroy() doesn't cover interpreter +-- +2.20.1 + + diff --git a/salt.changes b/salt.changes index d9ec62a..0d81950 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Thu May 2 16:18:45 UTC 2019 - mdinca + +- Fix issue preventing syndic to start +- Update year on spec copyright notice + +- Added: + * fix-syndic-start-issue.patch + ------------------------------------------------------------------- Tue Apr 30 11:51:59 UTC 2019 - psuarezhernandez@suse.com diff --git a/salt.spec b/salt.spec index 8555f39..e4f8046 100644 --- a/salt.spec +++ b/salt.spec @@ -1,7 +1,7 @@ # # spec file for package salt # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -167,6 +167,8 @@ Patch49: add-batch_presence_ping_timeout-and-batch_presence_p.patch Patch50: do-not-report-patches-as-installed-when-not-all-the-.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52527 Patch51: use-threadpool-from-multiprocessing.pool-to-avoid-le.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52519 (partial porting) +Patch52: fix-syndic-start-issue.patch # BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -676,6 +678,7 @@ cp %{S:5} ./.travis.yml %patch49 -p1 %patch50 -p1 %patch51 -p1 +%patch52 -p1 %build %if 0%{?build_py2} From 318cf26574d56aa8440795782ceddc664df6f79484515e1ef3194c63554f163b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mihai=20Dinc=C4=83?= Date: Fri, 3 May 2019 09:46:52 +0000 Subject: [PATCH 6/6] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:264 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=138 --- _lastrevision | 2 +- salt.changes | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/_lastrevision b/_lastrevision index 9f38e34..43041da 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -19d76462de94cefedc905c8c5d24635c7c8e9b40 \ No newline at end of file +8d79ae9a816ab27810786c5a4a60021af08ec366 \ No newline at end of file diff --git a/salt.changes b/salt.changes index 0d81950..66cb04c 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Fri May 3 09:42:06 UTC 2019 - mdinca + +- Include aliases in FQDNS grain (bsc#1121439) + ------------------------------------------------------------------- Thu May 2 16:18:45 UTC 2019 - mdinca