Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Status Code Lists for HTTP Query State #55145

Merged
merged 6 commits into from
Dec 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 61 additions & 12 deletions salt/states/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.

Expand All @@ -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``,
Expand All @@ -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,
Expand Down Expand Up @@ -94,28 +117,54 @@ 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:
ret['result'] = False
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
Expand Down
72 changes: 72 additions & 0 deletions tests/unit/states/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,78 @@ def test_query(self):
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)

def test_wait_for_with_interval(self):
'''
Test for wait_for_successful_query waits for request_interval
Expand Down