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")

Reply via email to