Blame SOURCES/0005-Unify-command-line-options-and-configuration.patch

d27d41
From 424daee946f9189d7ecdd2e5e21202c2ec02f4f8 Mon Sep 17 00:00:00 2001
d27d41
From: Rob Crittenden <rcritten@redhat.com>
d27d41
Date: Wed, 6 Apr 2022 13:19:36 -0400
d27d41
Subject: [PATCH] Unify command-line options and configuration
d27d41
d27d41
This makes it possible to add command-line options to the
d27d41
configuration file.
d27d41
d27d41
The config file is loaded then the command-line options are
d27d41
merged in. The first one option set takes precedence. So if
d27d41
an option, say output_type, is in the configuration file then
d27d41
passing output-type on the command-line will not override it.
d27d41
The workaround is to pass --config= to ipa-healthcheck in order
d27d41
to not load the configuration file.
d27d41
d27d41
This will allow for greater flexibility when running this automatically
d27d41
without having to manually change test timer scripting directly.
d27d41
d27d41
https://bugzilla.redhat.com/show_bug.cgi?id=1872467
d27d41
d27d41
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
d27d41
---
d27d41
 man/man5/ipahealthcheck.conf.5    | 10 ++++++-
d27d41
 man/man8/ipa-healthcheck.8        |  3 +++
d27d41
 src/ipahealthcheck/core/config.py |  2 +-
d27d41
 src/ipahealthcheck/core/core.py   | 22 +++++++++++----
d27d41
 src/ipahealthcheck/core/output.py |  4 +--
d27d41
 tests/test_init.py                |  6 ++---
d27d41
 tests/test_options.py             | 45 +++++++++++++++++++++++++++++++
d27d41
 7 files changed, 80 insertions(+), 12 deletions(-)
d27d41
 create mode 100644 tests/test_options.py
d27d41
d27d41
diff --git a/man/man5/ipahealthcheck.conf.5 b/man/man5/ipahealthcheck.conf.5
d27d41
index 50d5506..8ff83c9 100644
d27d41
--- a/man/man5/ipahealthcheck.conf.5
d27d41
+++ b/man/man5/ipahealthcheck.conf.5
d27d41
@@ -36,6 +36,14 @@ The following options are relevant for the server:
d27d41
 .TP
d27d41
 .B cert_expiration_days\fR
d27d41
 The number of days left before a certificate expires to start displaying a warning. The default is 28.
d27d41
+.TP
d27d41
+All command\-line options may be included in the configuration file. Dashes must be converted to underscore for the configuration file, e.g. \-\-output\-type becomes output_type. All options, including those that don't make sense in a config file, like \-\-list\-sources, are allowed. Let the buyer beware.
d27d41
+.TP
d27d41
+The purpose of allowing command\-line options to be in the configuration file is for automation without having to tweak the automation script. For example, if you want the default output type to be human for the systemd timer automated runs, settting output_type=human in the configuration file will do this. When loading configuration the first option wins, so if any option is in the configuration file then it cannot be overridden by the command-line unless a different configuration file is specified (see \-\-config).
d27d41
+.TP
d27d41
+There may be conflicting exceptions. For example, if all=True is set in the configuration file, and the command\-line contains \-\-failures\-only, then only failures will be displayed because of the way the option evaluation is done.
d27d41
+.TP
d27d41
+Options that don't make sense for the configuration file include \-\-list\-sources and \-\-input\-file.
d27d41
 .SH "FILES"
d27d41
 .TP
d27d41
 .I /etc/ipahealthcheck/ipahealthcheck.conf
d27d41
@@ -49,4 +57,4 @@ configuration file
d27d41
  cert_expiration_days=7
d27d41
 
d27d41
 .SH "SEE ALSO"
d27d41
-.BR ipa-healthcheck (8)
d27d41
+.BR ipa\-healthcheck (8)
d27d41
diff --git a/man/man8/ipa-healthcheck.8 b/man/man8/ipa-healthcheck.8
d27d41
index 9c97cfe..148a08b 100644
d27d41
--- a/man/man8/ipa-healthcheck.8
d27d41
+++ b/man/man8/ipa-healthcheck.8
d27d41
@@ -30,6 +30,9 @@ Display a list of the available sources and the checks associated with those sou
d27d41
 
d27d41
 .SS "OPTIONAL ARGUMENTS"
d27d41
 .TP
