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

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