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

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