From d74bf944f12f052447f3a8c36f2e0ba5fdce2189 Mon Sep 17 00:00:00 2001 From: Ajnbro <50883410+Ajnbro@users.noreply.github.com> Date: Mon, 28 Oct 2019 20:19:42 +0000 Subject: [PATCH 1/3] Updated HTTP Query functionality and tests --- salt/states/http.py | 73 ++++++++++++++++++++++++++++------ tests/unit/states/test_http.py | 72 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 12 deletions(-) diff --git a/salt/states/http.py b/salt/states/http.py index 2bd124478f2d..9e4fae8f271d 100644 --- a/salt/states/http.py +++ b/salt/states/http.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) -def query(name, match=None, match_type='string', status=None, wait_for=None, **kwargs): +def query(name, match=None, match_type='string', status=None, status_type='string', wait_for=None, **kwargs): ''' Perform an HTTP query and statefully return the result @@ -36,7 +36,7 @@ def query(name, match=None, match_type='string', status=None, wait_for=None, **k text. match_type - Specifies the type of pattern matching to use. Default is ``string``, but + Specifies the type of pattern matching to use on match. Default is ``string``, but can also be set to ``pcre`` to use regular expression matching if a more complex pattern matching is required. @@ -48,7 +48,22 @@ def query(name, match=None, match_type='string', status=None, wait_for=None, **k status The status code for a URL for which to be checked. Can be used instead of - or in addition to the ``match`` setting. + or in addition to the ``match`` setting. This can be passed as an individual status code + or a list of status codes. + + status_type + Specifies the type of pattern matching to use for status. Default is ``string``, but + can also be set to ``pcre`` to use regular expression matching if a more + complex pattern matching is required. Additionally, if a list of strings representing + statuses is given, the type ``list`` can be used. + + .. versionadded:: Neon + + .. note:: + + Despite the name of ``match_type`` for this argument, this setting + actually uses Python's ``re.search()`` function rather than Python's + ``re.match()`` function. If both ``match`` and ``status`` options are set, both settings will be checked. However, note that if only one option is ``True`` and the other is ``False``, @@ -65,6 +80,14 @@ def query(name, match=None, match_type='string', status=None, wait_for=None, **k - name: 'http://example.com/' - status: 200 + query_example2: + http.query: + - name: 'http://example.com/' + - status: + - 200 + - 201 + - status_type: list + ''' # Monitoring state, but changes may be made over HTTP ret = {'name': name, @@ -94,14 +117,14 @@ def query(name, match=None, match_type='string', status=None, wait_for=None, **k if match is not None: if match_type == 'string': - if match in data.get('text', ''): + if str(match) in data.get('text', ''): ret['result'] = True ret['comment'] += ' Match text "{0}" was found.'.format(match) else: ret['result'] = False ret['comment'] += ' Match text "{0}" was not found.'.format(match) elif match_type == 'pcre': - if re.search(match, data.get('text', '')): + if re.search(str(match), str(data.get('text', ''))): ret['result'] = True ret['comment'] += ' Match pattern "{0}" was found.'.format(match) else: @@ -109,13 +132,39 @@ def query(name, match=None, match_type='string', status=None, wait_for=None, **k ret['comment'] += ' Match pattern "{0}" was not found.'.format(match) if status is not None: - if data.get('status', '') == status: - ret['comment'] += 'Status {0} was found, as specified.'.format(status) - if ret['result'] is None: - ret['result'] = True - else: - ret['comment'] += 'Status {0} was not found, as specified.'.format(status) - ret['result'] = False + # Deals with case of status_type as a list of strings representing statuses + if status_type == 'list': + for stat in status: + if str(data.get('status', '')) == str(stat): + ret['comment'] += ' Status {0} was found.'.format(stat) + if ret['result'] is None: + ret['result'] = True + if ret['result'] is not True: + ret['comment'] += ' Statuses {0} were not found.'.format(status) + ret['result'] = False + + # Deals with the case of status_type representing a regex + elif status_type == 'pcre': + if re.search(str(status), str(data.get('status', ''))): + ret['comment'] += ' Status pattern "{0}" was found.'.format(status) + if ret['result'] is None: + ret['result'] = True + else: + ret['comment'] += ' Status pattern "{0}" was not found.'.format(status) + ret['result'] = False + + # Deals with the case of status_type as a single string representing a status + elif status_type == 'string': + if str(data.get('status', '')) == str(status): + ret['comment'] += ' Status {0} was found.'.format(status) + if ret['result'] is None: + ret['result'] = True + else: + ret['comment'] += ' Status {0} was not found.'.format(status) + ret['result'] = False + + # cleanup spaces in comment + ret['comment'] = ret['comment'].strip() if __opts__['test'] is True: ret['result'] = None diff --git a/tests/unit/states/test_http.py b/tests/unit/states/test_http.py index 68dcc971744c..daf088ec368e 100644 --- a/tests/unit/states/test_http.py +++ b/tests/unit/states/test_http.py @@ -44,3 +44,75 @@ def test_query(self): with patch.dict(http.__salt__, {'http.query': mock}): self.assertDictEqual(http.query("salt", "Dude", "stack"), ret[1]) + + def test_query_pcre_statustype(self): + ''' + Test to perform an HTTP query with a regex used to match the status code and statefully return the result + ''' + testurl = "salturl" + http_result = { + "text": "This page returned a 201 status code", + "status": "201" + } + state_return = {'changes': {}, + 'comment': 'Match text "This page returned" was found. Status pattern "200|201" was found.', + 'data': {'status': '201', 'text': 'This page returned a 201 status code'}, + 'name': testurl, + 'result': True} + + with patch.dict(http.__opts__, {'test': False}): + mock = MagicMock(return_value=http_result) + with patch.dict(http.__salt__, {'http.query': mock}): + self.assertDictEqual(http.query(testurl, + match="This page returned", + status="200|201", + status_type='pcre' + ), state_return) + + def test_query_stringstatustype(self): + ''' + Test to perform an HTTP query with a string status code and statefully return the result + ''' + testurl = "salturl" + http_result = { + "text": "This page returned a 201 status code", + "status": "201" + } + state_return = {'changes': {}, + 'comment': 'Match text "This page returned" was found. Status 201 was found.', + 'data': {'status': '201', 'text': 'This page returned a 201 status code'}, + 'name': testurl, + 'result': True} + + with patch.dict(http.__opts__, {'test': False}): + mock = MagicMock(return_value=http_result) + with patch.dict(http.__salt__, {'http.query': mock}): + self.assertDictEqual(http.query(testurl, + match="This page returned", + status="201", + status_type='string' + ), state_return) + + def test_query_liststatustype(self): + ''' + Test to perform an HTTP query with a list of status codes and statefully return the result + ''' + testurl = "salturl" + http_result = { + "text": "This page returned a 201 status code", + "status": "201" + } + state_return = {'changes': {}, + 'comment': 'Match text "This page returned" was found. Status 201 was found.', + 'data': {'status': '201', 'text': 'This page returned a 201 status code'}, + 'name': testurl, + 'result': True} + + with patch.dict(http.__opts__, {'test': False}): + mock = MagicMock(return_value=http_result) + with patch.dict(http.__salt__, {'http.query': mock}): + self.assertDictEqual(http.query(testurl, + match="This page returned", + status=["200", "201"], + status_type='list' + ), state_return) From 15947fcea5959aef59ac7567153199ed89587013 Mon Sep 17 00:00:00 2001 From: Daniel Wozniak Date: Mon, 23 Dec 2019 16:57:06 -0700 Subject: [PATCH 2/3] Fix linter --- tests/unit/states/test_http.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/states/test_http.py b/tests/unit/states/test_http.py index 0f7eac143c29..1f3e9fecb185 100644 --- a/tests/unit/states/test_http.py +++ b/tests/unit/states/test_http.py @@ -137,4 +137,5 @@ def test_wait_for_without_interval(self): with patch.object(http, 'query', query_mock): with patch('time.sleep', MagicMock()) as sleep_mock: self.assertEqual(http.wait_for_successful_query('url', status=200), {'result': True}) - sleep_mock.assert_not_called() \ No newline at end of file + sleep_mock.assert_not_called() + From ee13b154bb90f0a0ab271342a9e156ea4afcfdc2 Mon Sep 17 00:00:00 2001 From: Daniel Wozniak Date: Tue, 24 Dec 2019 09:18:26 -0700 Subject: [PATCH 3/3] linter again --- tests/unit/states/test_http.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/states/test_http.py b/tests/unit/states/test_http.py index 1f3e9fecb185..6ee22300886e 100644 --- a/tests/unit/states/test_http.py +++ b/tests/unit/states/test_http.py @@ -138,4 +138,3 @@ def test_wait_for_without_interval(self): with patch('time.sleep', MagicMock()) as sleep_mock: self.assertEqual(http.wait_for_successful_query('url', status=200), {'result': True}) sleep_mock.assert_not_called() -