Skip to content

Commit

Permalink
#6: Implement "docker-container-log" check
Browse files Browse the repository at this point in the history
  • Loading branch information
blackandred committed Nov 14, 2020
1 parent ad3e821 commit e266dc9
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/source/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ Parameters:
.. include:: ../../infracheck/checks/postgres-replica-status
:start-after: <sphinx>
:end-before: </sphinx>

.. include:: ../../infracheck/checks/docker-container-log
:start-after: <sphinx>
:end-before: </sphinx>
85 changes: 85 additions & 0 deletions infracheck/checks/docker-container-log
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3

"""
<sphinx>
docker-container-log
--------------------
Searches docker container logs for matching given regular expression.
Parameters:
- container: Docker container name
- regexp: Regular expression
- max_lines: Number of last lines to check (defaults to 5)
- since_seconds: Get only logs since this time (eg. last 5 minutes = 5 * 60 = 300) (defaults to 300)
- present: Boolean, if the string should be present in the output or not
</sphinx>
"""

import os
import sys
import re
from subprocess import check_output
from subprocess import CalledProcessError
from subprocess import STDOUT


def parse_bool(boolean: str) -> bool:
return bool(int(boolean.lower().replace('true', "1").replace('false', "0")))


class DockerContainerLogCheck(object):
def _check_output(self, *args, **kwargs):
return check_output(*args, **kwargs)

def main(self, container: str, regexp: str, max_lines: int, since: int, should_be_present: bool):
opts = []

if since > 0:
opts.append('--since={}s'.format(since))

try:
out = self._check_output(
['docker', 'logs', container, '--tail="{}"'.format(max_lines)] + opts, stderr=STDOUT).decode('utf-8')

except CalledProcessError as e:
out = e.output.decode('utf-8')

is_present_in_output = re.findall(regexp, out)

if should_be_present and is_present_in_output:
return True, "The container last {} lines of output has a match, as expected".format(max_lines)

if not should_be_present and is_present_in_output:
return False, "The container output is matching the pattern but should not, " \
"looked at {} lines since {} in {}"\
.format(max_lines, str(since) + 's', container)

if should_be_present and not is_present_in_output:
return False, "The container last {} lines of output are not matching, expecting they were"\
.format(max_lines)

if not should_be_present and not is_present_in_output:
return True, "The container last {} lines of output are not matching, as expected".format(max_lines)

return False, "Unknown error"


if __name__ == '__main__':
app = DockerContainerLogCheck()

try:
status, message = app.main(
container=os.environ['CONTAINER'],
regexp=os.environ['REGEXP'],
max_lines=int(os.getenv('MAX_LINES', '5')),
since=int(os.getenv('SINCE_SECONDS', '300')),
should_be_present=parse_bool(os.environ['PRESENT'])
)
except KeyError as attribute:
print('Missing environment variable: {}'.format(attribute))
sys.exit(1)

print(message)
sys.exit(0 if status else 1)
91 changes: 91 additions & 0 deletions tests/functional_test_docker_container_log_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import unittest
import os
import inspect
from typing import Tuple

# import a script with "-" in filename
path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + '/../'
with open(path + '/infracheck/checks/docker-container-log', 'r') as script:
exec(script.read())


class DockerContainerLogCheckTest(unittest.TestCase):
@staticmethod
def _run_mocked_check(container: str, regexp: str, max_lines: int, since: int, should_be_present: bool) \
-> Tuple[bool, str, list]:

check = DockerContainerLogCheck()
called = []

def check_output(*args, **kwargs):
called.append(args)

return b"""
Hello
World
This
Is
A
Test
"""

check._check_output = check_output
result, message = check.main(
container,
regexp,
max_lines,
since,
should_be_present
)

return result, message, called

def test_expecting_exact_match_and_have_match(self) -> None:
result, message, called = self._run_mocked_check(
container='container_1',
regexp='Hello',
max_lines=15,
since=5,
should_be_present=True
)

self.assertTrue(result)
self.assertEqual('The container last 15 lines of output has a match, as expected', message)

def test_expecting_that_will_not_match(self) -> None:
result, message, called = self._run_mocked_check(
container='container_1',
regexp='This text will not be found',
max_lines=15,
since=5,
should_be_present=True
)

self.assertFalse(result)
self.assertEqual('The container last 15 lines of output are not matching, expecting they were', message)

def test_expecting_to_not_find_text_and_will_not_find(self) -> None:
result, message, called = self._run_mocked_check(
container='container_1',
regexp='This text will not be found',
max_lines=15,
since=5,
should_be_present=False
)

self.assertTrue(result)
self.assertEqual('The container last 15 lines of output are not matching, as expected', message)

def test_expecting_to_not_find_but_will_find(self) -> None:
result, message, called = self._run_mocked_check(
container='container_1',
regexp='Hello',
max_lines=15,
since=5,
should_be_present=False
)

self.assertFalse(result)
self.assertEqual('The container output is matching the pattern but should not, '
'looked at 15 lines since 5s in container_1', message)

0 comments on commit e266dc9

Please sign in to comment.