From f908ce43317bfc5c6ae7f286f7920e6798c0c0fe Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 16:40:16 -0700 Subject: [PATCH 01/21] Porting PR #49843 to 2019.2.1 --- salt/states/file.py | 1 - tests/unit/states/test_file.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/salt/states/file.py b/salt/states/file.py index 4e451c58f812..e299b15356ac 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -6401,7 +6401,6 @@ def rename(name, source, force=False, makedirs=False): if not force: ret['comment'] = ('The target file "{0}" exists and will not be ' 'overwritten'.format(name)) - ret['result'] = False return ret elif not __opts__['test']: # Remove the destination to prevent problems later diff --git a/tests/unit/states/test_file.py b/tests/unit/states/test_file.py index 72cc1fe72867..97b948f4469f 100644 --- a/tests/unit/states/test_file.py +++ b/tests/unit/states/test_file.py @@ -1524,7 +1524,7 @@ def test_rename(self): with patch.object(os.path, 'lexists', mock_lex): comt = ('The target file "{0}" exists and will not be ' 'overwritten'.format(name)) - ret.update({'comment': comt, 'result': False}) + ret.update({'comment': comt, 'result': True}) self.assertDictEqual(filestate.rename(name, source), ret) mock_lex = MagicMock(side_effect=[True, True, True]) From fb956bb7398ed053d10753e530bad4fd37768149 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 17:04:47 -0700 Subject: [PATCH 02/21] Porting PR #52933 to 2019.2.1 --- salt/beacons/inotify.py | 4 ++++ tests/unit/beacons/test_inotify.py | 27 +++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/salt/beacons/inotify.py b/salt/beacons/inotify.py index da68b6ab5743..e3bfe4eb8947 100644 --- a/salt/beacons/inotify.py +++ b/salt/beacons/inotify.py @@ -117,6 +117,10 @@ def validate(config): if 'files' not in _config: return False, 'Configuration for inotify beacon must include files.' else: + if not isinstance(_config['files'], dict): + return False, ('Configuration for inotify beacon invalid, ' + 'files must be a dict.') + for path in _config.get('files'): if not isinstance(_config['files'][path], dict): diff --git a/tests/unit/beacons/test_inotify.py b/tests/unit/beacons/test_inotify.py index 5c0dc8c89868..41b6f8475764 100644 --- a/tests/unit/beacons/test_inotify.py +++ b/tests/unit/beacons/test_inotify.py @@ -39,10 +39,33 @@ def setUp(self): def tearDown(self): shutil.rmtree(self.tmpdir, ignore_errors=True) + def test_non_list_config(self): + config = {} + + ret = inotify.validate(config) + + self.assertEqual(ret, (False, 'Configuration for inotify beacon must' + ' be a list.')) + def test_empty_config(self): config = [{}] - ret = inotify.beacon(config) - self.assertEqual(ret, []) + ret = inotify.validate(config) + _expected = (False, 'Configuration for inotify beacon must include files.') + self.assertEqual(ret, _expected) + + def test_files_none_config(self): + config = [{'files': None}] + ret = inotify.validate(config) + _expected = (False, 'Configuration for inotify beacon invalid, ' + 'files must be a dict.') + self.assertEqual(ret, _expected) + + def test_files_list_config(self): + config = [{'files': [{u'/importantfile': {u'mask': [u'modify']}}]}] + ret = inotify.validate(config) + _expected = (False, 'Configuration for inotify beacon invalid, ' + 'files must be a dict.') + self.assertEqual(ret, _expected) def test_file_open(self): path = os.path.realpath(__file__) From d1a840083828c06e1adfb268026e8e7e51c6196b Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 17:13:14 -0700 Subject: [PATCH 03/21] Porting PR #52786 to 2019.2.1 --- salt/states/user.py | 8 +++--- tests/integration/states/test_user.py | 36 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/salt/states/user.py b/salt/states/user.py index 4ba985b10268..f545173e6bdf 100644 --- a/salt/states/user.py +++ b/salt/states/user.py @@ -161,13 +161,13 @@ def _changes(name, if fullname is not None and lusr['fullname'] != fullname: change['fullname'] = fullname if win_homedrive and lusr['homedrive'] != win_homedrive: - change['homedrive'] = win_homedrive + change['win_homedrive'] = win_homedrive if win_profile and lusr['profile'] != win_profile: - change['profile'] = win_profile + change['win_profile'] = win_profile if win_logonscript and lusr['logonscript'] != win_logonscript: - change['logonscript'] = win_logonscript + change['win_logonscript'] = win_logonscript if win_description and lusr['description'] != win_description: - change['description'] = win_description + change['win_description'] = win_description # MacOS doesn't have full GECOS support, so check for the "ch" functions # and ignore these parameters if these functions do not exist. diff --git a/tests/integration/states/test_user.py b/tests/integration/states/test_user.py index a820e97c7cab..a1efae88e513 100644 --- a/tests/integration/states/test_user.py +++ b/tests/integration/states/test_user.py @@ -269,3 +269,39 @@ def tearDown(self): self.assertSaltTrueReturn( self.run_state('user.absent', name=self.user_name) ) + + +@destructiveTest +@skip_if_not_root +@skipIf(not salt.utils.platform.is_windows(), 'Windows only tests') +class WinUserTest(ModuleCase, SaltReturnAssertsMixin): + ''' + test for user absent + ''' + def tearDown(self): + self.assertSaltTrueReturn( + self.run_state('user.absent', name=USER) + ) + + def test_user_present_existing(self): + ret = self.run_state('user.present', + name=USER, + win_homedrive='U:', + win_profile='C:\\User\\{0}'.format(USER), + win_logonscript='C:\\logon.vbs', + win_description='Test User Account') + self.assertSaltTrueReturn(ret) + ret = self.run_state('user.present', + name=USER, + win_homedrive='R:', + win_profile='C:\\Users\\{0}'.format(USER), + win_logonscript='C:\\Windows\\logon.vbs', + win_description='Temporary Account') + self.assertSaltTrueReturn(ret) + self.assertSaltStateChangesEqual(ret, 'R:', keys=['homedrive']) + self.assertSaltStateChangesEqual( + ret, 'C:\\Users\\{0}'.format(USER), keys=['profile']) + self.assertSaltStateChangesEqual( + ret, 'C:\\Windows\\logon.vbs', keys=['logonscript']) + self.assertSaltStateChangesEqual( + ret, 'Temporary Account', keys=['description']) From fcfb37c4e5101964ccef565c090585c4f4cdc126 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 17:26:02 -0700 Subject: [PATCH 04/21] Porting PR #52698 to 2019.2.1 --- tests/conftest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 906cca6dd518..1dbe2176b671 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -244,24 +244,24 @@ def pytest_runtest_setup(item): ''' Fixtures injection based on markers or test skips based on CLI arguments ''' - destructive_tests_marker = item.get_marker('destructive_test') + destructive_tests_marker = item.get_closest_marker('destructive_test') if destructive_tests_marker is not None: if item.config.getoption('--run-destructive') is False: pytest.skip('Destructive tests are disabled') os.environ['DESTRUCTIVE_TESTS'] = six.text_type(item.config.getoption('--run-destructive')) - expensive_tests_marker = item.get_marker('expensive_test') + expensive_tests_marker = item.get_closest_marker('expensive_test') if expensive_tests_marker is not None: if item.config.getoption('--run-expensive') is False: pytest.skip('Expensive tests are disabled') os.environ['EXPENSIVE_TESTS'] = six.text_type(item.config.getoption('--run-expensive')) - skip_if_not_root_marker = item.get_marker('skip_if_not_root') + skip_if_not_root_marker = item.get_closest_marker('skip_if_not_root') if skip_if_not_root_marker is not None: if os.getuid() != 0: pytest.skip('You must be logged in as root to run this test') - skip_if_binaries_missing_marker = item.get_marker('skip_if_binaries_missing') + skip_if_binaries_missing_marker = item.get_closest_marker('skip_if_binaries_missing') if skip_if_binaries_missing_marker is not None: binaries = skip_if_binaries_missing_marker.args if len(binaries) == 1: @@ -286,7 +286,7 @@ def pytest_runtest_setup(item): ) ) - requires_network_marker = item.get_marker('requires_network') + requires_network_marker = item.get_closest_marker('requires_network') if requires_network_marker is not None: only_local_network = requires_network_marker.kwargs.get('only_local_network', False) has_local_network = False From 55f0cee9457fd434ee5f6b587c8cbe42c0b252f7 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 18:01:27 -0700 Subject: [PATCH 05/21] Porting PR #51385 to 2019.2.1 --- salt/states/disk.py | 129 +++++++++---- tests/unit/states/test_disk.py | 318 ++++++++++++++++++++++++++++++--- 2 files changed, 385 insertions(+), 62 deletions(-) diff --git a/salt/states/disk.py b/salt/states/disk.py index 82254a391a2e..aca5f7c2d113 100644 --- a/salt/states/disk.py +++ b/salt/states/disk.py @@ -47,10 +47,11 @@ # Import salt libs from salt.ext.six import string_types +from os import path __monitor__ = [ - 'status', - ] + 'status', +] def _validate_int(name, value, limits=(), strip='%'): @@ -74,12 +75,88 @@ def _validate_int(name, value, limits=(), strip='%'): return value, comment -def status(name, maximum=None, minimum=None, absolute=False): +def _status_mount(name, ret, minimum, maximum, absolute, free, data): + # Get used space + if absolute: + used = int(data[name]['used']) + available = int(data[name]['available']) + else: + # POSIX-compliant df output reports percent used as 'capacity' + used = int(data[name]['capacity'].strip('%')) + available = 100 - used + + # Collect return information + ret['data'] = data[name] + return _check_min_max(absolute, free, available, used, maximum, minimum, ret) + + +def _status_path(directory, ret, minimum, maximum, absolute, free): + if path.isdir(directory) is False: + ret['result'] = False + ret['comment'] += ('Directory {0} does not exist or is not a directory'.format(directory)) + return ret + + data = __salt__['status.diskusage'](directory) + + if absolute: + used = int(data[directory]['total']) - int(data[directory]['available']) + available = int(data[directory]['available']) + else: + if int(data[directory]['total']) == 0: + used = 0 + available = 0 + else: + used = round(float(int(data[directory]['total']) - int(data[directory]['available'])) / + int(data[directory]['total']) * 100, 1) + available = round(float(data[directory]['available']) / int(data[directory]['total']) * 100, 1) + + ret['data'] = data + return _check_min_max(absolute, free, available, used, maximum, minimum, ret) + + +def _check_min_max(absolute, free, available, used, maximum, minimum, ret): + unit = 'KB' if absolute else '%' + if minimum is not None: + if free: + if available < minimum: + ret['comment'] = ('Disk available space is below minimum' + ' of {0} {2} at {1} {2}' + ''.format(minimum, available, unit) + ) + return ret + else: + if used < minimum: + ret['comment'] = ('Disk used space is below minimum' + ' of {0} {2} at {1} {2}' + ''.format(minimum, used, unit) + ) + return ret + if maximum is not None: + if free: + if available > maximum: + ret['comment'] = ('Disk available space is above maximum' + ' of {0} {2} at {1} {2}' + ''.format(maximum, available, unit) + ) + return ret + else: + if used > maximum: + ret['comment'] = ('Disk used space is above maximum' + ' of {0} {2} at {1} {2}' + ''.format(maximum, used, unit) + ) + return ret + ret['comment'] = 'Disk used space in acceptable range' + ret['result'] = True + return ret + + +def status(name, maximum=None, minimum=None, absolute=False, free=False): ''' Return the current disk usage stats for the named mount point name - Disk mount with which to check used space + Disk mount or directory for which to check used space maximum The maximum disk utilization @@ -92,6 +169,10 @@ def status(name, maximum=None, minimum=None, absolute=False): the `absolute` flag to use kilobytes. .. versionadded:: 2016.11.0 + + free + By default, `minimum` & `maximum` refer to the amount of used space. + Set to `True` to evaluate the free space instead. ''' # Monitoring state, no changes will be made so no test interface needed ret = {'name': name, @@ -100,13 +181,6 @@ def status(name, maximum=None, minimum=None, absolute=False): 'changes': {}, 'data': {}} # Data field for monitoring state - data = __salt__['disk.usage']() - - # Validate name - if name not in data: - ret['result'] = False - ret['comment'] += 'Named disk mount not present ' - return ret # Validate extrema if maximum is not None: if not absolute: @@ -126,30 +200,11 @@ def status(name, maximum=None, minimum=None, absolute=False): if ret['comment']: return ret - # Get used space - if absolute: - used = int(data[name]['used']) - else: - # POSIX-compliant df output reports percent used as 'capacity' - used = int(data[name]['capacity'].strip('%')) + data = __salt__['disk.usage']() - # Collect return information - ret['data'] = data[name] - unit = 'KB' if absolute else '%' - if minimum is not None: - if used < minimum: - ret['comment'] = ('Disk used space is below minimum' - ' of {0} {2} at {1} {2}' - ''.format(minimum, used, unit) - ) - return ret - if maximum is not None: - if used > maximum: - ret['comment'] = ('Disk used space is above maximum' - ' of {0} {2} at {1} {2}' - ''.format(maximum, used, unit) - ) - return ret - ret['comment'] = 'Disk used space in acceptable range' - ret['result'] = True - return ret + # Validate name + if name not in data: + ret['comment'] += ('Disk mount {0} not present. '.format(name)) + return _status_path(name, ret, minimum, maximum, absolute, free) + else: + return _status_mount(name, ret, minimum, maximum, absolute, free, data) diff --git a/tests/unit/states/test_disk.py b/tests/unit/states/test_disk.py index 8c2e4aa9a619..189fd2b09224 100644 --- a/tests/unit/states/test_disk.py +++ b/tests/unit/states/test_disk.py @@ -13,6 +13,7 @@ NO_MOCK_REASON, MagicMock, patch) +from os import path # Import Salt Libs import salt.states.disk as disk @@ -50,8 +51,12 @@ def setup_loader_modules(self): 'filesystem': 'tmpfs', 'used': '0'} } + + self.mock_data_path = {'/foo': {'available': '42', 'total': '100'}} + self.addCleanup(delattr, self, 'mock_data') - return {disk: {'__salt__': {'disk.usage': MagicMock(return_value=self.mock_data)}}} + self.addCleanup(delattr, self, 'mock_data_path') + return {disk: {'__salt__': {'disk.usage': MagicMock(return_value=self.mock_data), 'status.diskusage': MagicMock(return_value=self.mock_data_path)}}} def test_status_missing(self): ''' @@ -60,7 +65,7 @@ def test_status_missing(self): mock_fs = '/mnt/cheese' mock_ret = {'name': mock_fs, 'result': False, - 'comment': 'Named disk mount not present ', + 'comment': 'Disk mount /mnt/cheese not present. Directory /mnt/cheese does not exist or is not a directory', 'changes': {}, 'data': {}} @@ -203,27 +208,290 @@ def test_status(self): 'changes': {}, 'data': {}} - mock = MagicMock(side_effect=[[], [mock_fs], {mock_fs: {'capacity': '8 %', 'used': '8'}}, - {mock_fs: {'capacity': '22 %', 'used': '22'}}, - {mock_fs: {'capacity': '15 %', 'used': '15'}}]) + data_1 = {'capacity': '8 %', 'used': '8', 'available': '92'} + data_2 = {'capacity': '22 %', 'used': '22', 'available': '78'} + data_3 = {'capacity': '15 %', 'used': '15', 'available': '85'} + mock = MagicMock(side_effect=[[], {mock_fs: data_1}, {mock_fs: data_2}, {mock_fs: data_3}]) with patch.dict(disk.__salt__, {'disk.usage': mock}): - comt = ('Named disk mount not present ') - ret.update({'comment': comt}) - self.assertDictEqual(disk.status(mock_fs), ret) - - comt = ('minimum must be less than maximum ') - ret.update({'comment': comt}) - self.assertDictEqual(disk.status(mock_fs, '10', '20', absolute=True), ret) - - comt = ('Disk used space is below minimum of 10 KB at 8 KB') - ret.update({'comment': comt, 'data': {'capacity': '8 %', 'used': '8'}}) - self.assertDictEqual(disk.status(mock_fs, '20', '10', absolute=True), ret) - - comt = ('Disk used space is above maximum of 20 KB at 22 KB') - ret.update({'comment': comt, 'data': {'capacity': '22 %', 'used': '22'}}) - self.assertDictEqual(disk.status(mock_fs, '20', '10', absolute=True), ret) - - comt = ('Disk used space in acceptable range') - ret.update({'comment': comt, 'result': True, - 'data': {'capacity': '15 %', 'used': '15'}}) - self.assertDictEqual(disk.status(mock_fs, '20', '10', absolute=True), ret) + mock = MagicMock(return_value=False) + with patch.object(path, 'isdir', mock): + comt = 'Disk mount / not present. Directory / does not exist or is not a directory' + ret.update({'comment': comt}) + self.assertDictEqual(disk.status(mock_fs), ret) + + comt = 'minimum must be less than maximum ' + ret.update({'comment': comt}) + self.assertDictEqual(disk.status(mock_fs, '10', '20', absolute=True), ret) + + comt = 'Disk used space is below minimum of 10 KB at 8 KB' + ret.update({'comment': comt, 'data': data_1}) + self.assertDictEqual(disk.status(mock_fs, '20', '10', absolute=True), ret) + + comt = 'Disk used space is above maximum of 20 KB at 22 KB' + ret.update({'comment': comt, 'data': data_2}) + self.assertDictEqual(disk.status(mock_fs, '20', '10', absolute=True), ret) + + comt = 'Disk used space in acceptable range' + ret.update({'comment': comt, 'result': True, 'data': data_3}) + self.assertDictEqual(disk.status(mock_fs, '20', '10', absolute=True), ret) + + def test_path_missing(self): + mock_fs = '/bar' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk mount {0} not present. Directory {0} does not exist or is not a directory'.format( + mock_fs), + 'changes': {}, + 'data': {}} + mock = MagicMock(return_value=False) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '58', '55', absolute=True, free=False), mock_ret) + + # acceptable range + def test_path_used_absolute_acceptable(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '58', '55', absolute=True, free=False), mock_ret) + + def test_path_used_relative_acceptable(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '100%', '57%', absolute=False, free=False), mock_ret) + + def test_path_free_absolute_acceptable(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '100', '42', absolute=True, free=True), mock_ret) + + def test_path_free_relative_acceptable(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '42%', '41%', absolute=False, free=True), mock_ret) + + def test_mount_used_absolute_acceptable(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + self.assertDictEqual(disk.status(mock_fs, '2172881', '2172880', absolute=True, free=False), mock_ret) + + def test_mount_used_relative_acceptable(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + + self.assertDictEqual(disk.status(mock_fs, '7%', '6%', absolute=False, free=False), mock_ret) + + def test_mount_free_absolute_acceptable(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + self.assertDictEqual(disk.status(mock_fs, '37087976', '37087975', absolute=True, free=True), mock_ret) + + def test_mount_free_relative_acceptable(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': True, + 'comment': 'Disk used space in acceptable range', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + + self.assertDictEqual(disk.status(mock_fs, '100%', '94%', absolute=False, free=True), mock_ret) + + # below minimum + def test_path_used_absolute_below(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is below minimum of 59 KB at 58 KB', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '60', '59', absolute=True, free=False), mock_ret) + + def test_path_used_relative_below(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is below minimum of 59 % at 58.0 %', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '60%', '59%', absolute=False, free=False), mock_ret) + + def test_path_free_absolute_below(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is below minimum of 43 KB at 42 KB', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '100', '43', absolute=True, free=True), mock_ret) + + def test_path_free_relative_below(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is below minimum of 43 % at 42.0 %', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '100%', '43%', absolute=False, free=True), mock_ret) + + def test_mount_used_absolute_below(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is below minimum of 2172881 KB at 2172880 KB', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + self.assertDictEqual(disk.status(mock_fs, '2172882', '2172881', absolute=True, free=False), mock_ret) + + def test_mount_used_relative_below(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is below minimum of 7 % at 6 %', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + + self.assertDictEqual(disk.status(mock_fs, '8%', '7%', absolute=False, free=False), mock_ret) + + def test_mount_free_absolute_below(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is below minimum of 37087977 KB at 37087976 KB', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + self.assertDictEqual(disk.status(mock_fs, '37087978', '37087977', absolute=True, free=True), mock_ret) + + def test_mount_free_relative_below(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is below minimum of 95 % at 94 %', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + + self.assertDictEqual(disk.status(mock_fs, '100%', '95%', absolute=False, free=True), mock_ret) + + # above maximum + def test_path_used_absolute_above(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is above maximum of 57 KB at 58 KB', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '57', '56', absolute=True, free=False), mock_ret) + + def test_path_used_relative_above(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is above maximum of 57 % at 58.0 %', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '57%', '56%', absolute=False, free=False), mock_ret) + + def test_path_free_absolute_above(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is above maximum of 41 KB at 42 KB', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '41', '40', absolute=True, free=True), mock_ret) + + def test_path_free_relative_above(self): + mock_fs = '/foo' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is above maximum of 41 % at 42.0 %', + 'changes': {}, + 'data': self.mock_data_path} + mock = MagicMock(return_value=True) + with patch.object(path, 'isdir', mock): + self.assertDictEqual(disk.status(mock_fs, '41%', '40%', absolute=False, free=True), mock_ret) + + def test_mount_used_absolute_above(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is above maximum of 2172879 KB at 2172880 KB', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + self.assertDictEqual(disk.status(mock_fs, '2172879', '2172878', absolute=True, free=False), mock_ret) + + def test_mount_used_relative_above(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk used space is above maximum of 5 % at 6 %', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + + self.assertDictEqual(disk.status(mock_fs, '5%', '4%', absolute=False, free=False), mock_ret) + + def test_mount_free_absolute_above(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is above maximum of 37087975 KB at 37087976 KB', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + self.assertDictEqual(disk.status(mock_fs, '37087975', '37087974', absolute=True, free=True), mock_ret) + + def test_mount_free_relative_above(self): + mock_fs = '/' + mock_ret = {'name': mock_fs, + 'result': False, + 'comment': 'Disk available space is above maximum of 93 % at 94 %', + 'changes': {}, + 'data': self.mock_data[mock_fs]} + + self.assertDictEqual(disk.status(mock_fs, '93%', '92%', absolute=False, free=True), mock_ret) From 11b39d2a6641715e6f2650d5db9abb6110d9a829 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 18:08:37 -0700 Subject: [PATCH 06/21] Porting PR #49616 to 2019.2.1 --- doc/topics/grains/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/topics/grains/index.rst b/doc/topics/grains/index.rst index 106f0829cf92..8cfe7d1da978 100644 --- a/doc/topics/grains/index.rst +++ b/doc/topics/grains/index.rst @@ -63,8 +63,7 @@ Just add the option :conf_minion:`grains` and pass options to it: cab_u: 14-15 Then status data specific to your servers can be retrieved via Salt, or used -inside of the State system for matching. It also makes targeting, in the case -of the example above, simply based on specific data about your deployment. +inside of the State system for matching. It also makes it possible to target based on specific data about your deployment, as in the example above. Grains in /etc/salt/grains @@ -292,7 +291,7 @@ the Salt minion and provides the principal example of how to write grains: Syncing Grains ============== -Syncing grains can be done a number of ways, they are automatically synced when +Syncing grains can be done a number of ways. They are automatically synced when :mod:`state.highstate ` is called, or (as noted above) the grains can be manually synced and reloaded by calling the :mod:`saltutil.sync_grains ` or From 3c60e25d19d6a94bd783d210d032d71c85c71f68 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 18 Sep 2019 18:16:32 -0700 Subject: [PATCH 07/21] Porting PR #49293 to 2019.2.1 --- doc/ref/modules/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ref/modules/index.rst b/doc/ref/modules/index.rst index 73c47f168055..b19d91e1e8f8 100644 --- a/doc/ref/modules/index.rst +++ b/doc/ref/modules/index.rst @@ -298,7 +298,7 @@ prevent loading if dependencies are not met. Since ``__virtual__`` is called before the module is loaded, ``__salt__`` will be unreliable as not all modules will be available at this point in time. The -``__pillar`` and ``__grains__`` :ref:`"dunder" dictionaries ` +``__pillar__`` and ``__grains__`` :ref:`"dunder" dictionaries ` are available however. .. note:: From c104f3ae3fee90fd2a98e9f7af972d66206e1df1 Mon Sep 17 00:00:00 2001 From: xeacott Date: Fri, 13 Sep 2019 14:19:24 -0600 Subject: [PATCH 08/21] Address file.managed with binary file --- salt/modules/file.py | 6 +++--- tests/integration/states/test_file.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/salt/modules/file.py b/salt/modules/file.py index d0847c669b76..49ca570e02df 100644 --- a/salt/modules/file.py +++ b/salt/modules/file.py @@ -5336,11 +5336,11 @@ def manage_file(name, # Write the static contents to a temporary file tmp = salt.utils.files.mkstemp(prefix=salt.utils.files.TEMPFILE_PREFIX, text=True) - if salt.utils.platform.is_windows(): - contents = os.linesep.join( - _splitlines_preserving_trailing_newline(contents)) with salt.utils.files.fopen(tmp, 'wb') as tmp_: if encoding: + if salt.utils.platform.is_windows(): + contents = os.linesep.join( + _splitlines_preserving_trailing_newline(contents)) log.debug('File will be encoded with %s', encoding) tmp_.write(contents.encode(encoding=encoding, errors=encoding_errors)) else: diff --git a/tests/integration/states/test_file.py b/tests/integration/states/test_file.py index db006161ad50..205c46b62f52 100644 --- a/tests/integration/states/test_file.py +++ b/tests/integration/states/test_file.py @@ -2696,6 +2696,26 @@ def test_binary_contents(self): except OSError: pass + def test_binary_contents_twice(self): + ''' + This test ensures that after a binary file is created, salt can confirm + that the file is in the correct state. + ''' + # Create a binary file + name = os.path.join(TMP, '1px.gif') + + # First run state ensures file is created + ret = self.run_state('file.managed', name=name, contents=BINARY_FILE) + self.assertSaltTrueReturn(ret) + + # Second run of state ensures file is in correct state + ret = self.run_state('file.managed', name=name, contents=BINARY_FILE) + self.assertSaltTrueReturn(ret) + try: + os.remove(name) + except OSError: + pass + @skip_if_not_root @skipIf(not HAS_PWD, "pwd not available. Skipping test") @skipIf(not HAS_GRP, "grp not available. Skipping test") From 5958b7e973c83012ba4a8fd235d0322359adb488 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Tue, 5 Mar 2019 08:03:35 -0800 Subject: [PATCH 09/21] Merge pull request #51949 from mchugh19/remove_hipchat Remove hipchat engine due to service retirement --- doc/ref/engines/all/index.rst | 1 - doc/ref/engines/all/salt.engines.hipchat.rst | 6 - salt/engines/hipchat.py | 429 ------------------- 3 files changed, 436 deletions(-) delete mode 100644 doc/ref/engines/all/salt.engines.hipchat.rst delete mode 100644 salt/engines/hipchat.py diff --git a/doc/ref/engines/all/index.rst b/doc/ref/engines/all/index.rst index 15f6d8a667e0..d9b19e016666 100644 --- a/doc/ref/engines/all/index.rst +++ b/doc/ref/engines/all/index.rst @@ -11,7 +11,6 @@ engine modules :template: autosummary.rst.tmpl docker_events - hipchat http_logstash ircbot junos_syslog diff --git a/doc/ref/engines/all/salt.engines.hipchat.rst b/doc/ref/engines/all/salt.engines.hipchat.rst deleted file mode 100644 index 4b12b40f99b2..000000000000 --- a/doc/ref/engines/all/salt.engines.hipchat.rst +++ /dev/null @@ -1,6 +0,0 @@ -salt.engines.hipchat module -=========================== - -.. automodule:: salt.engines.hipchat - :members: - :undoc-members: diff --git a/salt/engines/hipchat.py b/salt/engines/hipchat.py deleted file mode 100644 index d25e428e72ea..000000000000 --- a/salt/engines/hipchat.py +++ /dev/null @@ -1,429 +0,0 @@ -# -*- coding: utf-8 -*- -''' -An engine that reads messages from Hipchat and sends them to the Salt -event bus. Alternatively Salt commands can be sent to the Salt master -via Hipchat by setting the control parameter to ``True`` and using command -prefaced with a ``!``. Only token key is required, but room and control -keys make the engine interactive. - -.. versionadded: 2016.11.0 - -:depends: hypchat -:configuration: Example configuration - - .. code-block:: yaml - - engines: - - hipchat: - api_url: http://api.hipchat.myteam.com - token: 'XXXXXX' - room: 'salt' - control: True - valid_users: - - SomeUser - valid_commands: - - test.ping - - cmd.run - - list_jobs - - list_commands - aliases: - list_jobs: - cmd: jobs.list_jobs - list_commands: - cmd: pillar.get salt:engines:hipchat:valid_commands target=saltmaster - max_rooms: 0 - wait_time: 1 -''' - -from __future__ import absolute_import, print_function, unicode_literals -import logging -import time -import os - - -try: - import hypchat -except ImportError: - hypchat = None - -import salt.utils.args -import salt.utils.event -import salt.utils.files -import salt.utils.http -import salt.utils.json -import salt.utils.stringutils -import salt.runner -import salt.client -import salt.loader -import salt.output -from salt.ext import six - -log = logging.getLogger(__name__) - -_DEFAULT_API_URL = 'https://api.hipchat.com' -_DEFAULT_SLEEP = 5 -_DEFAULT_MAX_ROOMS = 1000 - -__virtualname__ = 'hipchat' - - -def __virtual__(): - return __virtualname__ if hypchat is not None \ - else (False, 'hypchat is not installed') - - -def _publish_file(token, room, filepath, message='', outputter=None, api_url=None): - ''' - Send file to a HipChat room via API version 2 - - Parameters - ---------- - token : str - HipChat API version 2 compatible token - must be token for active user - room: str - Name or API ID of the room to notify - filepath: str - Full path of file to be sent - message: str, optional - Message to send to room - api_url: str, optional - Hipchat API URL to use, defaults to http://api.hipchat.com - ''' - - if not os.path.isfile(filepath): - raise ValueError("File '{0}' does not exist".format(filepath)) - if len(message) > 1000: - raise ValueError('Message too long') - - url = "{0}/v2/room/{1}/share/file".format(api_url, room) - headers = {'Content-type': 'multipart/related; boundary=boundary123456'} - headers['Authorization'] = "Bearer " + token - msg = salt.utils.json.dumps({'message': message}) - - # future lint: disable=blacklisted-function - with salt.utils.files.fopen(filepath, 'rb') as rfh: - payload = str('''\ ---boundary123456 -Content-Type: application/json; charset=UTF-8 -Content-Disposition: attachment; name="metadata" - -{0} - ---boundary123456 -Content-Disposition: attachment; name="file"; filename="{1}" - -{2} - ---boundary123456--\ -''').format(msg, - os.path.basename(salt.utils.stringutils.to_str(filepath)), - salt.utils.stringutils.to_str(rfh.read())) - # future lint: enable=blacklisted-function - - salt.utils.http.query(url, method='POST', header_dict=headers, data=payload) - - -def _publish_html_message(token, room, data, message='', outputter='nested', api_url=None): - ''' - Publishes the HTML-formatted message. - ''' - url = "{0}/v2/room/{1}/notification".format(api_url, room) - headers = { - 'Content-type': 'text/plain' - } - headers['Authorization'] = 'Bearer ' + token - salt.utils.http.query( - url, - 'POST', - data=message, - decode=True, - status=True, - header_dict=headers, - opts=__opts__, - ) - headers['Content-type'] = 'text/html' - message = salt.output.html_format(data, outputter, opts=__opts__) - salt.utils.http.query( - url, - 'POST', - data=message, - decode=True, - status=True, - header_dict=headers, - opts=__opts__, - ) - - -def _publish_code_message(token, room, data, message='', outputter='nested', api_url=None): - ''' - Publishes the output format as code. - ''' - url = "{0}/v2/room/{1}/notification".format(api_url, room) - headers = { - 'Content-type': 'text/plain' - } - headers['Authorization'] = 'Bearer ' + token - salt.utils.http.query( - url, - 'POST', - data=message, - decode=True, - status=True, - header_dict=headers, - opts=__opts__, - ) - message = '/code ' - message += salt.output.string_format(data, outputter, opts=__opts__) - salt.utils.http.query( - url, - 'POST', - data=message, - decode=True, - status=True, - header_dict=headers, - opts=__opts__, - ) - - -def start(token, - room='salt', - aliases=None, - valid_users=None, - valid_commands=None, - control=False, - trigger="!", - tag='salt/engines/hipchat/incoming', - api_key=None, - api_url=None, - max_rooms=None, - wait_time=None, - output_type='file', - outputter='nested'): - ''' - Listen to Hipchat messages and forward them to Salt. - - token - The HipChat API key. It requires a key for global usgae, - assigned per user, rather than room. - - room - The HipChat room name. - - aliases - Define custom aliases. - - valid_users - Restrict access only to certain users. - - valid_commands - Restrict the execution to a limited set of commands. - - control - Send commands to the master. - - trigger: ``!`` - Special character that triggers the execution of salt commands. - - tag: ``salt/engines/hipchat/incoming`` - The event tag on the Salt bus. - - api_url: ``https://api.hipchat.com`` - The URL to the HipChat API. - - .. versionadded:: 2017.7.0 - - max_rooms: ``1000`` - Maximum number of rooms allowed to fetch. If set to 0, - it is able to retrieve the entire list of rooms. - - wait_time: ``5`` - Maximum wait time, in seconds. - - output_type: ``file`` - The type of the output. Choose bewteen: - - - ``file``: save the output into a temporary file and upload - - ``html``: send the output as HTML - - ``code``: send the output as code - - This can be overridden when executing a command, using the ``--out-type`` argument. - - .. versionadded:: 2017.7.0 - - outputter: ``nested`` - The format to display the data, using the outputters available on the CLI. - This argument can also be overridden when executing a command, using the ``--out`` option. - - .. versionadded:: 2017.7.0 - - HipChat Example: - - .. code-block:: text - - ! test.ping - ! test.ping target=minion1 - ! test.ping --out=nested - ! test.ping --out-type=code --out=table - ''' - target_room = None - - if __opts__.get('__role') == 'master': - fire_master = salt.utils.event.get_master_event( - __opts__, - __opts__['sock_dir']).fire_event - else: - fire_master = None - - def fire(tag, msg): - ''' - fire event to salt bus - ''' - - if fire_master: - fire_master(msg, tag) - else: - __salt__['event.send'](tag, msg) - - def _eval_bot_mentions(all_messages, trigger): - ''' yield partner message ''' - for message in all_messages: - message_text = message['message'] - if message_text.startswith(trigger): - fire(tag, message) - text = message_text.replace(trigger, '').strip() - yield message['from']['mention_name'], text - - token = token or api_key - if not token: - raise UserWarning("Hipchat token not found") - - runner_functions = sorted(salt.runner.Runner(__opts__).functions) - - if not api_url: - api_url = _DEFAULT_API_URL - hipc = hypchat.HypChat(token, endpoint=api_url) - if not hipc: - raise UserWarning("Unable to connect to hipchat") - - log.debug('Connected to Hipchat') - rooms_kwargs = {} - if max_rooms is None: - max_rooms = _DEFAULT_MAX_ROOMS - rooms_kwargs['max_results'] = max_rooms - elif max_rooms > 0: - rooms_kwargs['max_results'] = max_rooms - # if max_rooms is 0 => retrieve all (rooms_kwargs is empty dict) - all_rooms = hipc.rooms(**rooms_kwargs)['items'] - for a_room in all_rooms: - if a_room['name'] == room: - target_room = a_room - if not target_room: - log.debug("Unable to connect to room %s", room) - # wait for a bit as to not burn through api calls - time.sleep(30) - raise UserWarning("Unable to connect to room {0}".format(room)) - - after_message_id = target_room.latest(maxResults=1)['items'][0]['id'] - - while True: - try: - new_messages = target_room.latest( - not_before=after_message_id)['items'] - except hypchat.requests.HttpServiceUnavailable: - time.sleep(15) - continue - - after_message_id = new_messages[-1]['id'] - for partner, text in _eval_bot_mentions(new_messages[1:], trigger): - # bot summoned by partner - - if not control: - log.debug("Engine not configured for control") - return - - # Ensure the user is allowed to run commands - if valid_users: - if partner not in valid_users: - target_room.message('{0} not authorized to run Salt commands'.format(partner)) - return - - args = [] - kwargs = {} - - cmdline = salt.utils.args.shlex_split(text) - cmd = cmdline[0] - - # Evaluate aliases - if aliases and isinstance(aliases, dict) and cmd in aliases.keys(): - cmdline = aliases[cmd].get('cmd') - cmdline = salt.utils.args.shlex_split(cmdline) - cmd = cmdline[0] - - # Parse args and kwargs - if len(cmdline) > 1: - for item in cmdline[1:]: - if '=' in item: - (key, value) = item.split('=', 1) - kwargs[key] = value - else: - args.append(item) - - # Check for target. Otherwise assume * - if 'target' not in kwargs: - target = '*' - else: - target = kwargs['target'] - del kwargs['target'] - - # Check for tgt_type. Otherwise assume glob - if 'tgt_type' not in kwargs: - tgt_type = 'glob' - else: - tgt_type = kwargs['tgt_type'] - del kwargs['tgt_type'] - - # Check for outputter. Otherwise assume nested - if '--out' in kwargs: - outputter = kwargs['--out'] - del kwargs['--out'] - - # Check for outputter. Otherwise assume nested - if '--out-type' in kwargs: - output_type = kwargs['--out-type'] - del kwargs['--out-type'] - - # Ensure the command is allowed - if valid_commands: - if cmd not in valid_commands: - target_room.message('Using {0} is not allowed.'.format(cmd)) - return - - ret = {} - if cmd in runner_functions: - runner = salt.runner.RunnerClient(__opts__) - ret = runner.cmd(cmd, arg=args, kwarg=kwargs) - - # Default to trying to run as a client module. - else: - local = salt.client.LocalClient() - ret = local.cmd('{0}'.format(target), cmd, args, kwargs, tgt_type='{0}'.format(tgt_type)) - - nice_args = (' ' + ' '.join(args)) if args else '' - nice_kwargs = (' ' + ' '.join('{0}={1}'.format(key, value) for (key, value) in six.iteritems(kwargs))) \ - if kwargs else '' - message_string = '@{0} Results for: {1}{2}{3} on {4}'.format(partner, - cmd, - nice_args, - nice_kwargs, - target) - if output_type == 'html': - _publish_html_message(token, room, ret, message=message_string, outputter=outputter, api_url=api_url) - elif output_type == 'code': - _publish_code_message(token, room, ret, message=message_string, outputter=outputter, api_url=api_url) - else: - tmp_path_fn = salt.utils.files.mkstemp() - with salt.utils.files.fopen(tmp_path_fn, 'w+') as fp_: - salt.utils.json.dump(ret, fp_, sort_keys=True, indent=4) - _publish_file(token, room, tmp_path_fn, message=message_string, api_url=api_url) - salt.utils.files.safe_rm(tmp_path_fn) - time.sleep(wait_time or _DEFAULT_SLEEP) From 0c251cf9192ba708b72d61d79bd796bc50345ca7 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Sat, 16 Mar 2019 08:35:21 -0700 Subject: [PATCH 10/21] Merge pull request #51996 from mchugh19/remove_hipchat Add release note about hipchat removal --- doc/ref/modules/all/index.rst | 1 - doc/ref/modules/all/salt.modules.hipchat.rst | 6 - doc/ref/returners/all/index.rst | 1 - .../all/salt.returners.hipchat_return.rst | 6 - doc/ref/states/all/index.rst | 1 - doc/ref/states/all/salt.states.hipchat.rst | 6 - doc/topics/releases/neon.rst | 32 ++ salt/modules/hipchat.py | 356 ---------------- salt/returners/hipchat_return.py | 400 ------------------ salt/states/hipchat.py | 144 ------- tests/unit/modules/test_hipchat.py | 93 ---- tests/unit/states/test_hipchat.py | 74 ---- 12 files changed, 32 insertions(+), 1088 deletions(-) delete mode 100644 doc/ref/modules/all/salt.modules.hipchat.rst delete mode 100644 doc/ref/returners/all/salt.returners.hipchat_return.rst delete mode 100644 doc/ref/states/all/salt.states.hipchat.rst delete mode 100644 salt/modules/hipchat.py delete mode 100644 salt/returners/hipchat_return.py delete mode 100644 salt/states/hipchat.py delete mode 100644 tests/unit/modules/test_hipchat.py delete mode 100644 tests/unit/states/test_hipchat.py diff --git a/doc/ref/modules/all/index.rst b/doc/ref/modules/all/index.rst index 94a42d8dad80..4fb32ea9b4c7 100644 --- a/doc/ref/modules/all/index.rst +++ b/doc/ref/modules/all/index.rst @@ -171,7 +171,6 @@ execution modules hashutil heat hg - hipchat hosts http ifttt diff --git a/doc/ref/modules/all/salt.modules.hipchat.rst b/doc/ref/modules/all/salt.modules.hipchat.rst deleted file mode 100644 index 09b519a3a93a..000000000000 --- a/doc/ref/modules/all/salt.modules.hipchat.rst +++ /dev/null @@ -1,6 +0,0 @@ -==================== -salt.modules.hipchat -==================== - -.. automodule:: salt.modules.hipchat - :members: diff --git a/doc/ref/returners/all/index.rst b/doc/ref/returners/all/index.rst index 9ee7a3b0b3d0..68831349e34c 100644 --- a/doc/ref/returners/all/index.rst +++ b/doc/ref/returners/all/index.rst @@ -19,7 +19,6 @@ returner modules elasticsearch_return etcd_return highstate_return - hipchat_return influxdb_return kafka_return librato_return diff --git a/doc/ref/returners/all/salt.returners.hipchat_return.rst b/doc/ref/returners/all/salt.returners.hipchat_return.rst deleted file mode 100644 index 37e5d6d4be11..000000000000 --- a/doc/ref/returners/all/salt.returners.hipchat_return.rst +++ /dev/null @@ -1,6 +0,0 @@ -============================= -salt.returners.hipchat_return -============================= - -.. automodule:: salt.returners.hipchat_return - :members: diff --git a/doc/ref/states/all/index.rst b/doc/ref/states/all/index.rst index fb9ac25953a0..292a728a0614 100644 --- a/doc/ref/states/all/index.rst +++ b/doc/ref/states/all/index.rst @@ -115,7 +115,6 @@ state modules group heat hg - hipchat host http icinga2 diff --git a/doc/ref/states/all/salt.states.hipchat.rst b/doc/ref/states/all/salt.states.hipchat.rst deleted file mode 100644 index 921bd1bbe89a..000000000000 --- a/doc/ref/states/all/salt.states.hipchat.rst +++ /dev/null @@ -1,6 +0,0 @@ -=================== -salt.states.hipchat -=================== - -.. automodule:: salt.states.hipchat - :members: diff --git a/doc/topics/releases/neon.rst b/doc/topics/releases/neon.rst index 780a8d9e429b..3b250266d8d5 100644 --- a/doc/topics/releases/neon.rst +++ b/doc/topics/releases/neon.rst @@ -3,3 +3,35 @@ ================================== Salt Release Notes - Codename Neon ================================== + +Deprecations +============ + +Module Deprecations +------------------- + +- The hipchat module has been removed due to the service being retired. + :py:func:`Google Chat `, + :py:func:`MS Teams `, or + :py:func:`Slack ` may be suitable replacements. + + +State Deprecations +------------------ + +- The hipchat state has been removed due to the service being retired. + :py:func:`MS Teams ` or + :py:func:`Slack ` may be suitable replacements. + +Engine Removal +-------------- + +- The hipchat engine has been removed due to the service being retired. For users migrating + to Slack, the :py:func:`slack ` engine may be a suitable replacement. + +Returner Removal +---------------- + +- The hipchat returner has been removed due to the service being retired. For users migrating + to Slack, the :py:func:`slack ` returner may be a suitable + replacement. diff --git a/salt/modules/hipchat.py b/salt/modules/hipchat.py deleted file mode 100644 index db7e5891279e..000000000000 --- a/salt/modules/hipchat.py +++ /dev/null @@ -1,356 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Module for sending messages to hipchat. - -.. versionadded:: 2015.5.0 - -:configuration: This module can be used by either passing an api key and version - directly or by specifying both in a configuration profile in the salt - master/minion config. - - It is possible to use a different API than http://api.hipchat.com, - by specifying the API URL in config as api_url, or by passing the value directly. - - For example: - - .. code-block:: yaml - - hipchat: - api_key: peWcBiMOS9HrZG15peWcBiMOS9HrZG15 - api_version: v1 - - Custom API Example: - - .. code-block:: yaml - - hipchat: - api_url: http://api.hipchat.myteam.com - api_key: peWcBiMOS9HrZG15peWcBiMOS9HrZG15 - api_version: v2 -''' -# Import Python Libs -from __future__ import absolute_import, print_function, unicode_literals -import logging - -# Import Salt libs -import salt.utils.http -import salt.utils.json - -# Import 3rd-party Libs -# pylint: disable=import-error,no-name-in-module,redefined-builtin -from salt.ext.six.moves.urllib.parse import urljoin as _urljoin -from salt.ext.six.moves.urllib.parse import urlencode as _urlencode -from salt.ext.six.moves import range -import salt.ext.six.moves.http_client - -# pylint: enable=import-error,no-name-in-module,redefined-builtin - -log = logging.getLogger(__name__) - -__virtualname__ = 'hipchat' - - -def __virtual__(): - ''' - Return virtual name of the module. - - :return: The virtual name of the module. - ''' - return __virtualname__ - - -def _query(function, - api_url=None, - api_key=None, - api_version=None, - room_id=None, - method='GET', - data=None): - ''' - HipChat object method function to construct and execute on the API URL. - - :param api_url: The HipChat API URL. - :param api_key: The HipChat api key. - :param function: The HipChat api function to perform. - :param api_version: The HipChat api version (v1 or v2). - :param method: The HTTP method, e.g. GET or POST. - :param data: The data to be sent for POST method. - :return: The json response from the API call or False. - ''' - headers = {} - query_params = {} - - if not api_url: - try: - options = __salt__['config.option']('hipchat') - api_url = options.get('api_url') - except (NameError, KeyError, AttributeError): - pass # not mandatory, thus won't fail if not found - - if not api_key or not api_version: - try: - options = __salt__['config.option']('hipchat') - if not api_key: - api_key = options.get('api_key') - if not api_version: - api_version = options.get('api_version') - except (NameError, KeyError, AttributeError): - log.error("No HipChat api key or version found.") - return False - - if room_id: - room_id = 'room/{0}/notification'.format(room_id) - else: - room_id = 'room/0/notification' - - hipchat_functions = { - 'v1': { - 'rooms': { - 'request': 'rooms/list', - 'response': 'rooms', - }, - 'users': { - 'request': 'users/list', - 'response': 'users', - }, - 'message': { - 'request': 'rooms/message', - 'response': 'status', - }, - }, - 'v2': { - 'rooms': { - 'request': 'room', - 'response': 'items', - }, - 'users': { - 'request': 'user', - 'response': 'items', - }, - 'message': { - 'request': room_id, - 'response': None, - }, - }, - } - - use_api_url = 'https://api.hipchat.com' # default API URL - if api_url: - use_api_url = api_url - base_url = _urljoin(use_api_url, api_version + '/') - path = hipchat_functions.get(api_version).get(function).get('request') - url = _urljoin(base_url, path, False) - - if api_version == 'v1': - query_params['format'] = 'json' - query_params['auth_token'] = api_key - - if method == 'POST': - headers['Content-Type'] = 'application/x-www-form-urlencoded' - - if data: - if data.get('notify', None): - data['notify'] = 1 - data = _urlencode(data) - elif api_version == 'v2': - headers['Authorization'] = 'Bearer {0}'.format(api_key) - if data: - data = salt.utils.json.dumps(data) - - if method == 'POST': - headers['Content-Type'] = 'application/json' - else: - log.error('Unsupported HipChat API version') - return False - - result = salt.utils.http.query( - url, - method, - params=query_params, - data=data, - decode=True, - status=True, - header_dict=headers, - opts=__opts__, - ) - - if result.get('status', None) == salt.ext.six.moves.http_client.OK: - response = hipchat_functions.get(api_version).get(function).get('response') - return result.get('dict', {}).get(response, None) - elif result.get('status', None) == salt.ext.six.moves.http_client.NO_CONTENT and \ - api_version == 'v2': - return True - else: - log.debug(url) - log.debug(query_params) - log.debug(data) - log.debug(result) - if result.get('error'): - log.error(result) - return False - - -def list_rooms(api_url=None, - api_key=None, - api_version=None): - ''' - List all HipChat rooms. - - :param api_url: The HipChat API URL, if not specified in the configuration. - :param api_key: The HipChat admin api key. - :param api_version: The HipChat api version, if not specified in the configuration. - :return: The room list. - - CLI Example: - - .. code-block:: bash - - salt '*' hipchat.list_rooms - - salt '*' hipchat.list_rooms api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15 api_version=v1 - ''' - foo = _query(function='rooms', - api_url=api_url, - api_key=api_key, - api_version=api_version) - log.debug('foo %s', foo) - return foo - - -def list_users(api_url=None, - api_key=None, - api_version=None): - ''' - List all HipChat users. - - :param api_url: The HipChat API URL, if not specified in the configuration. - :param api_key: The HipChat admin api key. - :param api_version: The HipChat api version, if not specified in the configuration. - :return: The user list. - - CLI Example: - - .. code-block:: bash - - salt '*' hipchat.list_users - - salt '*' hipchat.list_users api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15 api_version=v1 - ''' - return _query(function='users', - api_url=api_url, - api_key=api_key, - api_version=api_version) - - -def find_room(name, - api_url=None, - api_key=None, - api_version=None): - ''' - Find a room by name and return it. - - :param name: The room name. - :param api_url: The HipChat API URL, if not specified in the configuration. - :param api_key: The HipChat admin api key. - :param api_version: The HipChat api version, if not specified in the configuration. - :return: The room object. - - CLI Example: - - .. code-block:: bash - - salt '*' hipchat.find_room name="Development Room" - - salt '*' hipchat.find_room name="Development Room" api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15 api_version=v1 - ''' - rooms = list_rooms(api_url=api_url, - api_key=api_key, - api_version=api_version) - if rooms: - for x in range(0, len(rooms)): - if rooms[x]['name'] == name: - return rooms[x] - return False - - -def find_user(name, - api_url=None, - api_key=None, - api_version=None): - ''' - Find a user by name and return it. - - :param name: The user name. - :param api_url: The HipChat API URL, if not specified in the configuration. - :param api_key: The HipChat admin api key. - :param api_version: The HipChat api version, if not specified in the configuration. - :return: The user object. - - CLI Example: - - .. code-block:: bash - - salt '*' hipchat.find_user name="Thomas Hatch" - - salt '*' hipchat.find_user name="Thomas Hatch" api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15 api_version=v1 - ''' - users = list_users(api_url=api_url, - api_key=api_key, - api_version=api_version) - if users: - for x in range(0, len(users)): - if users[x]['name'] == name: - return users[x] - return False - - -def send_message(room_id, - message, - from_name, - api_url=None, - api_key=None, - api_version=None, - color='yellow', - notify=False): - ''' - Send a message to a HipChat room. - - :param room_id: The room id or room name, either will work. - :param message: The message to send to the HipChat room. - :param from_name: Specify who the message is from. - :param api_url: The HipChat api URL, if not specified in the configuration. - :param api_key: The HipChat api key, if not specified in the configuration. - :param api_version: The HipChat api version, if not specified in the configuration. - :param color: The color for the message, default: yellow. - :param notify: Whether to notify the room, default: False. - :return: Boolean if message was sent successfully. - - CLI Example: - - .. code-block:: bash - - salt '*' hipchat.send_message room_id="Development Room" message="Build is done" from_name="Build Server" - - salt '*' hipchat.send_message room_id="Development Room" message="Build failed" from_name="Build Server" color="red" notify=True - ''' - - parameters = dict() - parameters['room_id'] = room_id - parameters['from'] = from_name[:15] - parameters['message'] = message[:10000] - parameters['message_format'] = 'text' - parameters['color'] = color - parameters['notify'] = notify - - result = _query(function='message', - api_url=api_url, - api_key=api_key, - api_version=api_version, - room_id=room_id, - method='POST', - data=parameters) - - if result: - return True - else: - return False diff --git a/salt/returners/hipchat_return.py b/salt/returners/hipchat_return.py deleted file mode 100644 index a777a7322803..000000000000 --- a/salt/returners/hipchat_return.py +++ /dev/null @@ -1,400 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Return salt data via hipchat. - -.. versionadded:: 2015.5.0 - -The following fields can be set in the minion conf file:: - - hipchat.room_id (required) - hipchat.api_key (required) - hipchat.api_version (required) - hipchat.api_url (optional) - hipchat.from_name (required) - hipchat.color (optional) - hipchat.notify (optional) - hipchat.profile (optional) - hipchat.url (optional) - -.. note:: - - When using Hipchat's API v2, ``api_key`` needs to be assigned to the room with the - "Label" set to what you would have been set in the hipchat.from_name field. The v2 - API disregards the ``from_name`` in the data sent for the room notification and uses - the Label assigned through the Hipchat control panel. - -Alternative configuration values can be used by prefacing the configuration. -Any values not found in the alternative configuration will be pulled from -the default location:: - - hipchat.room_id - hipchat.api_key - hipchat.api_version - hipchat.api_url - hipchat.from_name - -Hipchat settings may also be configured as: - -.. code-block:: yaml - - hipchat: - room_id: RoomName - api_url: https://hipchat.myteam.con - api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - api_version: v1 - from_name: user@email.com - - alternative.hipchat: - room_id: RoomName - api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - api_version: v1 - from_name: user@email.com - - hipchat_profile: - hipchat.api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - hipchat.api_version: v1 - hipchat.from_name: user@email.com - - hipchat: - profile: hipchat_profile - room_id: RoomName - - alternative.hipchat: - profile: hipchat_profile - room_id: RoomName - - hipchat: - room_id: RoomName - api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - api_version: v1 - api_url: api.hipchat.com - from_name: user@email.com - -To use the HipChat returner, append '--return hipchat' to the salt command. - -.. code-block:: bash - - salt '*' test.ping --return hipchat - -To use the alternative configuration, append '--return_config alternative' to the salt command. - -.. versionadded:: 2015.5.0 - -.. code-block:: bash - - salt '*' test.ping --return hipchat --return_config alternative - -To override individual configuration items, append --return_kwargs '{"key:": "value"}' to the salt command. - -.. versionadded:: 2016.3.0 - -.. code-block:: bash - - salt '*' test.ping --return hipchat --return_kwargs '{"room_id": "another-room"}' - -''' -from __future__ import absolute_import, print_function, unicode_literals - -# Import Python libs -import pprint -import logging - -# pylint: disable=import-error,no-name-in-module -from salt.ext import six -from salt.ext.six.moves.urllib.parse import urljoin as _urljoin -from salt.ext.six.moves.urllib.parse import urlencode as _urlencode -import salt.ext.six.moves.http_client -# pylint: enable=import-error - -# Import Salt Libs -import salt.returners -import salt.utils.json - - -log = logging.getLogger(__name__) -__virtualname__ = 'hipchat' - - -def _get_options(ret=None): - ''' - Get the hipchat options from salt. - ''' - - defaults = {'color': 'yellow', - 'notify': False, - 'api_url': 'api.hipchat.com'} - - attrs = {'hipchat_profile': 'profile', - 'room_id': 'room_id', - 'from_name': 'from_name', - 'api_key': 'api_key', - 'api_version': 'api_version', - 'color': 'color', - 'notify': 'notify', - 'api_url': 'api_url', - } - - profile_attr = 'hipchat_profile' - - profile_attrs = {'from_jid': 'from_jid', - 'api_key': 'api_key', - 'api_version': 'api_key', - 'api_url': 'api_url', - } - - _options = salt.returners.get_returner_options(__virtualname__, - ret, - attrs, - profile_attr=profile_attr, - profile_attrs=profile_attrs, - __salt__=__salt__, - __opts__=__opts__, - defaults=defaults) - return _options - - -def __virtual__(): - ''' - Return virtual name of the module. - - :return: The virtual name of the module. - ''' - return __virtualname__ - - -def _query(function, - api_key=None, - api_version=None, - room_id=None, - api_url=None, - method='GET', - data=None): - ''' - HipChat object method function to construct and execute on the API URL. - - :param api_url: The HipChat API URL. - :param api_key: The HipChat api key. - :param function: The HipChat api function to perform. - :param api_version: The HipChat api version (v1 or v2). - :param method: The HTTP method, e.g. GET or POST. - :param data: The data to be sent for POST method. - :return: The json response from the API call or False. - ''' - headers = {} - query_params = {} - - if room_id: - room_id = 'room/{0}/notification'.format(six.text_type(room_id)) - else: - room_id = 'room/0/notification' - - hipchat_functions = { - 'v1': { - 'rooms': { - 'request': 'rooms/list', - 'response': 'rooms', - }, - 'users': { - 'request': 'users/list', - 'response': 'users', - }, - 'message': { - 'request': 'rooms/message', - 'response': 'status', - }, - }, - 'v2': { - 'rooms': { - 'request': 'room', - 'response': 'items', - }, - 'users': { - 'request': 'user', - 'response': 'items', - }, - 'message': { - 'request': room_id, - 'response': None, - }, - }, - } - - api_url = 'https://{0}'.format(api_url) - base_url = _urljoin(api_url, api_version + '/') - path = hipchat_functions.get(api_version).get(function).get('request') - url = _urljoin(base_url, path, False) - - if api_version == 'v1': - query_params['format'] = 'json' - query_params['auth_token'] = api_key - - if method == 'POST': - headers['Content-Type'] = 'application/x-www-form-urlencoded' - - if data: - if data.get('notify'): - data['notify'] = 1 - else: - data['notify'] = 0 - data = _urlencode(data) - elif api_version == 'v2': - headers['Content-Type'] = 'application/json' - headers['Authorization'] = 'Bearer {0}'.format(api_key) - if data: - data = salt.utils.json.dumps(data) - else: - log.error('Unsupported HipChat API version') - return False - - result = salt.utils.http.query( - url, - method, - params=query_params, - data=data, - decode=True, - status=True, - header_dict=headers, - opts=__opts__, - ) - - if result.get('status', None) == salt.ext.six.moves.http_client.OK: - response = hipchat_functions.get(api_version).get(function).get('response') - return result.get('dict', {}).get(response, None) - elif result.get('status', None) == salt.ext.six.moves.http_client.NO_CONTENT: - return False - else: - log.debug(url) - log.debug(query_params) - log.debug(data) - log.debug(result) - if result.get('error'): - log.error(result) - return False - - -def _send_message(room_id, - message, - from_name, - api_key=None, - api_version=None, - api_url=None, - color=None, - notify=False): - ''' - Send a message to a HipChat room. - :param room_id: The room id or room name, either will work. - :param message: The message to send to the HipChat room. - :param from_name: Specify who the message is from. - :param api_url: The HipChat API URL, if not specified in the configuration. - :param api_key: The HipChat api key, if not specified in the configuration. - :param api_version: The HipChat api version, if not specified in the configuration. - :param color: The color for the message, default: yellow. - :param notify: Whether to notify the room, default: False. - :return: Boolean if message was sent successfully. - ''' - - parameters = dict() - parameters['room_id'] = room_id - parameters['from'] = from_name[:15] - parameters['message'] = message[:10000] - parameters['message_format'] = 'text' - parameters['color'] = color - parameters['notify'] = notify - - result = _query(function='message', - api_key=api_key, - api_version=api_version, - room_id=room_id, - api_url=api_url, - method='POST', - data=parameters) - - if result: - return True - else: - return False - - -def _verify_options(options): - ''' - Verify Hipchat options and log warnings - - Returns True if all options can be verified, - otherwise False - ''' - if not options.get('room_id'): - log.error('hipchat.room_id not defined in salt config') - return False - - if not options.get('from_name'): - log.error('hipchat.from_name not defined in salt config') - return False - - if not options.get('api_key'): - log.error('hipchat.api_key not defined in salt config') - return False - - if not options.get('api_version'): - log.error('hipchat.api_version not defined in salt config') - return False - - return True - - -def returner(ret): - ''' - Send an hipchat message with the return data from a job - ''' - - _options = _get_options(ret) - - if not _verify_options(_options): - return - - message = ('id: {0}\r\n' - 'function: {1}\r\n' - 'function args: {2}\r\n' - 'jid: {3}\r\n' - 'return: {4}\r\n').format( - ret.get('id'), - ret.get('fun'), - ret.get('fun_args'), - ret.get('jid'), - pprint.pformat(ret.get('return'))) - - if ret.get('retcode') == 0: - color = _options.get('color') - else: - color = 'red' - - hipchat = _send_message(_options.get('room_id'), # room_id - message, # message - _options.get('from_name'), # from_name - api_key=_options.get('api_key'), - api_version=_options.get('api_version'), - api_url=_options.get('api_url'), - color=color, - notify=_options.get('notify')) - - return hipchat - - -def event_return(events): - ''' - Return event data to hipchat - ''' - _options = _get_options() - - for event in events: - # TODO: - # Pre-process messages to apply individualized colors for various - # event types. - log.trace('Hipchat returner received event: %s', event) - _send_message(_options.get('room_id'), # room_id - event['data'], # message - _options.get('from_name'), # from_name - api_key=_options.get('api_key'), - api_version=_options.get('api_version'), - api_url=_options.get('api_url'), - color=_options.get('color'), - notify=_options.get('notify')) diff --git a/salt/states/hipchat.py b/salt/states/hipchat.py deleted file mode 100644 index cb31d58dd9c7..000000000000 --- a/salt/states/hipchat.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Send a message to Hipchat -========================= - -This state is useful for sending messages to Hipchat during state runs. - -The property api_url is optional. By defaul will use the public HipChat API at https://api.hipchat.com - -.. versionadded:: 2015.5.0 - -.. code-block:: yaml - - hipchat-message: - hipchat.send_message: - - room_id: 123456 - - from_name: SuperAdmin - - message: 'This state was executed successfully.' - - api_url: https://hipchat.myteam.com - - api_key: peWcBiMOS9HrZG15peWcBiMOS9HrZG15 - - api_version: v1 - -The api key can be specified in the master or minion configuration like below: - -.. code-block:: yaml - - hipchat: - api_key: peWcBiMOS9HrZG15peWcBiMOS9HrZG15 - api_version: v1 - -''' - -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals - - -def __virtual__(): - ''' - Only load if the hipchat module is available in __salt__ - ''' - return 'hipchat' if 'hipchat.send_message' in __salt__ else False - - -def send_message(name, - room_id, - from_name, - message, - api_url=None, - api_key=None, - api_version=None, - message_color='yellow', - notify=False): - ''' - Send a message to a Hipchat room. - - .. code-block:: yaml - - hipchat-message: - hipchat.send_message: - - room_id: 123456 - - from_name: SuperAdmin - - message: 'This state was executed successfully.' - - api_url: https://hipchat.myteam.com - - api_key: peWcBiMOS9HrZG15peWcBiMOS9HrZG15 - - api_version: v1 - - message_color: green - - notify: True - - The following parameters are required: - - name - The unique name for this event. - - room_id - The room to send the message to. Can either be the ID or the name. - - from_name - The name of that is to be shown in the "from" field. - If not specified, defaults to. - - message - The message that is to be sent to the Hipchat room. - - The following parameters are optional: - - api_url - The API URl to be used. - If not specified here or in the configuration options of master or minion, - will use the public HipChat API: https://api.hipchat.com - - api_key - The api key for Hipchat to use for authentication, - if not specified in the configuration options of master or minion. - - api_version - The api version for Hipchat to use, - if not specified in the configuration options of master or minion. - - message_color - The color the Hipchat message should be displayed in. One of the following, default: yellow - "yellow", "red", "green", "purple", "gray", or "random". - - notify - Should a notification in the room be raised. - ''' - ret = {'name': name, - 'changes': {}, - 'result': False, - 'comment': ''} - - if __opts__['test']: - ret['comment'] = 'The following message is to be sent to Hipchat: {0}'.format(message) - ret['result'] = None - return ret - - if not room_id: - ret['comment'] = 'Hipchat room id is missing: {0}'.format(name) - return ret - - if not from_name: - ret['comment'] = 'Hipchat from name is missing: {0}'.format(name) - return ret - - if not message: - ret['comment'] = 'Hipchat message is missing: {0}'.format(name) - return ret - - ret['result'] = __salt__['hipchat.send_message']( - room_id=room_id, - message=message, - from_name=from_name, - api_url=api_url, - api_key=api_key, - api_version=api_version, - color=message_color, - notify=notify, - ) - - if ret and ret['result']: - ret['comment'] = 'Sent message: {0}'.format(name) - else: - ret['comment'] = 'Failed to send message: {0}'.format(name) - - return ret diff --git a/tests/unit/modules/test_hipchat.py b/tests/unit/modules/test_hipchat.py deleted file mode 100644 index b31be8c93398..000000000000 --- a/tests/unit/modules/test_hipchat.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -''' - :codeauthor: Jayesh Kariya -''' - -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals - -# Import Salt Testing Libs -from tests.support.mixins import LoaderModuleMockMixin -from tests.support.unit import TestCase, skipIf -from tests.support.mock import ( - MagicMock, - patch, - NO_MOCK, - NO_MOCK_REASON -) - -# Import Salt Libs -import salt.modules.hipchat as hipchat - - -@skipIf(NO_MOCK, NO_MOCK_REASON) -class HipchatTestCase(TestCase, LoaderModuleMockMixin): - ''' - Test cases for salt.modules.hipchat - ''' - def setup_loader_modules(self): - return {hipchat: {}} - - # 'list_rooms' function tests: 1 - - def test_list_rooms(self): - ''' - Test if it list all HipChat rooms. - ''' - with patch('salt.modules.hipchat._query', MagicMock(return_value=True)): - self.assertEqual(hipchat.list_rooms(), True) - - # 'list_users' function tests: 1 - - def test_list_users(self): - ''' - Test if it list all HipChat users. - ''' - with patch('salt.modules.hipchat._query', MagicMock(return_value=True)): - self.assertEqual(hipchat.list_users(), True) - - # 'find_room' function tests: 1 - - def test_find_room(self): - ''' - Test if it find a room by name and return it. - ''' - mock = MagicMock(return_value=[{'name': 'Development Room'}]) - with patch.object(hipchat, 'list_rooms', mock): - self.assertEqual(hipchat.find_room('Development Room'), - {'name': 'Development Room'}) - - self.assertEqual(hipchat.find_room('QA Room'), False) - - # 'find_user' function tests: 1 - - def test_find_user(self): - ''' - Test if it find a user by name and return it. - ''' - mock = MagicMock(return_value=[{'name': 'Thomas Hatch'}]) - with patch.object(hipchat, 'list_rooms', mock): - self.assertEqual(hipchat.find_room('Thomas Hatch'), - {'name': 'Thomas Hatch'}) - - self.assertEqual(hipchat.find_user('Salt QA'), False) - - # 'send_message' function tests: 1 - - def test_send_message(self): - ''' - Test if it send a message to a HipChat room. - ''' - with patch('salt.modules.hipchat._query', MagicMock(return_value=True)): - self.assertEqual(hipchat.send_message('Development Room', - 'Build is done', - 'Build Server'), True) - - def test_send_message_false(self): - ''' - Test if it send a message to a HipChat room. - ''' - with patch('salt.modules.hipchat._query', MagicMock(return_value=False)): - self.assertEqual(hipchat.send_message('Development Room', - 'Build is done', - 'Build Server'), False) diff --git a/tests/unit/states/test_hipchat.py b/tests/unit/states/test_hipchat.py deleted file mode 100644 index 722433a4af99..000000000000 --- a/tests/unit/states/test_hipchat.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -''' - :codeauthor: Jayesh Kariya -''' -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals - -# Import Salt Testing Libs -from tests.support.mixins import LoaderModuleMockMixin -from tests.support.unit import skipIf, TestCase -from tests.support.mock import ( - NO_MOCK, - NO_MOCK_REASON, - MagicMock, - patch) - -# Import Salt Libs -import salt.states.hipchat as hipchat - - -@skipIf(NO_MOCK, NO_MOCK_REASON) -class HipchatTestCase(TestCase, LoaderModuleMockMixin): - ''' - Test cases for salt.states.hipchat - ''' - def setup_loader_modules(self): - return {hipchat: {}} - - # 'send_message' function tests: 1 - - def test_send_message(self): - ''' - Test to send a message to a Hipchat room. - ''' - name = 'salt' - room_id = '123456' - from_name = 'SuperAdmin' - message = 'This state was executed successfully.' - - ret = {'name': name, - 'result': None, - 'comment': '', - 'changes': {}} - - with patch.dict(hipchat.__opts__, {'test': True}): - comt = ('The following message is to be sent to Hipchat: {0}' - .format(message)) - ret.update({'comment': comt}) - self.assertDictEqual(hipchat.send_message(name, room_id, from_name, - message), ret) - - with patch.dict(hipchat.__opts__, {'test': False}): - comt = ('Hipchat room id is missing: {0}'.format(name)) - ret.update({'comment': comt, 'result': False}) - self.assertDictEqual(hipchat.send_message(name, None, from_name, - message), ret) - - comt = ('Hipchat from name is missing: {0}'.format(name)) - ret.update({'comment': comt}) - self.assertDictEqual(hipchat.send_message(name, room_id, None, - message), ret) - - comt = ('Hipchat message is missing: {0}'.format(name)) - ret.update({'comment': comt}) - self.assertDictEqual(hipchat.send_message(name, room_id, from_name, - None), ret) - - mock = MagicMock(return_value=True) - with patch.dict(hipchat.__salt__, {'hipchat.send_message': mock}): - comt = ('Sent message: {0}'.format(name)) - ret.update({'comment': comt, 'result': True}) - self.assertDictEqual(hipchat.send_message(name, room_id, - from_name, message), - ret) From 090778c449132dd8ed34c418ddc0ff36300e6288 Mon Sep 17 00:00:00 2001 From: Alberto Planas Date: Tue, 12 Mar 2019 10:36:09 +0100 Subject: [PATCH 11/21] mdadm_raid: Optionally ignore errors on examine. During the RAID creation, the code examine each device to decide if belongs to an already present array or not. The first time that the RAID is created, is expected that examine will fail. This patch add an optional parameter to examine, to ignore the fail in the logs. Also calls examine with this parameter during the RAID creation. (cherry picked from commit bfd8b1157f2de4db3453dd7545fcb6f3332ce8e2) --- salt/modules/mdadm_raid.py | 12 ++++++++++-- salt/states/mdadm_raid.py | 2 +- tests/unit/modules/test_mdadm_raid.py | 23 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/salt/modules/mdadm_raid.py b/salt/modules/mdadm_raid.py index 829f4cdd245e..93dd0a1e3336 100644 --- a/salt/modules/mdadm_raid.py +++ b/salt/modules/mdadm_raid.py @@ -360,17 +360,25 @@ def assemble(name, return __salt__['cmd.run'](cmd, python_shell=False) -def examine(device): +def examine(device, quiet=False): ''' Show detail for a specified RAID component device + device + Device to examine, that is part of the RAID + + quiet + If the device is not part of the RAID, do not show any error + CLI Example: .. code-block:: bash salt '*' raid.examine '/dev/sda1' ''' - res = __salt__['cmd.run_stdout']('mdadm -Y -E {0}'.format(device), output_loglevel='trace', python_shell=False) + res = __salt__['cmd.run_stdout']('mdadm -Y -E {0}'.format(device), + python_shell=False, + ignore_retcode=quiet) ret = {} for line in res.splitlines(): diff --git a/salt/states/mdadm_raid.py b/salt/states/mdadm_raid.py index fd285b6acee2..d634522c334e 100644 --- a/salt/states/mdadm_raid.py +++ b/salt/states/mdadm_raid.py @@ -98,7 +98,7 @@ def present(name, if dev == 'missing' or not __salt__['file.access'](dev, 'f'): missing.append(dev) continue - superblock = __salt__['raid.examine'](dev) + superblock = __salt__['raid.examine'](dev, quiet=True) if 'MD_UUID' in superblock: uuid = superblock['MD_UUID'] diff --git a/tests/unit/modules/test_mdadm_raid.py b/tests/unit/modules/test_mdadm_raid.py index bfca3af8762e..c9b09ed94eb8 100644 --- a/tests/unit/modules/test_mdadm_raid.py +++ b/tests/unit/modules/test_mdadm_raid.py @@ -75,3 +75,26 @@ def test_create_test_mode(self): '--force -l 5 -e default -n 3 ' '/dev/sdb1 /dev/sdc1 /dev/sdd1'.split()), sorted(ret.split())) assert not mock.called, 'test mode failed, cmd.run called' + + def test_examine(self): + ''' + Test for mdadm_raid.examine + ''' + mock = MagicMock(return_value='ARRAY /dev/md/pool metadata=1.2 UUID=567da122:fb8e445e:55b853e0:81bd0a3e name=positron:pool') + with patch.dict(mdadm.__salt__, {'cmd.run_stdout': mock}): + self.assertEqual(mdadm.examine('/dev/md0'), + { + 'ARRAY /dev/md/pool metadata': '1.2 UUID=567da122:fb8e445e:55b853e0:81bd0a3e name=positron:pool' + }) + mock.assert_called_with('mdadm -Y -E /dev/md0', ignore_retcode=False, + python_shell=False) + + def test_examine_quiet(self): + ''' + Test for mdadm_raid.examine + ''' + mock = MagicMock(return_value='') + with patch.dict(mdadm.__salt__, {'cmd.run_stdout': mock}): + self.assertEqual(mdadm.examine('/dev/md0', quiet=True), {}) + mock.assert_called_with('mdadm -Y -E /dev/md0', ignore_retcode=True, + python_shell=False) From 321710e7c8a7aed9cbafc16138cfb0715cd927e9 Mon Sep 17 00:00:00 2001 From: Wayne Werner Date: Wed, 8 May 2019 09:44:02 -0500 Subject: [PATCH 12/21] Merge pull request #52773 from bloomberg/exec_2019 2019.2: bug: executors lazyloader is missing functions and proxy args --- doc/topics/development/modules/index.rst | 6 +-- salt/minion.py | 2 +- salt/modules/saltutil.py | 40 +++++++++++++++++++ salt/runners/saltutil.py | 27 +++++++++++++ tests/integration/executors/__init__.py | 1 + .../files/file/base/_executors/arg.py | 10 +++++ tests/integration/minion/test_executor.py | 24 +++++++++++ tests/integration/modules/test_saltutil.py | 4 ++ tests/unit/test_module_names.py | 1 + 9 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 tests/integration/executors/__init__.py create mode 100644 tests/integration/files/file/base/_executors/arg.py create mode 100644 tests/integration/minion/test_executor.py diff --git a/doc/topics/development/modules/index.rst b/doc/topics/development/modules/index.rst index 8c2771109f78..223e57ae484a 100644 --- a/doc/topics/development/modules/index.rst +++ b/doc/topics/development/modules/index.rst @@ -83,7 +83,7 @@ Sync Via the saltutil Module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The saltutil module has a number of functions that can be used to sync all -or specific dynamic modules. The ``saltutil.sync_*`` +or specific dynamic modules. The ``saltutil.sync_*`` :py:mod:`execution functions ` and :py:mod:`runner functions ` can be used to sync modules to minions and the master, respectively. @@ -120,7 +120,7 @@ This is done via setuptools entry points: ) Note that these are not synced from the Salt Master to the Minions. They must be -installed indepdendently on each Minion. +installed independently on each Minion. Module Types ============ @@ -139,7 +139,7 @@ Cache ``salt.cache`` (:ref:`index `) `` Cloud ``salt.cloud.clouds`` (:ref:`index `) ``clouds`` ``cloud_dirs`` Engine ``salt.engines`` (:ref:`index `) ``engines`` ``engines_dirs`` Execution ``salt.modules`` (:ref:`index `) ``modules`` ``module_dirs`` -Executor ``salt.executors`` (:ref:`index `) ``executors`` [#no-fs]_ ``executor_dirs`` +Executor ``salt.executors`` (:ref:`index `) ``executors`` ``executor_dirs`` File Server ``salt.fileserver`` (:ref:`index `) ``fileserver`` ``fileserver_dirs`` Grain ``salt.grains`` (:ref:`index `) ``grains`` ``grains_dirs`` Log Handler ``salt.log.handlers`` (:ref:`index `) ``log_handlers`` ``log_handlers_dirs`` diff --git a/salt/minion.py b/salt/minion.py index 8666c2fe4160..5f701a6d21ee 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -457,7 +457,7 @@ def gen_modules(self, initial_load=False): # self.matcher = Matcher(self.opts, self.functions) self.matchers = salt.loader.matchers(self.opts) self.functions['sys.reload_modules'] = self.gen_modules - self.executors = salt.loader.executors(self.opts, self.functions) + self.executors = salt.loader.executors(self.opts, self.functions, proxy=self.proxy) @staticmethod def process_schedule(minion, loop_interval): diff --git a/salt/modules/saltutil.py b/salt/modules/saltutil.py index b3351e70d374..f1f77cf1f399 100644 --- a/salt/modules/saltutil.py +++ b/salt/modules/saltutil.py @@ -918,6 +918,45 @@ def sync_pillar(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blackl return ret +def sync_executors(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist=None): + ''' + .. versionadded:: Neon + + Sync executors from ``salt://_executors`` to the minion + + saltenv + The fileserver environment from which to sync. To sync from more than + one environment, pass a comma-separated list. + + If not passed, then all environments configured in the :ref:`top files + ` will be checked for log handlers to sync. If no top files + are found, then the ``base`` environment will be synced. + + refresh : True + If ``True``, refresh the available execution modules on the minion. + This refresh will be performed even if no new log handlers are synced. + Set to ``False`` to prevent this refresh. + + extmod_whitelist : None + comma-seperated list of modules to sync + + extmod_blacklist : None + comma-seperated list of modules to blacklist based on type + + CLI Examples: + + .. code-block:: bash + + salt '*' saltutil.sync_executors + salt '*' saltutil.sync_executors saltenv=dev + salt '*' saltutil.sync_executors saltenv=base,dev + ''' + ret = _sync('executors', saltenv, extmod_whitelist, extmod_blacklist) + if refresh: + refresh_modules() + return ret + + def sync_all(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist=None): ''' .. versionchanged:: 2015.8.11,2016.3.2 @@ -978,6 +1017,7 @@ def sync_all(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist ret['output'] = sync_output(saltenv, False, extmod_whitelist, extmod_blacklist) ret['utils'] = sync_utils(saltenv, False, extmod_whitelist, extmod_blacklist) ret['log_handlers'] = sync_log_handlers(saltenv, False, extmod_whitelist, extmod_blacklist) + ret['executors'] = sync_executors(saltenv, False, extmod_whitelist, extmod_blacklist) ret['proxymodules'] = sync_proxymodules(saltenv, False, extmod_whitelist, extmod_blacklist) ret['engines'] = sync_engines(saltenv, False, extmod_whitelist, extmod_blacklist) ret['thorium'] = sync_thorium(saltenv, False, extmod_whitelist, extmod_blacklist) diff --git a/salt/runners/saltutil.py b/salt/runners/saltutil.py index 6f6af53dea38..f80e865f29cc 100644 --- a/salt/runners/saltutil.py +++ b/salt/runners/saltutil.py @@ -64,6 +64,7 @@ def sync_all(saltenv='base', extmod_whitelist=None, extmod_blacklist=None): ret['tops'] = sync_tops(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist) ret['tokens'] = sync_eauth_tokens(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist) ret['serializers'] = sync_serializers(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist) + ret['executors'] = sync_executors(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist) return ret @@ -607,3 +608,29 @@ def sync_serializers(saltenv='base', extmod_whitelist=None, extmod_blacklist=Non ''' return salt.utils.extmods.sync(__opts__, 'serializers', saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)[0] + + +def sync_executors(saltenv='base', extmod_whitelist=None, extmod_blacklist=None): + ''' + .. versionadded:: Neon + + Sync executor modules from ``salt://_executors`` to the master + + saltenv : base + The fileserver environment from which to sync. To sync from more than + one environment, pass a comma-separated list. + + extmod_whitelist : None + comma-seperated list of modules to sync + + extmod_blacklist : None + comma-seperated list of modules to blacklist based on type + + CLI Example: + + .. code-block:: bash + + salt-run saltutil.sync_executors + ''' + return salt.utils.extmods.sync(__opts__, 'executors', saltenv=saltenv, extmod_whitelist=extmod_whitelist, + extmod_blacklist=extmod_blacklist)[0] diff --git a/tests/integration/executors/__init__.py b/tests/integration/executors/__init__.py new file mode 100644 index 000000000000..40a96afc6ff0 --- /dev/null +++ b/tests/integration/executors/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/integration/files/file/base/_executors/arg.py b/tests/integration/files/file/base/_executors/arg.py new file mode 100644 index 000000000000..dfe09cb7b834 --- /dev/null +++ b/tests/integration/files/file/base/_executors/arg.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + + +def __virtual__(): + return True + + +def execute(*args, **kwargs): + # we use the dunder to assert the loader is provided minionmods + return __salt__['test.arg']('test.arg fired') diff --git a/tests/integration/minion/test_executor.py b/tests/integration/minion/test_executor.py new file mode 100644 index 000000000000..93e9c57ffdb3 --- /dev/null +++ b/tests/integration/minion/test_executor.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Import python libs +from __future__ import absolute_import, print_function, unicode_literals + +import logging + +# Import Salt Testing libs +from tests.support.case import ModuleCase, ShellCase + +log = logging.getLogger(__name__) + + +class ExecutorTest(ModuleCase, ShellCase): + + def setup(self): + self.run_function('saltutil.sync_all') + + def test_executor(self): + ''' + test that dunders are set + ''' + data = self.run_call('test.arg --module-executors=arg') + self.assertIn('test.arg fired', "".join(data)) diff --git a/tests/integration/modules/test_saltutil.py b/tests/integration/modules/test_saltutil.py index e3c218509809..4a4515c7f2df 100644 --- a/tests/integration/modules/test_saltutil.py +++ b/tests/integration/modules/test_saltutil.py @@ -98,6 +98,7 @@ def test_sync_all(self): 'states': [], 'sdb': [], 'proxymodules': [], + 'executors': [], 'output': [], 'thorium': [], 'serializers': []} @@ -121,6 +122,7 @@ def test_sync_all_whitelist(self): 'states': [], 'sdb': [], 'proxymodules': [], + 'executors': [], 'output': [], 'thorium': [], 'serializers': []} @@ -147,6 +149,7 @@ def test_sync_all_blacklist(self): 'states': [], 'sdb': [], 'proxymodules': [], + 'executors': [], 'output': [], 'thorium': [], 'serializers': []} @@ -163,6 +166,7 @@ def test_sync_all_blacklist_and_whitelist(self): 'beacons': [], 'utils': [], 'returners': [], + 'executors': [], 'modules': [], 'renderers': [], 'log_handlers': [], diff --git a/tests/unit/test_module_names.py b/tests/unit/test_module_names.py index 0e3863eda18a..b09be3cf262a 100644 --- a/tests/unit/test_module_names.py +++ b/tests/unit/test_module_names.py @@ -135,6 +135,7 @@ def test_module_name_source_match(self): 'integration.master.test_event_return', 'integration.minion.test_blackout', 'integration.minion.test_pillar', + 'integration.minion.test_executor', 'integration.minion.test_timeout', 'integration.modules.test_decorators', 'integration.modules.test_pkg', From 2b99d3437be1d7891f02471b60d105864999a9a0 Mon Sep 17 00:00:00 2001 From: Max Arnold Date: Wed, 13 Nov 2019 16:29:15 +0700 Subject: [PATCH 13/21] Add saltutil.sync_executors state --- salt/states/saltutil.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/salt/states/saltutil.py b/salt/states/saltutil.py index bb6f8cd493d6..3803abf18de0 100644 --- a/salt/states/saltutil.py +++ b/salt/states/saltutil.py @@ -143,6 +143,20 @@ def sync_grains(name, **kwargs): return _sync_single(name, "grains", **kwargs) +def sync_executors(name, **kwargs): + ''' + Performs the same task as saltutil.sync_executors module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_executors: + - refresh: True + ''' + return _sync_single(name, "executors", **kwargs) + + def sync_log_handlers(name, **kwargs): ''' Performs the same task as saltutil.sync_log_handlers module From a5ad351ffb1f83aa0932e48f22db6d10abede79a Mon Sep 17 00:00:00 2001 From: Alyssa Rock Date: Wed, 13 Nov 2019 14:23:23 -0700 Subject: [PATCH 14/21] Minor change to bullet points on index topic --- doc/topics/index.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/topics/index.rst b/doc/topics/index.rst index 47656ebb4f87..4b9b80fc77aa 100644 --- a/doc/topics/index.rst +++ b/doc/topics/index.rst @@ -9,13 +9,13 @@ The 30 second summary Salt is: -* a configuration management system, capable of maintaining remote nodes - in defined states (for example, ensuring that specific packages are installed and - specific services are running) +* **A configuration management system.** Salt is capable of maintaining remote + nodes in defined states. For example, it can ensure that specific packages are + installed and that specific services are running. -* a distributed remote execution system used to execute commands and - query data on remote nodes, either individually or by arbitrary - selection criteria +* **A distributed remote execution system used to execute commands and + query data on remote nodes.** Salt can query and execute commands either on + individual nodes or by using an arbitrary selection criteria. It was developed in order to bring the best solutions found in the world of remote execution together and make them better, faster, and more @@ -188,4 +188,3 @@ documentation efforts, please review the :ref:`contributing documentation `! .. _`Apache 2.0 license`: http://www.apache.org/licenses/LICENSE-2.0.html - From a99c6cf049bfac6450386e1bf23b69f2d77ae1f1 Mon Sep 17 00:00:00 2001 From: Bryce Larson Date: Tue, 12 Nov 2019 14:13:22 -0700 Subject: [PATCH 15/21] add fedora31 --- .ci/kitchen-fedora31-py3 | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .ci/kitchen-fedora31-py3 diff --git a/.ci/kitchen-fedora31-py3 b/.ci/kitchen-fedora31-py3 new file mode 100644 index 000000000000..f123b4ca6014 --- /dev/null +++ b/.ci/kitchen-fedora31-py3 @@ -0,0 +1,48 @@ +@Library('salt@master-1.3') _ + +// Define the maximum time, in hours, that a test run should run for +def testrun_timeout = 6 + +def distro_name = 'fedora' +def distro_version = '31' +def python_version = 'py3' +def nox_env_name = 'runtests-zeromq' +def golden_images_branch = 'master' +def nox_passthrough_opts = '--ssh-tests' +def concurrent_builds = 1 +def use_spot_instances = true +def jenkins_slave_label = 'kitchen-slave' + +properties([ + buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '30')), + parameters([ + booleanParam(defaultValue: true, description: 'Run full test suite', name: 'runFull') + ]) +]) + +// Only set milestones on PR builds +if (env.CHANGE_ID) { + // Be sure to cancel any previously running builds + def buildNumber = env.BUILD_NUMBER as int + if (buildNumber > concurrent_builds) { + // This will cancel the previous build which also defined a matching milestone + milestone(buildNumber - concurrent_builds) + } + // Define a milestone for this build so that, if another build starts, this one will be aborted + milestone(buildNumber) +} + +runTests( + env: env, + distro_name: distro_name, + distro_version: distro_version, + python_version: python_version, + golden_images_branch: golden_images_branch, + nox_env_name: nox_env_name, + nox_passthrough_opts: nox_passthrough_opts, + testrun_timeout: testrun_timeout, + run_full: params.runFull, + use_spot_instances: use_spot_instances, + jenkins_slave_label: jenkins_slave_label) + +// vim: ft=groovy From 6467bef52c0ca0618476c0e31a6497f8d376611a Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 14 Nov 2019 19:44:19 +0000 Subject: [PATCH 16/21] Skip `test_pkg_upgrade_has_pending_upgrades` on Arch Linux Because Arch has moved to Python 3.8 and we're not there yet. --- tests/integration/modules/test_pkg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/modules/test_pkg.py b/tests/integration/modules/test_pkg.py index bb011debfa75..dc3cb6eee897 100644 --- a/tests/integration/modules/test_pkg.py +++ b/tests/integration/modules/test_pkg.py @@ -267,6 +267,9 @@ def test_pkg_upgrade_has_pending_upgrades(self, grains): ''' Test running a system upgrade when there are packages that need upgrading ''' + if grains['os'] == 'Arch': + self.skipTest('Arch moved to Python 3.8 and we\'re not ready for it yet') + func = 'pkg.upgrade' # First make sure that an up-to-date copy of the package db is available From a7acd78f4bb53f1f74b0900610368938f5678464 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Thu, 14 Nov 2019 22:16:40 +0000 Subject: [PATCH 17/21] Add a timeout for netapi NetapiClientTest.test_local --- tests/integration/netapi/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/netapi/test_client.py b/tests/integration/netapi/test_client.py index 503bbaf335f5..5e538a3af4f3 100644 --- a/tests/integration/netapi/test_client.py +++ b/tests/integration/netapi/test_client.py @@ -32,7 +32,7 @@ def tearDown(self): del self.netapi def test_local(self): - low = {'client': 'local', 'tgt': '*', 'fun': 'test.ping'} + low = {'client': 'local', 'tgt': '*', 'fun': 'test.ping', 'timeout': 300} low.update(self.eauth_creds) ret = self.netapi.run(low) From ba12fbdbd5a48cc0286becfb08ed2d0f41c56fda Mon Sep 17 00:00:00 2001 From: Deepak Goel Date: Fri, 15 Nov 2019 12:19:16 +0530 Subject: [PATCH 18/21] [FIX] minor fix in git_pillar README --- salt/pillar/git_pillar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/pillar/git_pillar.py b/salt/pillar/git_pillar.py index 07f13b4ee98f..e36817f81097 100644 --- a/salt/pillar/git_pillar.py +++ b/salt/pillar/git_pillar.py @@ -226,7 +226,7 @@ Setting a :conf_minion:`pillarenv` in the minion config file will make that minion tell the master to ignore any pillar data from environments which don't match that pillarenv. A pillarenv can also be specified for a given minion or -set of minions when :mod:`running states `, by using he +set of minions when :mod:`running states `, by using the ``pillarenv`` argument. The CLI pillarenv will override one set in the minion config file. So, assuming that a pillarenv of ``base`` was set for a minion, it would not get any of the pillar variables configured in the ``qux`` remote, From 11d10ab09fb6d1266b91b99c5fc3e0047f27b059 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Mon, 18 Nov 2019 17:05:19 +0000 Subject: [PATCH 19/21] Fix failing pip tests integration.states.test_pip_state.PipStateTest.test_issue_6912_wrong_owner integration.states.test_pip_state.PipStateTest.test_pip_installed_errors --- salt/modules/pip.py | 9 +++++++++ salt/states/pip_state.py | 1 + 2 files changed, 10 insertions(+) diff --git a/salt/modules/pip.py b/salt/modules/pip.py index eac40c719cfa..a456b3b9c612 100644 --- a/salt/modules/pip.py +++ b/salt/modules/pip.py @@ -433,6 +433,7 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914 no_cache_dir=False, cache_dir=None, no_binary=None, + disable_version_check=False, **kwargs): ''' Install packages with pip @@ -604,6 +605,11 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914 no_cache_dir Disable the cache. + disable_version_check + Pip may periodically check PyPI to determine whether a new version of + pip is available to download. Passing True for this option disables + that check. + CLI Example: .. code-block:: bash @@ -756,6 +762,9 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914 ) cmd.extend(['--mirrors', mirror]) + if disable_version_check: + cmd.extend(['--disable-pip-version-check']) + if build: cmd.extend(['--build', build]) diff --git a/salt/states/pip_state.py b/salt/states/pip_state.py index a9b2995e09e1..1134c059786b 100644 --- a/salt/states/pip_state.py +++ b/salt/states/pip_state.py @@ -901,6 +901,7 @@ def installed(name, use_vt=use_vt, trusted_host=trusted_host, no_cache_dir=no_cache_dir, + disable_version_check=True, **kwargs ) From 014d9d10b4e7106eb4c306791459792bc8ea6829 Mon Sep 17 00:00:00 2001 From: Tyler Johnson Date: Tue, 19 Nov 2019 11:10:42 -0700 Subject: [PATCH 20/21] Fixed vultr list_sizes test --- .../cloud/clouds/{test_vultrpy.py => test_vultr.py} | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) rename tests/integration/cloud/clouds/{test_vultrpy.py => test_vultr.py} (96%) diff --git a/tests/integration/cloud/clouds/test_vultrpy.py b/tests/integration/cloud/clouds/test_vultr.py similarity index 96% rename from tests/integration/cloud/clouds/test_vultrpy.py rename to tests/integration/cloud/clouds/test_vultr.py index f0dc58e309e7..c91281440388 100644 --- a/tests/integration/cloud/clouds/test_vultrpy.py +++ b/tests/integration/cloud/clouds/test_vultr.py @@ -45,10 +45,7 @@ def test_list_sizes(self): Tests the return of running the --list-sizes command for Vultr ''' size_list = self.run_cloud('--list-sizes {0}'.format(self.PROVIDER)) - self.assertIn( - '32768 MB RAM,4x110 GB SSD,40.00 TB BW', - [i.strip() for i in size_list] - ) + self.assertIn('2048 MB RAM,64 GB SSD,2.00 TB BW', [i.strip() for i in size_list]) # Commented for now, Vultr driver does not yet support key management # def test_key_management(self): From 7a39affa08726e9a3c057db6ffc1e8a4e88a413b Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Tue, 19 Nov 2019 18:34:24 +0000 Subject: [PATCH 21/21] Fix cloud builds missing config --- .ci/kitchen-centos7-py2-cloud | 4 +++- .ci/kitchen-centos7-py3-cloud | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.ci/kitchen-centos7-py2-cloud b/.ci/kitchen-centos7-py2-cloud index 3c1fa5d60d31..cfe1451e565f 100644 --- a/.ci/kitchen-centos7-py2-cloud +++ b/.ci/kitchen-centos7-py2-cloud @@ -11,6 +11,7 @@ def golden_images_branch = 'master' def nox_passthrough_opts = '' def use_spot_instances = true def jenkins_slave_label = 'kitchen-slave' +def kitchen_platforms_file = '/var/jenkins/workspace/nox-cloud-platforms.yml' properties([ buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '30')), @@ -30,6 +31,7 @@ runTests( testrun_timeout: testrun_timeout, run_full: params.runFull, use_spot_instances: use_spot_instances, - jenkins_slave_label: jenkins_slave_label) + jenkins_slave_label: jenkins_slave_label, + kitchen_platforms_file: kitchen_platforms_file) // vim: ft=groovy diff --git a/.ci/kitchen-centos7-py3-cloud b/.ci/kitchen-centos7-py3-cloud index 359901508883..8af3ef6f5090 100644 --- a/.ci/kitchen-centos7-py3-cloud +++ b/.ci/kitchen-centos7-py3-cloud @@ -11,6 +11,7 @@ def golden_images_branch = 'master' def nox_passthrough_opts = '' def use_spot_instances = true def jenkins_slave_label = 'kitchen-slave' +def kitchen_platforms_file = '/var/jenkins/workspace/nox-cloud-platforms.yml' properties([ buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '30')), @@ -30,6 +31,7 @@ runTests( testrun_timeout: testrun_timeout, run_full: params.runFull, use_spot_instances: use_spot_instances, - jenkins_slave_label: jenkins_slave_label) + jenkins_slave_label: jenkins_slave_label, + kitchen_platforms_file: kitchen_platforms_file) // vim: ft=groovy