Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock
Please unblock package ionit ionit runs too late for /etc/network/interfaces (RC bug #919690). This is fixed in 0.3.2-1. The debdiff is attached. ionit is a quite new and very small tool (popcon count: 4), which is developed and used by us. It has 100% test coverage (run at build time and as autopkgtest). unblock ionit/0.3.2-1 -- Benjamin Drung System Developer Debian & Ubuntu Developer 1&1 IONOS Cloud GmbH | Greifswalder Str. 207 | 10405 Berlin | Germany E-mail: benjamin.dr...@cloud.ionos.com | Web: www.ionos.de Head Office: Berlin, Germany District Court Berlin Charlottenburg, Registration number: HRB 125506 B Executive Management: Christoph Steffens, Matthias Steinberg, Achim Weiss Member of United Internet
diff -Nru ionit-0.2.1/debian/changelog ionit-0.3.2/debian/changelog --- ionit-0.2.1/debian/changelog 2019-01-07 14:22:30.000000000 +0100 +++ ionit-0.3.2/debian/changelog 2019-06-20 12:21:44.000000000 +0200 @@ -1,3 +1,13 @@ +ionit (0.3.2-1) unstable; urgency=medium + + * New upstream release. + - Support specifying a configuration file + - Support specifying --config multiple times + - Run ionit.service before systemd-modules-load.service + - Run ionit.service before systemd-udev-trigger.service (Closes: #919690) + + -- Benjamin Drung <benjamin.dr...@cloud.ionos.com> Thu, 20 Jun 2019 12:21:44 +0200 + ionit (0.2.1-1) unstable; urgency=medium * New upstream release. diff -Nru ionit-0.2.1/ionit ionit-0.3.2/ionit --- ionit-0.2.1/ionit 2019-01-07 14:01:10.000000000 +0100 +++ ionit-0.3.2/ionit 2019-06-20 12:17:42.000000000 +0200 @@ -28,6 +28,7 @@ import ionit_plugin +DEFAULT_CONFIG = "/etc/ionit" LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' SCRIPT_NAME = "ionit" @@ -86,23 +87,34 @@ return context -def collect_context(directory): +def get_config_files(paths): + """Return files for the given paths (could either be files or directories).""" + logger = logging.getLogger(SCRIPT_NAME) + files = [] + for path in paths: + logger.debug("Searching for configuration files in '%s'...", path) + try: + if os.path.isfile(path): + files.append(path) + else: + files += sorted([os.path.join(path, f) for f in os.listdir(path)]) + except OSError as error: + logger.warning("Failed to read configuration directory: %s", error) + logger.debug("Configuration files: %s", files) + return files + + +def collect_context(paths): """Collect context that will be used when rendering the templates""" logger = logging.getLogger(SCRIPT_NAME) - logger.debug("Collecting context from '%s'...", directory) - try: - files = sorted(os.listdir(directory)) - except OSError as error: - logger.warning("Failed to read configuration directory: %s", error) - files = [] + logger.debug("Collecting context...") failures = 0 context = {} - for filename in files: + for file in get_config_files(paths): file_context = None - file = os.path.join(directory, filename) - extension = os.path.splitext(filename)[1] + extension = os.path.splitext(file)[1] try: if extension == ".json": logger.info("Reading configuration file '%s'...", file) @@ -184,9 +196,9 @@ def main(argv): """Main function with argument parsing""" parser = argparse.ArgumentParser() - parser.add_argument("-c", "--config", default="/etc/ionit", - help="Configuration directory containing context for rendering (default: " - "%(default)s)") + parser.add_argument("-c", "--config", action="append", + help="Configuration directory/file containing context for rendering " + "(default: %s)" % (DEFAULT_CONFIG,)) parser.add_argument("-t", "--templates", default="/etc", help="Directory to search for Jinja templates (default: %(default)s)") parser.add_argument("-e", "--template-extension", default="jinja", @@ -197,6 +209,8 @@ help="Decrease output verbosity to warnings and errors", action="store_const", const=logging.WARNING) args = parser.parse_args(argv) + if args.config is None: + args.config = [DEFAULT_CONFIG] logging.basicConfig(level=args.log_level, format=LOG_FORMAT) logger = logging.getLogger(SCRIPT_NAME) diff -Nru ionit-0.2.1/ionit.py ionit-0.3.2/ionit.py --- ionit-0.2.1/ionit.py 2019-01-07 14:01:10.000000000 +0100 +++ ionit-0.3.2/ionit.py 2019-06-20 12:17:42.000000000 +0200 @@ -28,6 +28,7 @@ import ionit_plugin +DEFAULT_CONFIG = "/etc/ionit" LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' SCRIPT_NAME = "ionit" @@ -86,23 +87,34 @@ return context -def collect_context(directory): +def get_config_files(paths): + """Return files for the given paths (could either be files or directories).""" + logger = logging.getLogger(SCRIPT_NAME) + files = [] + for path in paths: + logger.debug("Searching for configuration files in '%s'...", path) + try: + if os.path.isfile(path): + files.append(path) + else: + files += sorted([os.path.join(path, f) for f in os.listdir(path)]) + except OSError as error: + logger.warning("Failed to read configuration directory: %s", error) + logger.debug("Configuration files: %s", files) + return files + + +def collect_context(paths): """Collect context that will be used when rendering the templates""" logger = logging.getLogger(SCRIPT_NAME) - logger.debug("Collecting context from '%s'...", directory) - try: - files = sorted(os.listdir(directory)) - except OSError as error: - logger.warning("Failed to read configuration directory: %s", error) - files = [] + logger.debug("Collecting context...") failures = 0 context = {} - for filename in files: + for file in get_config_files(paths): file_context = None - file = os.path.join(directory, filename) - extension = os.path.splitext(filename)[1] + extension = os.path.splitext(file)[1] try: if extension == ".json": logger.info("Reading configuration file '%s'...", file) @@ -184,9 +196,9 @@ def main(argv): """Main function with argument parsing""" parser = argparse.ArgumentParser() - parser.add_argument("-c", "--config", default="/etc/ionit", - help="Configuration directory containing context for rendering (default: " - "%(default)s)") + parser.add_argument("-c", "--config", action="append", + help="Configuration directory/file containing context for rendering " + "(default: %s)" % (DEFAULT_CONFIG,)) parser.add_argument("-t", "--templates", default="/etc", help="Directory to search for Jinja templates (default: %(default)s)") parser.add_argument("-e", "--template-extension", default="jinja", @@ -197,6 +209,8 @@ help="Decrease output verbosity to warnings and errors", action="store_const", const=logging.WARNING) args = parser.parse_args(argv) + if args.config is None: + args.config = [DEFAULT_CONFIG] logging.basicConfig(level=args.log_level, format=LOG_FORMAT) logger = logging.getLogger(SCRIPT_NAME) diff -Nru ionit-0.2.1/ionit.service ionit-0.3.2/ionit.service --- ionit-0.2.1/ionit.service 2019-01-07 14:01:10.000000000 +0100 +++ ionit-0.3.2/ionit.service 2019-06-20 12:17:42.000000000 +0200 @@ -3,7 +3,7 @@ Documentation=man:ionit(1) DefaultDependencies=no After=local-fs.target -Before=ferm.service network-pre.target openibd.service shutdown.target sysinit.target +Before=ferm.service network-pre.target openibd.service shutdown.target sysinit.target systemd-modules-load.service systemd-udev-trigger.service Wants=network-pre.target RequiresMountsFor=/usr diff -Nru ionit-0.2.1/NEWS ionit-0.3.2/NEWS --- ionit-0.2.1/NEWS 2019-01-07 14:01:10.000000000 +0100 +++ ionit-0.3.2/NEWS 2019-06-20 12:17:42.000000000 +0200 @@ -1,3 +1,17 @@ +ionit 0.3.2 (2019-06-20) + +* Run ionit.service before systemd-udev-trigger.service + (fixes Debian bug #919690) + +ionit 0.3.1 (2019-04-11) + +* Run ionit.service before systemd-modules-load.service + +ionit 0.3.0 (2019-02-20) + +* Support specifying a configuration file (instead of a directory) +* Support specifying --config multiple times + ionit 0.2.1 (2019-01-07) * Remove unnecessary pass statement to make pylint 2.2.2 happy diff -Nru ionit-0.2.1/setup.py ionit-0.3.2/setup.py --- ionit-0.2.1/setup.py 2019-01-07 14:01:10.000000000 +0100 +++ ionit-0.3.2/setup.py 2019-06-20 12:17:42.000000000 +0200 @@ -34,7 +34,7 @@ if __name__ == "__main__": setup( name="ionit", - version="0.2.1", + version="0.3.2", description="Render configuration files from Jinja templates", long_description=( "ionit is a simple and small configuration templating tool. It collects a context and " diff -Nru ionit-0.2.1/tests/test_ionit.py ionit-0.3.2/tests/test_ionit.py --- ionit-0.2.1/tests/test_ionit.py 2019-01-07 14:01:10.000000000 +0100 +++ ionit-0.3.2/tests/test_ionit.py 2019-06-20 12:17:42.000000000 +0200 @@ -33,30 +33,35 @@ """ def test_collect_function(self): - """Test: Run collect_context("tests/config/function")""" - failures, context = collect_context(os.path.join(CONFIG_DIR, "function")) + """Test: Run collect_context(["tests/config/function"])""" + failures, context = collect_context([os.path.join(CONFIG_DIR, "function")]) self.assertEqual(failures, 0) self.assertEqual(set(context.keys()), set(["answer_to_all_questions"])) self.assertEqual(context["answer_to_all_questions"](), 42) def test_collect_static_context(self): - """Test: Run collect_context("tests/config/static")""" - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "static")), (0, { + """Test: Run collect_context(["tests/config/static"])""" + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "static")]), (0, { "first": 1, "second": 2, })) + def test_configuration_file(self): + """Test: Run collect_context(["tests/config/static/second.yaml"])""" + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "static", "second.yaml")]), + (0, {"second": 2})) + def test_context_stacking(self): - """Test: Run collect_context("tests/config/stacking")""" - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "stacking")), (0, { + """Test: Run collect_context(["tests/config/stacking"])""" + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "stacking")]), (0, { "big_number": 1071, "small_number": 7, })) def test_empty_python_file(self): - """Test: Run collect_context("tests/config/empty")""" + """Test: Run collect_context(["tests/config/empty"])""" with self.assertLogs("ionit", level="WARNING") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "empty")), (0, {})) + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "empty")]), (0, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], ( "WARNING:ionit:Python module '[^']+config/empty/empty.py' does " @@ -64,10 +69,10 @@ r"\(using the ionit_plugin.function decorator\).")) def test_empty_context(self): - """Test: Run collect_context("tests/config/empty-context")""" + """Test: Run collect_context(["tests/config/empty-context"])""" try: with self.assertLogs("ionit", level="WARNING") as context_manager: - failures, context = collect_context(os.path.join(CONFIG_DIR, "empty-context")) + failures, context = collect_context([os.path.join(CONFIG_DIR, "empty-context")]) except AssertionError: pass self.assertEqual(failures, 0) @@ -75,9 +80,9 @@ self.assertEqual(context_manager.output, []) def test_ignoring_additional_files(self): - """Test: Run collect_context("tests/config/additional-file")""" + """Test: Run collect_context(["tests/config/additional-file"])""" with self.assertLogs("ionit", level="INFO") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "additional-file")), + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "additional-file")]), (0, {"key": "value"})) self.assertEqual(len(context_manager.output), 2) self.assertRegex(context_manager.output[0], ( @@ -85,18 +90,19 @@ "because it does not end with .*")) def test_invalid_json(self): - """Test: Run collect_context("tests/config/invalid-json")""" + """Test: Run collect_context(["tests/config/invalid-json"])""" with self.assertLogs("ionit", level="ERROR") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "invalid-json")), (1, {})) + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "invalid-json")]), (1, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], ( "ERROR:ionit:Failed to read JSON from '[^']*config/invalid-json/invalid.json': " r"Expecting property name enclosed in double quotes: line 3 column 1 \(char 22\)")) def test_invalid_python(self): - """Test: Run collect_context("tests/config/invalid-python")""" + """Test: Run collect_context(["tests/config/invalid-python"])""" with self.assertLogs("ionit", level="ERROR") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "invalid-python")), (1, {})) + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "invalid-python")]), + (1, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], re.compile( "ERROR:ionit:Importing Python module '[^']*config/invalid-python/invalid.py' " @@ -104,9 +110,9 @@ flags=re.DOTALL)) def test_invalid_yaml(self): - """Test: Run collect_context("tests/config/invalid-yaml")""" + """Test: Run collect_context(["tests/config/invalid-yaml"])""" with self.assertLogs("ionit", level="ERROR") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "invalid-yaml")), (1, {})) + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "invalid-yaml")]), (1, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], ( "ERROR:ionit:Failed to read YAML from '[^']*config/invalid-yaml/invalid.yaml': " @@ -116,7 +122,7 @@ def test_missing_directory(self): """Test: Non-existing context directory""" with self.assertLogs("ionit", level="WARNING") as context_manager: - self.assertEqual(collect_context(os.path.join(TESTS_DIR, "non-existing-directory")), + self.assertEqual(collect_context([os.path.join(TESTS_DIR, "non-existing-directory")]), (0, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], ( @@ -124,9 +130,9 @@ r"No such file or directory: '\S*non-existing-directory'")) def test_non_dict_context(self): - """Test failure for collect_context("tests/config/non-dict")""" + """Test failure for collect_context(["tests/config/non-dict"])""" with self.assertLogs("ionit", level="ERROR") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "non-dict")), (1, {})) + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "non-dict")]), (1, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], ( "ERROR:ionit:Failed to update context with content from " @@ -134,16 +140,16 @@ "element #0 has length 1; 2 is required")) def test_python_module(self): - """Test: Run collect_context("tests/config/python")""" - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "python")), (0, { + """Test: Run collect_context(["tests/config/python"])""" + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "python")]), (0, { "small": 42, "big": 8000, })) def test_raise_exception(self): - """Test failure for collect_context("tests/config/exception")""" + """Test failure for collect_context(["tests/config/exception"])""" with self.assertLogs("ionit", level="ERROR") as context_manager: - self.assertEqual(collect_context(os.path.join(CONFIG_DIR, "exception")), (1, {})) + self.assertEqual(collect_context([os.path.join(CONFIG_DIR, "exception")]), (1, {})) self.assertEqual(len(context_manager.output), 1) self.assertRegex(context_manager.output[0], re.compile( r"ERROR:ionit:Calling collect_context\(\) from '\S*config/exception/exception.py' " @@ -230,6 +236,19 @@ class TestMain(unittest.TestCase): """Test main function""" + @unittest.mock.patch("ionit.DEFAULT_CONFIG", os.path.join(CONFIG_DIR, "function")) + def test_main_default_config(self): + """Test main() with default config""" + template_dir = os.path.join(TEMPLATE_DIR, "function") + try: + self.assertEqual(main(["-t", template_dir]), 0) + with open(os.path.join(template_dir, "Document")) as document_file: + self.assertEqual(document_file.read(), ( + "The answer to the Ultimate Question of Life, The Universe, " + "and Everything is 42.\n")) + finally: + os.remove(os.path.join(template_dir, "Document")) + def test_main_static(self): """Test main() with static context""" template_dir = os.path.join(TEMPLATE_DIR, "static")