d27d41
+\fB\-\-config\fR=\fIFILE\fR
d27d41
+The configuration file to use. If an empty string is passed in then no configuration file is loaded. The default is /etc/ipahealthcheck/ipahealthcheck.conf.
d27d41
+.TP
d27d41
 \fB\-\-source\fR=\fISOURCE\fR
d27d41
 Execute checks within the named source, or all sources in the given namespace.
d27d41
 .TP
d27d41
diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py
d27d41
index bf9ca1f..c4322a5 100644
d27d41
--- a/src/ipahealthcheck/core/config.py
d27d41
+++ b/src/ipahealthcheck/core/config.py
d27d41
@@ -63,7 +63,7 @@ class Config:
d27d41
         """
d27d41
         Merge variables from dict ``d`` into the configuration
d27d41
 
d27d41
-        The last one wins.
d27d41
+        The first one wins.
d27d41
 
d27d41
         :param d: dict containing configuration
d27d41
         """
d27d41
diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py
d27d41
index 6fd3640..aaadd3b 100644
d27d41
--- a/src/ipahealthcheck/core/core.py
d27d41
+++ b/src/ipahealthcheck/core/core.py
d27d41
@@ -163,6 +163,8 @@ def list_sources(plugins):
d27d41
 def add_default_options(parser, output_registry, default_output):
d27d41
     output_names = [plugin.__name__.lower() for
d27d41
                     plugin in output_registry.plugins]
d27d41
+    parser.add_argument('--config', dest='config',
d27d41
+                        default=None, help='Config file to load')
d27d41
     parser.add_argument('--verbose', dest='verbose', action='store_true',
d27d41
                         default=False, help='Run in verbose mode')
