From cd6ad8485f4a29d88d7c8cd58f5c95d5d11034ed Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 29 Jan 2019 14:03:47 +0100 Subject: [PATCH 1/4] Allow file assertions to include globs --- .../test_zaza_utilities_file_assertions.py | 63 +++++++++++++++++ zaza/charm_tests/security/tests.py | 33 ++++----- zaza/utilities/file_assertions.py | 68 +++++++++++++++++++ 3 files changed, 143 insertions(+), 21 deletions(-) create mode 100644 unit_tests/utilities/test_zaza_utilities_file_assertions.py create mode 100644 zaza/utilities/file_assertions.py diff --git a/unit_tests/utilities/test_zaza_utilities_file_assertions.py b/unit_tests/utilities/test_zaza_utilities_file_assertions.py new file mode 100644 index 0000000..f0eba1c --- /dev/null +++ b/unit_tests/utilities/test_zaza_utilities_file_assertions.py @@ -0,0 +1,63 @@ +# Copyright 2018 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import unit_tests.utils as ut_utils +import zaza.utilities.file_assertions as file_assertions + + +class TestFileAssertionUtils(ut_utils.BaseTestCase): + def setUp(self): + super(TestFileAssertionUtils, self).setUp() + # Patch all run_on_unit calls + self.patch( + 'zaza.utilities.file_assertions.model.run_on_unit', + new_callable=mock.MagicMock(), + name='run_on_unit' + ) + self._assert = mock.MagicMock() + self._assert.assertEqual = mock.MagicMock() + + def test_path_glob(self): + self.run_on_unit.return_value = { + 'Stdout': 'file-name root root 600' + } + file_details = {'path': '*'} + file_assertions.assert_path_glob( + self._assert, 'test/0', file_details) + self.run_on_unit.assert_called_once_with( + 'test/0', 'stat -c "%n %U %G %a" *') + + def test_single_path(self): + self.run_on_unit.return_value = { + 'Stdout': 'root root 600' + } + file_details = {'path': 'test'} + file_assertions.assert_single_file( + self._assert, 'test/0', file_details) + self.run_on_unit.assert_called_once_with( + 'test/0', 'stat -c "%U %G %a" test') + + def test_error_message_glob(self): + message = file_assertions._error_message( + "Owner", "test/0", "root", "/path/to/something") + self.assertEqual( + message, + "Owner is incorrect for /path/to/something on test/0: root") + + def test_error_message_single(self): + + message = file_assertions._error_message( + "Owner", "test/0", "root") + self.assertEqual(message, "Owner is incorrect on test/0: root") diff --git a/zaza/charm_tests/security/tests.py b/zaza/charm_tests/security/tests.py index 51c685e..2a414a2 100644 --- a/zaza/charm_tests/security/tests.py +++ b/zaza/charm_tests/security/tests.py @@ -20,31 +20,20 @@ import unittest import zaza.model as model import zaza.charm_lifecycle.utils as utils +from zaza.utilities.file_assertions import ( + assert_path_glob, + assert_single_file, +) -def _make_test_function(application, file_details): +def _make_test_function(application, file_details, paths=[]): def test(self): - expected_owner = file_details.get("owner", "root") - expected_group = file_details.get("group", "root") - expected_mode = file_details.get("mode", "600") for unit in model.get_units(application): unit = unit.entity_id - result = model.run_on_unit( - unit, 'stat -c "%U %G %a" {}'.format(file_details['path'])) - ownership = result['Stdout'] - owner, group, mode = ownership.split() - self.assertEqual(expected_owner, - owner, - "Owner is incorrect for {}: {}" - .format(unit, owner)) - self.assertEqual(expected_group, - group, - "Group is incorrect for {}: {}" - .format(unit, group)) - self.assertEqual(expected_mode, - mode, - "Mode is incorrect for {}: {}" - .format(unit, mode)) + if '*' in file_details['path']: + assert_path_glob(self, unit, file_details, paths) + else: + assert_single_file(self, unit, file_details) return test @@ -57,7 +46,9 @@ def _add_tests(): # Lets make sure to only add tests for deployed applications if name in deployed_applications: for file in attributes['files']: - test_func = _make_test_function(name, file) + paths = [file['path'] for file in attributes['files']] + paths = [path for path in paths if path[-1] is not "*"] + test_func = _make_test_function(name, file, paths=paths) setattr( cls, 'test_{}_{}'.format(name, file['path']), diff --git a/zaza/utilities/file_assertions.py b/zaza/utilities/file_assertions.py new file mode 100644 index 0000000..08375c1 --- /dev/null +++ b/zaza/utilities/file_assertions.py @@ -0,0 +1,68 @@ +# Copyright 2018 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module of helpers for Zaza file assertions.""" + +import zaza.model as model + + +def assert_path_glob(test_case, unit, file_details, paths=[]): + """Verify all files in a given directory.""" + result = model.run_on_unit( + unit, 'stat -c "%n %U %G %a" {}'.format(file_details['path'])) + files = result['Stdout'] + for file in files.splitlines(): + file, owner, group, mode = file.split() + if file not in paths and file not in ['.', '..']: + verify_file(test_case, + unit, + file_details, + owner, + group, + mode, + path=file) + + +def assert_single_file(test_case, unit, file_details): + """Verify ownership of a single file.""" + result = model.run_on_unit( + unit, 'stat -c "%U %G %a" {}'.format(file_details['path'])) + ownership = result['Stdout'] + owner, group, mode = ownership.split() + verify_file(test_case, unit, file_details, owner, group, mode) + + +def verify_file(test_case, unit, file_details, + actual_owner, actual_group, actual_mode, path=None): + """Assert file has correct permissions.""" + expected_owner = file_details.get("owner", "root") + expected_group = file_details.get("group", "root") + expected_mode = file_details.get("mode", "600") + test_case.assertEqual(expected_owner, + actual_owner, + _error_message("Owner", unit, actual_owner, path)) + test_case.assertEqual(expected_group, + actual_group, + _error_message("Group", unit, actual_group, path)) + test_case.assertEqual(expected_mode, + actual_mode, + _error_message("Mode", unit, actual_mode, path)) + + +def _error_message(thing, unit, value, path=None): + if path: + return "{} is incorrect for {} on {}: {}".format( + thing, path, unit, value) + else: + return "{} is incorrect on {}: {}".format(thing, unit, value) From 774a49faef092f88747bdc9f7bb2ea6e4d078a00 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 29 Jan 2019 14:49:03 +0100 Subject: [PATCH 2/4] Support globstar for recursion --- unit_tests/utilities/test_zaza_utilities_file_assertions.py | 3 ++- zaza/utilities/file_assertions.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_file_assertions.py b/unit_tests/utilities/test_zaza_utilities_file_assertions.py index f0eba1c..b40276c 100644 --- a/unit_tests/utilities/test_zaza_utilities_file_assertions.py +++ b/unit_tests/utilities/test_zaza_utilities_file_assertions.py @@ -37,7 +37,8 @@ class TestFileAssertionUtils(ut_utils.BaseTestCase): file_assertions.assert_path_glob( self._assert, 'test/0', file_details) self.run_on_unit.assert_called_once_with( - 'test/0', 'stat -c "%n %U %G %a" *') + 'test/0', 'bash -c "shopt -s -q globstar;' + ' stat -c "%n %U %G %a" *"') def test_single_path(self): self.run_on_unit.return_value = { diff --git a/zaza/utilities/file_assertions.py b/zaza/utilities/file_assertions.py index 08375c1..470fcd2 100644 --- a/zaza/utilities/file_assertions.py +++ b/zaza/utilities/file_assertions.py @@ -20,7 +20,9 @@ import zaza.model as model def assert_path_glob(test_case, unit, file_details, paths=[]): """Verify all files in a given directory.""" result = model.run_on_unit( - unit, 'stat -c "%n %U %G %a" {}'.format(file_details['path'])) + unit, 'bash -c "' + 'shopt -s -q globstar; ' + 'stat -c "%n %U %G %a" {}"'.format(file_details['path'])) files = result['Stdout'] for file in files.splitlines(): file, owner, group, mode = file.split() From a49dfb79c94f51f860e2b76f622f59f18ad16274 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Wed, 30 Jan 2019 11:50:10 +0100 Subject: [PATCH 3/4] move paths calculation out one loop --- zaza/charm_tests/security/tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zaza/charm_tests/security/tests.py b/zaza/charm_tests/security/tests.py index 2a414a2..a174c60 100644 --- a/zaza/charm_tests/security/tests.py +++ b/zaza/charm_tests/security/tests.py @@ -45,9 +45,12 @@ def _add_tests(): for name, attributes in files.items(): # Lets make sure to only add tests for deployed applications if name in deployed_applications: + paths = [ + file['path'] for + file in attributes['files'] + if "*" not in file["path"] + ] for file in attributes['files']: - paths = [file['path'] for file in attributes['files']] - paths = [path for path in paths if path[-1] is not "*"] test_func = _make_test_function(name, file, paths=paths) setattr( cls, From fdffe20a8a3491d3293bd02cfe05f63b985c8b12 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Wed, 30 Jan 2019 11:59:44 +0100 Subject: [PATCH 4/4] remove dynamic function default --- zaza/charm_tests/security/tests.py | 2 +- zaza/utilities/file_assertions.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/zaza/charm_tests/security/tests.py b/zaza/charm_tests/security/tests.py index a174c60..fd6db82 100644 --- a/zaza/charm_tests/security/tests.py +++ b/zaza/charm_tests/security/tests.py @@ -26,7 +26,7 @@ from zaza.utilities.file_assertions import ( ) -def _make_test_function(application, file_details, paths=[]): +def _make_test_function(application, file_details, paths=None): def test(self): for unit in model.get_units(application): unit = unit.entity_id diff --git a/zaza/utilities/file_assertions.py b/zaza/utilities/file_assertions.py index 470fcd2..5993f81 100644 --- a/zaza/utilities/file_assertions.py +++ b/zaza/utilities/file_assertions.py @@ -17,8 +17,10 @@ import zaza.model as model -def assert_path_glob(test_case, unit, file_details, paths=[]): +def assert_path_glob(test_case, unit, file_details, paths=None): """Verify all files in a given directory.""" + if not paths: + paths = [] result = model.run_on_unit( unit, 'bash -c "' 'shopt -s -q globstar; '