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

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