d27d41
     parser.add_argument('--debug', dest='debug', action='store_true',
d27d41
@@ -176,9 +178,10 @@ def add_default_options(parser, output_registry, default_output):
d27d41
     parser.add_argument('--check', dest='check',
d27d41
                         default=None,
d27d41
                         help='Check to execute, e.g. BazCheck')
d27d41
-    parser.add_argument('--output-type', dest='output', choices=output_names,
d27d41
+    parser.add_argument('--output-type', dest='output_type',
d27d41
+                        choices=output_names,
d27d41
                         default=default_output, help='Output method')
d27d41
-    parser.add_argument('--output-file', dest='outfile', default=None,
d27d41
+    parser.add_argument('--output-file', dest='output_file', default=None,
d27d41
                         help='File to store output')
d27d41
     parser.add_argument('--version', dest='version', action='store_true',
d27d41
                         help='Report the version number and exit')
d27d41
@@ -266,7 +269,6 @@ class RunChecks:
d27d41
         add_output_options(self.parser, self.output_registry)
d27d41
         self.add_options()
d27d41
         options = parse_options(self.parser)
d27d41
-        self.options = options
d27d41
 
d27d41
         if options.version:
d27d41
             for registry in self.entry_points:
d27d41
@@ -290,10 +292,20 @@ class RunChecks:
d27d41
         if options.debug:
d27d41
             logger.setLevel(logging.DEBUG)
d27d41
 
d27d41
-        config = read_config(self.configfile)
d27d41
+        if options.config is not None:
d27d41
+            config = read_config(options.config)
d27d41
+        else:
d27d41
+            config = read_config(self.configfile)
d27d41
         if config is None:
d27d41
             return 1
d27d41
 
d27d41
+        # Unify config and options. One of these variables will be
d27d41
+        # eventually deprecated in the future. This way all cli
d27d41
+        # options can be set in config instead.
d27d41
+        config.merge(vars(options))
d27d41
+        self.options = config
d27d41
+        options = config
d27d41
+
d27d41
         # pylint: disable=assignment-from-none
d27d41
         rval = self.pre_check()
d27d41
         # pylint: enable=assignment-from-none
d27d41
@@ -327,7 +339,7 @@ class RunChecks:
d27d41
                 plugins.append(plugin)
d27d41
 
d27d41
         for out in self.output_registry.plugins:
d27d41
-            if out.__name__.lower() == options.output:
d27d41
+            if out.__name__.lower() == options.output_type:
d27d41
                 output = out(options)
d27d41
                 break
d27d41
 
d27d41
diff --git a/src/ipahealthcheck/core/output.py b/src/ipahealthcheck/core/output.py
d27d41
index 787ac6d..e44990e 100644
d27d41
--- a/src/ipahealthcheck/core/output.py
d27d41
+++ b/src/ipahealthcheck/core/output.py
d27d41
@@ -36,7 +36,7 @@ class Output:
d27d41
        which will render the results into a string for writing.
d27d41
     """
d27d41
     def __init__(self, options):
d27d41
-        self.filename = options.outfile
d27d41
+        self.filename = options.output_file
d27d41
 
d27d41
         # Non-required options in the framework, set logical defaults to
d27d41
         # pre 0.6 behavior with everything reported.
d27d41
@@ -110,7 +110,7 @@ class JSON(Output):
d27d41
 
d27d41
     def __init__(self, options):
d27d41
         super().__init__(options)
d27d41
-        self.indent = options.indent
d27d41
+        self.indent = int(options.indent)
d27d41
 
d27d41
     def generate(self, data):
d27d41
         output = json.dumps(data, indent=self.indent)
d27d41
diff --git a/tests/test_init.py b/tests/test_init.py
d27d41
index e18e03c..5f9e3e2 100644
d27d41
--- a/tests/test_init.py
d27d41
+++ b/tests/test_init.py
d27d41
@@ -10,12 +10,12 @@ from ipahealthcheck.core.output import output_registry
d27d41
 class RunChecks:
d27d41
     def run_healthcheck(self):
d27d41
         options = argparse.Namespace(check=None, debug=False, indent=2,
d27d41
-                                     list_sources=False, outfile=None,
d27d41
-                                     output='json', source=None,
d27d41
+                                     list_sources=False, output_file=None,
d27d41
+                                     output_type='json', source=None,
d27d41
                                      verbose=False)
d27d41
 
d27d41
         for out in output_registry.plugins:
d27d41
-            if out.__name__.lower() == options.output:
d27d41
+            if out.__name__.lower() == options.output_type:
d27d41
                 out(options)
d27d41
                 break
d27d41
 
d27d41
diff --git a/tests/test_options.py b/tests/test_options.py
d27d41
new file mode 100644
d27d41
index 0000000..da1866f
d27d41
--- /dev/null
d27d41
+++ b/tests/test_options.py
d27d41
@@ -0,0 +1,45 @@
d27d41
+#
d27d41
+# Copyright (C) 2022 FreeIPA Contributors see COPYING for license
d27d41
+#
d27d41
+
d27d41
+import argparse
d27d41
+import os
d27d41
+import tempfile
d27d41
+from unittest.mock import patch
d27d41
+
d27d41
+from ipahealthcheck.core.core import RunChecks
d27d41
+from ipahealthcheck.core.plugin import Results
d27d41
+
d27d41
+options = argparse.Namespace(check=None, source=None, debug=False,
d27d41
+                             indent=2, list_sources=False,
d27d41
+                             output_type='json', output_file=None,
d27d41
+                             verbose=False, version=False, config=None)
d27d41
+
d27d41
+
d27d41
+@patch('ipahealthcheck.core.core.run_service_plugins')
d27d41
+@patch('ipahealthcheck.core.core.run_plugins')
d27d41
+@patch('ipahealthcheck.core.core.parse_options')
d27d41
+def test_options_merge(mock_parse, mock_run, mock_service):
d27d41
+    """
d27d41
+    Test merging file-based and CLI options
d27d41
+    """
d27d41
+    mock_service.return_value = (Results(), [])
d27d41
+    mock_run.return_value = Results()
d27d41
+    mock_parse.return_value = options
d27d41
+    fd, config_path = tempfile.mkstemp()
d27d41
+    os.close(fd)
d27d41
+    with open(config_path, "w") as fd:
d27d41
+        fd.write('[default]\n')
d27d41
+        fd.write('output_type=human\n')
d27d41
+        fd.write('indent=5\n')
d27d41
+
d27d41
+    try:
d27d41
+        run = RunChecks(['ipahealthcheck.registry'], config_path)
d27d41
+
d27d41
+        run.run_healthcheck()
d27d41
+
d27d41
+        # verify two valus that have defaults with our overriden values
d27d41
+        assert run.options.output_type == 'human'
d27d41
+        assert run.options.indent == '5'
d27d41
+    finally:
d27d41
+        os.remove(config_path)
d27d41
-- 
d27d41
2.31.1
d27d41