diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7218656 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/freeipa-healthcheck-0.4.tar.gz diff --git a/.ipa-healthcheck.metadata b/.ipa-healthcheck.metadata new file mode 100644 index 0000000..f0c76cd --- /dev/null +++ b/.ipa-healthcheck.metadata @@ -0,0 +1 @@ +2e61604c36f6f793612b1e3bb3f5e78df1cb58ac SOURCES/freeipa-healthcheck-0.4.tar.gz diff --git a/SOURCES/0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch b/SOURCES/0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch new file mode 100644 index 0000000..ed363bc --- /dev/null +++ b/SOURCES/0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch @@ -0,0 +1,26 @@ +From 611a7d51ac6b49770cdc0da02d101023a4a49536 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Fri, 3 May 2019 10:10:57 -0400 +Subject: [PATCH] Remove requirement for pytest-runner since PyPI isn't + available + +We won't be executing make check because the dependencies aren't +available due to IDM being in a module. +--- + setup.py | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/setup.py b/setup.py +index 801323f..c3cd215 100644 +--- a/setup.py ++++ b/setup.py +@@ -60,6 +60,5 @@ setup( + 'Programming Language :: Python :: 3.6', + ], + python_requires='!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', +- setup_requires=['pytest-runner',], + tests_require=['pytest',], + ) +-- +2.17.2 + diff --git a/SOURCES/0002-Move-main-to-run_healthcheck-for-abstraction-purpose.patch b/SOURCES/0002-Move-main-to-run_healthcheck-for-abstraction-purpose.patch new file mode 100644 index 0000000..c14ea28 --- /dev/null +++ b/SOURCES/0002-Move-main-to-run_healthcheck-for-abstraction-purpose.patch @@ -0,0 +1,106 @@ +From eb87d277442dd1a1076ba9ae74c18ace8cc7dda8 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 11 Nov 2019 10:29:53 -0500 +Subject: [PATCH 2/5] Move main to run_healthcheck for abstraction purposes + +Take as arguments the entry point name and the configuration +file. +--- + src/ipahealthcheck/core/config.py | 4 ++-- + src/ipahealthcheck/core/main.py | 37 +++++++++++++++++-------------- + 2 files changed, 22 insertions(+), 19 deletions(-) + +diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py +index 46da507..0cb72b7 100644 +--- a/src/ipahealthcheck/core/config.py ++++ b/src/ipahealthcheck/core/config.py +@@ -5,7 +5,7 @@ + import logging + from configparser import ConfigParser, ParsingError + +-from ipahealthcheck.core.constants import CONFIG_FILE, CONFIG_SECTION ++from ipahealthcheck.core.constants import CONFIG_SECTION + from ipahealthcheck.core.constants import DEFAULT_CONFIG + + logger = logging.getLogger() +@@ -70,7 +70,7 @@ class Config: + self.__d[key] = d[key] + + +-def read_config(config_file=CONFIG_FILE): ++def read_config(config_file): + """ + Simple configuration file reader + +diff --git a/src/ipahealthcheck/core/main.py b/src/ipahealthcheck/core/main.py +index b3fbe6a..f59a8a8 100644 +--- a/src/ipahealthcheck/core/main.py ++++ b/src/ipahealthcheck/core/main.py +@@ -28,11 +28,14 @@ else: + from ipahealthcheck.meta.services import ServiceCheck + + +-def find_registries(): +- return { +- ep.name: ep.resolve() +- for ep in pkg_resources.iter_entry_points('ipahealthcheck.registry') +- } ++def find_registries(entry_points): ++ registries = {} ++ for entry_point in entry_points: ++ registries.update({ ++ ep.name: ep.resolve() ++ for ep in pkg_resources.iter_entry_points(entry_point) ++ }) ++ return registries + + + def find_plugins(name, registry): +@@ -194,9 +197,7 @@ def limit_results(results, source, check): + return new_results + + +-def main(): +- environ["KRB5_CLIENT_KTNAME"] = "/etc/krb5.keytab" +- environ["KRB5CCNAME"] = "MEMORY:" ++def run_healthcheck(entry_points, configfile): + framework = object() + plugins = [] + output = constants.DEFAULT_OUTPUT +@@ -208,17 +209,11 @@ def main(): + if options.debug: + logger.setLevel(logging.DEBUG) + +- config = read_config() ++ config = read_config(configfile) + if config is None: + sys.exit(1) + +- if not ( +- options.source or options.list_sources +- ) and not is_ipa_configured(): +- logging.error("IPA is not configured on this system.") +- sys.exit(1) +- +- for name, registry in find_registries().items(): ++ for name, registry in find_registries(entry_points).items(): + try: + registry.initialize(framework) + except Exception as e: +@@ -283,4 +278,12 @@ def main(): + return_value = 1 + break + +- sys.exit(return_value) ++ return return_value ++ ++ ++def main(): ++ environ["KRB5_CLIENT_KTNAME"] = "/etc/krb5.keytab" ++ environ["KRB5CCNAME"] = "MEMORY:" ++ ++ sys.exit(run_healthcheck(['ipahealthcheck.registry'], ++ constants.CONFIG_FILE)) +-- +2.20.1 + diff --git a/SOURCES/0003-Abstract-ServiceCheck-to-not-be-IPA-specific.patch b/SOURCES/0003-Abstract-ServiceCheck-to-not-be-IPA-specific.patch new file mode 100644 index 0000000..1ae8db6 --- /dev/null +++ b/SOURCES/0003-Abstract-ServiceCheck-to-not-be-IPA-specific.patch @@ -0,0 +1,187 @@ +From c40e32b9d0ac49806b8336bd5065350574d29672 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 18 Nov 2019 12:51:27 -0500 +Subject: [PATCH 3/5] Abstract ServiceCheck to not be IPA-specific + +It is up to the implementor to check for services running for now. +--- + src/ipahealthcheck/core/main.py | 9 +-------- + src/ipahealthcheck/core/service.py | 10 ++++++++++ + src/ipahealthcheck/meta/services.py | 29 +++++++++++++++-------------- + 3 files changed, 26 insertions(+), 22 deletions(-) + create mode 100644 src/ipahealthcheck/core/service.py + +diff --git a/src/ipahealthcheck/core/main.py b/src/ipahealthcheck/core/main.py +index f59a8a8..2b818d4 100644 +--- a/src/ipahealthcheck/core/main.py ++++ b/src/ipahealthcheck/core/main.py +@@ -15,18 +15,11 @@ from ipahealthcheck.core.config import read_config + from ipahealthcheck.core.plugin import Result, Results, json_to_results + from ipahealthcheck.core.output import output_registry + from ipahealthcheck.core import constants ++from ipahealthcheck.core.service import ServiceCheck + + logging.basicConfig(format='%(message)s') + logger = logging.getLogger() + +-try: +- from ipaserver.install.installutils import is_ipa_configured +-except ImportError: +- logging.error("IPA server packages are not installed on this system.") +- sys.exit(1) +-else: +- from ipahealthcheck.meta.services import ServiceCheck +- + + def find_registries(entry_points): + registries = {} +diff --git a/src/ipahealthcheck/core/service.py b/src/ipahealthcheck/core/service.py +new file mode 100644 +index 0000000..f9e2645 +--- /dev/null ++++ b/src/ipahealthcheck/core/service.py +@@ -0,0 +1,10 @@ ++# ++# Copyright (C) 2019 FreeIPA Contributors see COPYING for license ++# ++ ++from ipahealthcheck.core.plugin import Plugin ++ ++ ++class ServiceCheck(Plugin): ++ def check(self, instance=''): ++ raise NotImplementedError +diff --git a/src/ipahealthcheck/meta/services.py b/src/ipahealthcheck/meta/services.py +index d375066..a987108 100644 +--- a/src/ipahealthcheck/meta/services.py ++++ b/src/ipahealthcheck/meta/services.py +@@ -6,7 +6,8 @@ import logging + + from ipahealthcheck.core import constants + from ipahealthcheck.core.plugin import Result, duration +-from ipahealthcheck.meta.plugin import Plugin, registry ++from ipahealthcheck.core.service import ServiceCheck ++from ipahealthcheck.meta.plugin import registry + try: + from ipapython.ipaldap import realm_to_serverid + except ImportError: +@@ -20,7 +21,7 @@ from ipaserver.install import cainstance + logger = logging.getLogger() + + +-class ServiceCheck(Plugin): ++class IPAServiceCheck(ServiceCheck): + @duration + def check(self, instance=''): + try: +@@ -47,7 +48,7 @@ class ServiceCheck(Plugin): + + + @registry +-class certmonger(ServiceCheck): ++class certmonger(IPAServiceCheck): + def check(self): + self.service_name = 'certmonger' + +@@ -55,7 +56,7 @@ class certmonger(ServiceCheck): + + + @registry +-class dirsrv(ServiceCheck): ++class dirsrv(IPAServiceCheck): + def check(self): + self.service_name = 'dirsrv' + +@@ -63,7 +64,7 @@ class dirsrv(ServiceCheck): + + + @registry +-class gssproxy(ServiceCheck): ++class gssproxy(IPAServiceCheck): + def check(self): + self.service_name = 'gssproxy' + +@@ -71,7 +72,7 @@ class gssproxy(ServiceCheck): + + + @registry +-class httpd(ServiceCheck): ++class httpd(IPAServiceCheck): + def check(self): + self.service_name = 'httpd' + +@@ -79,7 +80,7 @@ class httpd(ServiceCheck): + + + @registry +-class ipa_custodia(ServiceCheck): ++class ipa_custodia(IPAServiceCheck): + def check(self): + self.service_name = 'ipa-custodia' + +@@ -87,7 +88,7 @@ class ipa_custodia(ServiceCheck): + + + @registry +-class ipa_dnskeysyncd(ServiceCheck): ++class ipa_dnskeysyncd(IPAServiceCheck): + def check(self): + self.service_name = 'ipa-dnskeysyncd' + +@@ -98,7 +99,7 @@ class ipa_dnskeysyncd(ServiceCheck): + + + @registry +-class ipa_otpd(ServiceCheck): ++class ipa_otpd(IPAServiceCheck): + def check(self): + self.service_name = 'ipa-otpd' + +@@ -106,7 +107,7 @@ class ipa_otpd(ServiceCheck): + + + @registry +-class kadmin(ServiceCheck): ++class kadmin(IPAServiceCheck): + def check(self): + self.service_name = 'kadmin' + +@@ -114,7 +115,7 @@ class kadmin(ServiceCheck): + + + @registry +-class krb5kdc(ServiceCheck): ++class krb5kdc(IPAServiceCheck): + def check(self): + self.service_name = 'krb5kdc' + +@@ -122,7 +123,7 @@ class krb5kdc(ServiceCheck): + + + @registry +-class named(ServiceCheck): ++class named(IPAServiceCheck): + def check(self): + self.service_name = 'named' + +@@ -133,7 +134,7 @@ class named(ServiceCheck): + + + @registry +-class pki_tomcatd(ServiceCheck): ++class pki_tomcatd(IPAServiceCheck): + def check(self): + self.service_name = 'pki_tomcatd' + +@@ -145,7 +146,7 @@ class pki_tomcatd(ServiceCheck): + + + @registry +-class sssd(ServiceCheck): ++class sssd(IPAServiceCheck): + def check(self): + self.service_name = 'sssd' + +-- +2.20.1 + diff --git a/SOURCES/0004-Move-the-abstracted-plugin-runner-code-into-a-separa.patch b/SOURCES/0004-Move-the-abstracted-plugin-runner-code-into-a-separa.patch new file mode 100644 index 0000000..a27845e --- /dev/null +++ b/SOURCES/0004-Move-the-abstracted-plugin-runner-code-into-a-separa.patch @@ -0,0 +1,579 @@ +From 952fc6f6dee99523360c9826ad865086cd31474f Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 18 Nov 2019 14:33:32 -0500 +Subject: [PATCH 4/5] Move the abstracted plugin runner code into a separate + file + +This way main.py contains just the ipa-healthcheck main code +and not the rest of the abstracted code. + +Also replace sys.exit(1) with return 1. +--- + src/ipahealthcheck/core/core.py | 272 ++++++++++++++++++++++++++++++++ + src/ipahealthcheck/core/main.py | 267 +------------------------------ + 2 files changed, 273 insertions(+), 266 deletions(-) + create mode 100644 src/ipahealthcheck/core/core.py + +diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py +new file mode 100644 +index 0000000..182eac3 +--- /dev/null ++++ b/src/ipahealthcheck/core/core.py +@@ -0,0 +1,272 @@ ++# ++# Copyright (C) 2019 FreeIPA Contributors see COPYING for license ++# ++ ++import argparse ++import json ++import logging ++import pkg_resources ++ ++from datetime import datetime ++ ++from ipahealthcheck.core.config import read_config ++from ipahealthcheck.core.plugin import Result, Results, json_to_results ++from ipahealthcheck.core.output import output_registry ++from ipahealthcheck.core import constants ++from ipahealthcheck.core.service import ServiceCheck ++ ++logging.basicConfig(format='%(message)s') ++logger = logging.getLogger() ++ ++ ++def find_registries(entry_points): ++ registries = {} ++ for entry_point in entry_points: ++ registries.update({ ++ ep.name: ep.resolve() ++ for ep in pkg_resources.iter_entry_points(entry_point) ++ }) ++ return registries ++ ++ ++def find_plugins(name, registry): ++ for ep in pkg_resources.iter_entry_points(name): ++ # load module ++ ep.load() ++ return registry.get_plugins() ++ ++ ++def run_plugin(plugin, available=()): ++ # manually calculate duration when we create results of our own ++ start = datetime.utcnow() ++ try: ++ for result in plugin.check(): ++ if result is None: ++ # Treat no result as success, fudge start time ++ result = Result(plugin, constants.SUCCESS, start=start) ++ yield result ++ except Exception as e: ++ logger.debug('Exception raised: %s', e) ++ yield Result(plugin, constants.CRITICAL, exception=str(e), ++ start=start) ++ ++ ++def source_or_check_matches(plugin, source, check): ++ """Determine whether a given a plugin matches if a source ++ and optional check are provided. ++ """ ++ if source is not None and plugin.__module__ != source: ++ return False ++ ++ if check and plugin.__class__.__name__ != check: ++ return False ++ ++ return True ++ ++ ++def run_service_plugins(plugins, config, source, check): ++ """Execute plugins with the base class of ServiceCheck ++ ++ This is a specialized check to use systemd to determine ++ if a service is running or not. ++ """ ++ results = Results() ++ available = [] ++ ++ for plugin in plugins: ++ if not isinstance(plugin, ServiceCheck): ++ continue ++ ++ logger.debug('Calling check %s', plugin) ++ for result in plugin.check(): ++ # always run the service checks so dependencies work ++ if result is not None and result.result == constants.SUCCESS: ++ available.append(plugin.service.service_name) ++ if not source_or_check_matches(plugin, source, check): ++ continue ++ if result is not None: ++ results.add(result) ++ ++ return results, set(available) ++ ++ ++def run_plugins(plugins, config, available, source, check): ++ """Execute plugins without the base class of ServiceCheck ++ ++ These are the remaining, non-service checking checks ++ that do validation for various parts of a system. ++ """ ++ results = Results() ++ ++ for plugin in plugins: ++ if isinstance(plugin, ServiceCheck): ++ continue ++ ++ if not source_or_check_matches(plugin, source, check): ++ continue ++ ++ logger.debug('Calling check %s' % plugin) ++ plugin.config = config ++ if not set(plugin.requires).issubset(available): ++ logger.debug('Skipping %s:%s because %s service(s) not running', ++ plugin.__class__.__module__, ++ plugin.__class__.__name__, ++ ', '.join(set(plugin.requires) - available)) ++ # Not providing a Result in this case because if a required ++ # service isn't available then this could generate a lot of ++ # false positives. ++ else: ++ for result in run_plugin(plugin, available): ++ results.add(result) ++ ++ return results ++ ++ ++def list_sources(plugins): ++ """Print list of all sources and checks""" ++ source = None ++ for plugin in plugins: ++ if source != plugin.__class__.__module__: ++ print(plugin.__class__.__module__) ++ source = plugin.__class__.__module__ ++ print(" ", plugin.__class__.__name__) ++ ++ return 0 ++ ++ ++def parse_options(output_registry): ++ output_names = [plugin.__name__.lower() for ++ plugin in output_registry.plugins] ++ parser = argparse.ArgumentParser() ++ parser.add_argument('--debug', dest='debug', action='store_true', ++ default=False, help='Include debug output') ++ parser.add_argument('--list-sources', dest='list_sources', ++ action='store_true', default=False, ++ help='List all available sources') ++ parser.add_argument('--source', dest='source', ++ default=None, ++ help='Source of checks, e.g. ipahealthcheck.foo.bar') ++ parser.add_argument('--check', dest='check', ++ default=None, ++ help='Check to execute, e.g. BazCheck') ++ parser.add_argument('--output-type', dest='output', choices=output_names, ++ default='json', help='Output method') ++ parser.add_argument('--output-file', dest='outfile', default=None, ++ help='File to store output') ++ parser.add_argument('--input-file', dest='infile', ++ help='File to read as input') ++ parser.add_argument('--failures-only', dest='failures_only', ++ action='store_true', default=False, ++ help='Exclude SUCCESS results on output') ++ parser.add_argument('--severity', dest='severity', action="append", ++ help='Include only the selected severity(s)', ++ choices=[key for key in constants._nameToLevel]) ++ for plugin in output_registry.plugins: ++ onelinedoc = plugin.__doc__.split('\n\n', 1)[0].strip() ++ group = parser.add_argument_group(plugin.__name__.lower(), ++ onelinedoc) ++ for option in plugin.options: ++ group.add_argument(option[0], **option[1]) ++ ++ options = parser.parse_args() ++ ++ # Validation ++ if options.check and not options.source: ++ print("--source is required when --check is used") ++ return 1 ++ ++ return options ++ ++ ++def limit_results(results, source, check): ++ """Return ony those results which match source and/or check""" ++ new_results = Results() ++ for result in results.results: ++ if result.source == source: ++ if check is None or result.check == check: ++ new_results.add(result) ++ return new_results ++ ++ ++def run_healthcheck(entry_points, configfile): ++ framework = object() ++ plugins = [] ++ output = constants.DEFAULT_OUTPUT ++ ++ logger.setLevel(logging.INFO) ++ ++ options = parse_options(output_registry) ++ ++ if options.debug: ++ logger.setLevel(logging.DEBUG) ++ ++ config = read_config(configfile) ++ if config is None: ++ return 1 ++ ++ for name, registry in find_registries(entry_points).items(): ++ try: ++ registry.initialize(framework) ++ except Exception as e: ++ print("Unable to initialize %s: %s" % (name, e)) ++ return 1 ++ for plugin in find_plugins(name, registry): ++ plugins.append(plugin) ++ ++ for out in output_registry.plugins: ++ if out.__name__.lower() == options.output: ++ output = out(options) ++ ++ if options.list_sources: ++ return list_sources(plugins) ++ ++ if options.infile: ++ try: ++ with open(options.infile, 'r') as f: ++ raw_data = f.read() ++ ++ json_data = json.loads(raw_data) ++ results = json_to_results(json_data) ++ available = () ++ except Exception as e: ++ print("Unable to import '%s': %s" % (options.infile, e)) ++ return 1 ++ if options.source: ++ results = limit_results(results, options.source, options.check) ++ else: ++ results, available = run_service_plugins(plugins, config, ++ options.source, ++ options.check) ++ results.extend(run_plugins(plugins, config, available, ++ options.source, options.check)) ++ ++ if options.source and len(results.results) == 0: ++ for plugin in plugins: ++ if not source_or_check_matches(plugin, options.source, ++ options.check): ++ continue ++ ++ if not set(plugin.requires).issubset(available): ++ print("Source '%s' is missing one or more requirements '%s'" % ++ (options.source, ', '.join(plugin.requires))) ++ return 1 ++ ++ if options.check: ++ print("Check '%s' not found in Source '%s'" % ++ (options.check, options.source)) ++ else: ++ print("Source '%s' not found" % options.source) ++ return 1 ++ ++ try: ++ output.render(results) ++ except Exception as e: ++ logger.error('Output raised %s: %s', e.__class__.__name__, e) ++ ++ return_value = 0 ++ for result in results.results: ++ if result.result != constants.SUCCESS: ++ return_value = 1 ++ break ++ ++ return return_value +diff --git a/src/ipahealthcheck/core/main.py b/src/ipahealthcheck/core/main.py +index 2b818d4..d9e85d7 100644 +--- a/src/ipahealthcheck/core/main.py ++++ b/src/ipahealthcheck/core/main.py +@@ -2,276 +2,11 @@ + # Copyright (C) 2019 FreeIPA Contributors see COPYING for license + # + +-import argparse +-import json +-import logging + from os import environ +-import pkg_resources + import sys + +-from datetime import datetime +- +-from ipahealthcheck.core.config import read_config +-from ipahealthcheck.core.plugin import Result, Results, json_to_results +-from ipahealthcheck.core.output import output_registry + from ipahealthcheck.core import constants +-from ipahealthcheck.core.service import ServiceCheck +- +-logging.basicConfig(format='%(message)s') +-logger = logging.getLogger() +- +- +-def find_registries(entry_points): +- registries = {} +- for entry_point in entry_points: +- registries.update({ +- ep.name: ep.resolve() +- for ep in pkg_resources.iter_entry_points(entry_point) +- }) +- return registries +- +- +-def find_plugins(name, registry): +- for ep in pkg_resources.iter_entry_points(name): +- # load module +- ep.load() +- return registry.get_plugins() +- +- +-def run_plugin(plugin, available=()): +- # manually calculate duration when we create results of our own +- start = datetime.utcnow() +- try: +- for result in plugin.check(): +- if result is None: +- # Treat no result as success, fudge start time +- result = Result(plugin, constants.SUCCESS, start=start) +- yield result +- except Exception as e: +- logger.debug('Exception raised: %s', e) +- yield Result(plugin, constants.CRITICAL, exception=str(e), +- start=start) +- +- +-def source_or_check_matches(plugin, source, check): +- """Determine whether a given a plugin matches if a source +- and optional check are provided. +- """ +- if source is not None and plugin.__module__ != source: +- return False +- +- if check and plugin.__class__.__name__ != check: +- return False +- +- return True +- +- +-def run_service_plugins(plugins, config, source, check): +- """Execute plugins with the base class of ServiceCheck +- +- This is a specialized check to use systemd to determine +- if a service is running or not. +- """ +- results = Results() +- available = [] +- +- for plugin in plugins: +- if not isinstance(plugin, ServiceCheck): +- continue +- +- logger.debug('Calling check %s', plugin) +- for result in plugin.check(): +- # always run the service checks so dependencies work +- if result is not None and result.result == constants.SUCCESS: +- available.append(plugin.service.service_name) +- if not source_or_check_matches(plugin, source, check): +- continue +- if result is not None: +- results.add(result) +- +- return results, set(available) +- +- +-def run_plugins(plugins, config, available, source, check): +- """Execute plugins without the base class of ServiceCheck +- +- These are the remaining, non-service checking checks +- that do validation for various parts of a system. +- """ +- results = Results() +- +- for plugin in plugins: +- if isinstance(plugin, ServiceCheck): +- continue +- +- if not source_or_check_matches(plugin, source, check): +- continue +- +- logger.debug('Calling check %s' % plugin) +- plugin.config = config +- if not set(plugin.requires).issubset(available): +- logger.debug('Skipping %s:%s because %s service(s) not running', +- plugin.__class__.__module__, +- plugin.__class__.__name__, +- ', '.join(set(plugin.requires) - available)) +- # Not providing a Result in this case because if a required +- # service isn't available then this could generate a lot of +- # false positives. +- else: +- for result in run_plugin(plugin, available): +- results.add(result) +- +- return results +- +- +-def list_sources(plugins): +- """Print list of all sources and checks""" +- source = None +- for plugin in plugins: +- if source != plugin.__class__.__module__: +- print(plugin.__class__.__module__) +- source = plugin.__class__.__module__ +- print(" ", plugin.__class__.__name__) +- +- return 0 +- +- +-def parse_options(output_registry): +- output_names = [plugin.__name__.lower() for +- plugin in output_registry.plugins] +- parser = argparse.ArgumentParser() +- parser.add_argument('--debug', dest='debug', action='store_true', +- default=False, help='Include debug output') +- parser.add_argument('--list-sources', dest='list_sources', +- action='store_true', default=False, +- help='List all available sources') +- parser.add_argument('--source', dest='source', +- default=None, +- help='Source of checks, e.g. ipahealthcheck.foo.bar') +- parser.add_argument('--check', dest='check', +- default=None, +- help='Check to execute, e.g. BazCheck') +- parser.add_argument('--output-type', dest='output', choices=output_names, +- default='json', help='Output method') +- parser.add_argument('--output-file', dest='outfile', default=None, +- help='File to store output') +- parser.add_argument('--input-file', dest='infile', +- help='File to read as input') +- parser.add_argument('--failures-only', dest='failures_only', +- action='store_true', default=False, +- help='Exclude SUCCESS results on output') +- parser.add_argument('--severity', dest='severity', action="append", +- help='Include only the selected severity(s)', +- choices=[key for key in constants._nameToLevel]) +- for plugin in output_registry.plugins: +- onelinedoc = plugin.__doc__.split('\n\n', 1)[0].strip() +- group = parser.add_argument_group(plugin.__name__.lower(), +- onelinedoc) +- for option in plugin.options: +- group.add_argument(option[0], **option[1]) +- +- options = parser.parse_args() +- +- # Validation +- if options.check and not options.source: +- print("--source is required when --check is used") +- sys.exit(1) +- +- return options +- +- +-def limit_results(results, source, check): +- """Return ony those results which match source and/or check""" +- new_results = Results() +- for result in results.results: +- if result.source == source: +- if check is None or result.check == check: +- new_results.add(result) +- return new_results +- +- +-def run_healthcheck(entry_points, configfile): +- framework = object() +- plugins = [] +- output = constants.DEFAULT_OUTPUT +- +- logger.setLevel(logging.INFO) +- +- options = parse_options(output_registry) +- +- if options.debug: +- logger.setLevel(logging.DEBUG) +- +- config = read_config(configfile) +- if config is None: +- sys.exit(1) +- +- for name, registry in find_registries(entry_points).items(): +- try: +- registry.initialize(framework) +- except Exception as e: +- print("Unable to initialize %s: %s" % (name, e)) +- sys.exit(1) +- for plugin in find_plugins(name, registry): +- plugins.append(plugin) +- +- for out in output_registry.plugins: +- if out.__name__.lower() == options.output: +- output = out(options) +- +- if options.list_sources: +- return list_sources(plugins) +- +- if options.infile: +- try: +- with open(options.infile, 'r') as f: +- raw_data = f.read() +- +- json_data = json.loads(raw_data) +- results = json_to_results(json_data) +- available = () +- except Exception as e: +- print("Unable to import '%s': %s" % (options.infile, e)) +- sys.exit(1) +- if options.source: +- results = limit_results(results, options.source, options.check) +- else: +- results, available = run_service_plugins(plugins, config, +- options.source, +- options.check) +- results.extend(run_plugins(plugins, config, available, +- options.source, options.check)) +- +- if options.source and len(results.results) == 0: +- for plugin in plugins: +- if not source_or_check_matches(plugin, options.source, +- options.check): +- continue +- +- if not set(plugin.requires).issubset(available): +- print("Source '%s' is missing one or more requirements '%s'" % +- (options.source, ', '.join(plugin.requires))) +- sys.exit(1) +- +- if options.check: +- print("Check '%s' not found in Source '%s'" % +- (options.check, options.source)) +- else: +- print("Source '%s' not found" % options.source) +- sys.exit(1) +- +- try: +- output.render(results) +- except Exception as e: +- logger.error('Output raised %s: %s', e.__class__.__name__, e) +- +- return_value = 0 +- for result in results.results: +- if result.result != constants.SUCCESS: +- return_value = 1 +- break +- +- return return_value ++from ipahealthcheck.core.core import run_healthcheck + + + def main(): +-- +2.20.1 + diff --git a/SOURCES/0005-Convert-running-healthchecks-into-a-class-and-add-pr.patch b/SOURCES/0005-Convert-running-healthchecks-into-a-class-and-add-pr.patch new file mode 100644 index 0000000..7a071e4 --- /dev/null +++ b/SOURCES/0005-Convert-running-healthchecks-into-a-class-and-add-pr.patch @@ -0,0 +1,228 @@ +From af5c169151f0697d34954ac5c9dd0686dc73ef3f Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 18 Nov 2019 15:13:44 -0500 +Subject: [PATCH 5/5] Convert running healthchecks into a class and add + pre-check + +Add a pre-check so that dependencies can be checked per product. +In the case of IPA verify that the server is installed otherwise +bail out. +--- + src/ipahealthcheck/core/core.py | 168 +++++++++++++++++--------------- + src/ipahealthcheck/core/main.py | 17 +++- + 2 files changed, 104 insertions(+), 81 deletions(-) + +diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py +index 182eac3..aef6197 100644 +--- a/src/ipahealthcheck/core/core.py ++++ b/src/ipahealthcheck/core/core.py +@@ -188,85 +188,97 @@ def limit_results(results, source, check): + return new_results + + +-def run_healthcheck(entry_points, configfile): +- framework = object() +- plugins = [] +- output = constants.DEFAULT_OUTPUT +- +- logger.setLevel(logging.INFO) +- +- options = parse_options(output_registry) +- +- if options.debug: +- logger.setLevel(logging.DEBUG) +- +- config = read_config(configfile) +- if config is None: +- return 1 +- +- for name, registry in find_registries(entry_points).items(): +- try: +- registry.initialize(framework) +- except Exception as e: +- print("Unable to initialize %s: %s" % (name, e)) +- return 1 +- for plugin in find_plugins(name, registry): +- plugins.append(plugin) +- +- for out in output_registry.plugins: +- if out.__name__.lower() == options.output: +- output = out(options) +- +- if options.list_sources: +- return list_sources(plugins) +- +- if options.infile: +- try: +- with open(options.infile, 'r') as f: +- raw_data = f.read() +- +- json_data = json.loads(raw_data) +- results = json_to_results(json_data) +- available = () +- except Exception as e: +- print("Unable to import '%s': %s" % (options.infile, e)) ++class RunChecks: ++ def __init__(self, entry_points, configfile): ++ self.entry_points = entry_points ++ self.configfile = configfile ++ ++ def pre_check(self): ++ pass ++ ++ def run_healthcheck(self): ++ framework = object() ++ plugins = [] ++ output = constants.DEFAULT_OUTPUT ++ ++ logger.setLevel(logging.INFO) ++ ++ options = parse_options(output_registry) ++ ++ if options.debug: ++ logger.setLevel(logging.DEBUG) ++ ++ config = read_config(self.configfile) ++ if config is None: + return 1 +- if options.source: +- results = limit_results(results, options.source, options.check) +- else: +- results, available = run_service_plugins(plugins, config, +- options.source, +- options.check) +- results.extend(run_plugins(plugins, config, available, +- options.source, options.check)) +- +- if options.source and len(results.results) == 0: +- for plugin in plugins: +- if not source_or_check_matches(plugin, options.source, +- options.check): +- continue + +- if not set(plugin.requires).issubset(available): +- print("Source '%s' is missing one or more requirements '%s'" % +- (options.source, ', '.join(plugin.requires))) ++ rval = self.pre_check() ++ if rval is not None: ++ return rval ++ ++ for name, registry in find_registries(self.entry_points).items(): ++ try: ++ registry.initialize(framework) ++ except Exception as e: ++ print("Unable to initialize %s: %s" % (name, e)) + return 1 +- +- if options.check: +- print("Check '%s' not found in Source '%s'" % +- (options.check, options.source)) ++ for plugin in find_plugins(name, registry): ++ plugins.append(plugin) ++ ++ for out in output_registry.plugins: ++ if out.__name__.lower() == options.output: ++ output = out(options) ++ ++ if options.list_sources: ++ return list_sources(plugins) ++ ++ if options.infile: ++ try: ++ with open(options.infile, 'r') as f: ++ raw_data = f.read() ++ ++ json_data = json.loads(raw_data) ++ results = json_to_results(json_data) ++ available = () ++ except Exception as e: ++ print("Unable to import '%s': %s" % (options.infile, e)) ++ return 1 ++ if options.source: ++ results = limit_results(results, options.source, options.check) + else: +- print("Source '%s' not found" % options.source) +- return 1 +- +- try: +- output.render(results) +- except Exception as e: +- logger.error('Output raised %s: %s', e.__class__.__name__, e) +- +- return_value = 0 +- for result in results.results: +- if result.result != constants.SUCCESS: +- return_value = 1 +- break +- +- return return_value ++ results, available = run_service_plugins(plugins, config, ++ options.source, ++ options.check) ++ results.extend(run_plugins(plugins, config, available, ++ options.source, options.check)) ++ ++ if options.source and len(results.results) == 0: ++ for plugin in plugins: ++ if not source_or_check_matches(plugin, options.source, ++ options.check): ++ continue ++ ++ if not set(plugin.requires).issubset(available): ++ print("Source '%s' is missing one or more requirements '%s'" % ++ (options.source, ', '.join(plugin.requires))) ++ return 1 ++ ++ if options.check: ++ print("Check '%s' not found in Source '%s'" % ++ (options.check, options.source)) ++ else: ++ print("Source '%s' not found" % options.source) ++ return 1 ++ ++ try: ++ output.render(results) ++ except Exception as e: ++ logger.error('Output raised %s: %s', e.__class__.__name__, e) ++ ++ return_value = 0 ++ for result in results.results: ++ if result.result != constants.SUCCESS: ++ return_value = 1 ++ break ++ ++ return return_value +diff --git a/src/ipahealthcheck/core/main.py b/src/ipahealthcheck/core/main.py +index d9e85d7..63a1de6 100644 +--- a/src/ipahealthcheck/core/main.py ++++ b/src/ipahealthcheck/core/main.py +@@ -6,12 +6,23 @@ from os import environ + import sys + + from ipahealthcheck.core import constants +-from ipahealthcheck.core.core import run_healthcheck ++from ipahealthcheck.core.core import RunChecks ++ ++from ipaserver.install.installutils import is_ipa_configured ++ ++ ++class IPAChecks(RunChecks): ++ def pre_check(self): ++ if not is_ipa_configured(): ++ print("IPA is not configured") ++ return 1 + + + def main(): + environ["KRB5_CLIENT_KTNAME"] = "/etc/krb5.keytab" + environ["KRB5CCNAME"] = "MEMORY:" + +- sys.exit(run_healthcheck(['ipahealthcheck.registry'], +- constants.CONFIG_FILE)) ++ ipachecks = IPAChecks(['ipahealthcheck.registry', ++ 'pkihealthcheck.registry'], ++ constants.CONFIG_FILE) ++ sys.exit(ipachecks.run_healthcheck()) +-- +2.20.1 + diff --git a/SOURCES/0006-Move-config-object-from-plugins-to-registry.patch b/SOURCES/0006-Move-config-object-from-plugins-to-registry.patch new file mode 100644 index 0000000..df32763 --- /dev/null +++ b/SOURCES/0006-Move-config-object-from-plugins-to-registry.patch @@ -0,0 +1,1535 @@ +From 9b6a90f1fc2508252bcb33791b24ec53f6b471cb Mon Sep 17 00:00:00 2001 +From: Dinesh Prasanth M K +Date: Tue, 10 Dec 2019 14:02:16 -0500 +Subject: [PATCH] Move config object from plugins to registry + +This patch moves the config object from the plugins +to the registry, thereby allowing the plugins to use +the values parsed from the healthcheck.conf file while +initializing. This improves the way a plugin can use +the values defined in the conf + +Signed-off-by: Dinesh Prasanth M K +--- + src/ipahealthcheck/core/core.py | 11 ++- + src/ipahealthcheck/core/plugin.py | 6 +- + src/ipahealthcheck/dogtag/plugin.py | 3 +- + src/ipahealthcheck/ds/plugin.py | 3 +- + src/ipahealthcheck/ipa/plugin.py | 3 +- + src/ipahealthcheck/system/plugin.py | 2 +- + tests/test_dogtag_ca.py | 9 +-- + tests/test_dogtag_connectivity.py | 8 +-- + tests/test_ds_replication.py | 6 +- + tests/test_ds_ruv.py | 9 +-- + tests/test_ipa_agent.py | 21 ++---- + tests/test_ipa_certfile_expiration.py | 9 +-- + tests/test_ipa_certmonger_ca.py | 6 +- + tests/test_ipa_dna.py | 9 +-- + tests/test_ipa_dns.py | 27 +++----- + tests/test_ipa_expiration.py | 18 ++--- + tests/test_ipa_nssdb.py | 12 ++-- + tests/test_ipa_nssvalidation.py | 9 +-- + tests/test_ipa_opensslvalidation.py | 6 +- + tests/test_ipa_revocation.py | 6 +- + tests/test_ipa_roles.py | 15 ++--- + tests/test_ipa_topology.py | 12 ++-- + tests/test_ipa_tracking.py | 8 +-- + tests/test_ipa_trust.py | 96 +++++++++------------------ + tests/test_meta_services.py | 3 +- + tests/test_system_filesystemspace.py | 9 +-- + 26 files changed, 120 insertions(+), 206 deletions(-) + +diff --git a/src/ipahealthcheck/core/core.py b/src/ipahealthcheck/core/core.py +index aef6197..ade8455 100644 +--- a/src/ipahealthcheck/core/core.py ++++ b/src/ipahealthcheck/core/core.py +@@ -64,7 +64,7 @@ def source_or_check_matches(plugin, source, check): + return True + + +-def run_service_plugins(plugins, config, source, check): ++def run_service_plugins(plugins, source, check): + """Execute plugins with the base class of ServiceCheck + + This is a specialized check to use systemd to determine +@@ -90,7 +90,7 @@ def run_service_plugins(plugins, config, source, check): + return results, set(available) + + +-def run_plugins(plugins, config, available, source, check): ++def run_plugins(plugins, available, source, check): + """Execute plugins without the base class of ServiceCheck + + These are the remaining, non-service checking checks +@@ -106,7 +106,6 @@ def run_plugins(plugins, config, available, source, check): + continue + + logger.debug('Calling check %s' % plugin) +- plugin.config = config + if not set(plugin.requires).issubset(available): + logger.debug('Skipping %s:%s because %s service(s) not running', + plugin.__class__.__module__, +@@ -218,7 +217,7 @@ class RunChecks: + + for name, registry in find_registries(self.entry_points).items(): + try: +- registry.initialize(framework) ++ registry.initialize(framework, config) + except Exception as e: + print("Unable to initialize %s: %s" % (name, e)) + return 1 +@@ -246,10 +245,10 @@ class RunChecks: + if options.source: + results = limit_results(results, options.source, options.check) + else: +- results, available = run_service_plugins(plugins, config, ++ results, available = run_service_plugins(plugins, + options.source, + options.check) +- results.extend(run_plugins(plugins, config, available, ++ results.extend(run_plugins(plugins, available, + options.source, options.check)) + + if options.source and len(results.results) == 0: +diff --git a/src/ipahealthcheck/core/plugin.py b/src/ipahealthcheck/core/plugin.py +index 6a29a7f..5754808 100644 +--- a/src/ipahealthcheck/core/plugin.py ++++ b/src/ipahealthcheck/core/plugin.py +@@ -41,9 +41,11 @@ class Registry: + def __init__(self): + self.plugins = [] + self.framework = None ++ self.config = dict() + +- def initialize(self, framework): ++ def initialize(self, framework, config): + self.framework = framework ++ self.config = config + + def __call__(self, cls): + if not callable(cls): +@@ -98,7 +100,7 @@ class Plugin: + + def __init__(self, registry): + self.registry = registry +- self.config = dict() ++ self.config = registry.config + + + class Result: +diff --git a/src/ipahealthcheck/dogtag/plugin.py b/src/ipahealthcheck/dogtag/plugin.py +index 3d8257e..d54fe3b 100644 +--- a/src/ipahealthcheck/dogtag/plugin.py ++++ b/src/ipahealthcheck/dogtag/plugin.py +@@ -16,7 +16,8 @@ class DogtagPlugin(Plugin): + + + class DogtagRegistry(Registry): +- def initialize(self, framework): ++ def initialize(self, framework, config): ++ super(DogtagRegistry, self).initialize(framework, config) + installutils.check_server_configuration() + if not api.isdone('bootstrap'): + api.bootstrap(in_server=True, +diff --git a/src/ipahealthcheck/ds/plugin.py b/src/ipahealthcheck/ds/plugin.py +index 9ef3461..0e40922 100644 +--- a/src/ipahealthcheck/ds/plugin.py ++++ b/src/ipahealthcheck/ds/plugin.py +@@ -16,7 +16,8 @@ class DSPlugin(Plugin): + + + class DSRegistry(Registry): +- def initialize(self, framework): ++ def initialize(self, framework, config): ++ super(DSRegistry, self).initialize(framework, config) + installutils.check_server_configuration() + if not api.isdone('bootstrap'): + api.bootstrap(in_server=True, +diff --git a/src/ipahealthcheck/ipa/plugin.py b/src/ipahealthcheck/ipa/plugin.py +index 34cb8c9..c1a6d39 100644 +--- a/src/ipahealthcheck/ipa/plugin.py ++++ b/src/ipahealthcheck/ipa/plugin.py +@@ -36,7 +36,8 @@ class IPARegistry(Registry): + self.trust_agent = False + self.trust_controller = False + +- def initialize(self, framework): ++ def initialize(self, framework, config): ++ super(IPARegistry, self).initialize(framework, config) + # deferred import for mock + from ipaserver.servroles import ADtrustBasedRole, ServiceBasedRole + +diff --git a/src/ipahealthcheck/system/plugin.py b/src/ipahealthcheck/system/plugin.py +index 23e8b5e..03d8c23 100644 +--- a/src/ipahealthcheck/system/plugin.py ++++ b/src/ipahealthcheck/system/plugin.py +@@ -12,7 +12,7 @@ class SystemPlugin(Plugin): + + + class SystemRegistry(Registry): +- def initialize(self, framework): ++ def initialize(self, framework, config): + pass + + +diff --git a/tests/test_dogtag_ca.py b/tests/test_dogtag_ca.py +index 48eba20..0820aba 100644 +--- a/tests/test_dogtag_ca.py ++++ b/tests/test_dogtag_ca.py +@@ -59,10 +59,9 @@ class TestCACerts(BaseTest): + mock_directive.side_effect = [name for name, nsstrust in trust.items()] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = DogtagCertsConfigCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 6 +@@ -98,10 +97,9 @@ class TestCACerts(BaseTest): + mock_directive.side_effect = nicknames + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = DogtagCertsConfigCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + num = len(self.results.results) +@@ -129,10 +127,9 @@ class TestCACerts(BaseTest): + mock_cainstance.return_value = CAInstance(False) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config) + f = DogtagCertsConfigCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 0 +diff --git a/tests/test_dogtag_connectivity.py b/tests/test_dogtag_connectivity.py +index 544e325..7165706 100644 +--- a/tests/test_dogtag_connectivity.py ++++ b/tests/test_dogtag_connectivity.py +@@ -5,7 +5,7 @@ + from util import capture_results, CAInstance + from util import m_api + from base import BaseTest +-from ipahealthcheck.core import constants ++from ipahealthcheck.core import constants, config + from ipahealthcheck.dogtag.plugin import registry + from ipahealthcheck.dogtag.ca import DogtagCertsConnectivityCheck + from unittest.mock import Mock +@@ -26,7 +26,7 @@ class TestCAConnectivity(BaseTest): + } + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = DogtagCertsConnectivityCheck(registry) + + self.results = capture_results(f) +@@ -47,7 +47,7 @@ class TestCAConnectivity(BaseTest): + ) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = DogtagCertsConnectivityCheck(registry) + + self.results = capture_results(f) +@@ -67,7 +67,7 @@ class TestCAConnectivity(BaseTest): + ) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = DogtagCertsConnectivityCheck(registry) + + self.results = capture_results(f) +diff --git a/tests/test_ds_replication.py b/tests/test_ds_replication.py +index b6b3652..24e909e 100644 +--- a/tests/test_ds_replication.py ++++ b/tests/test_ds_replication.py +@@ -44,10 +44,9 @@ class TestReplicationConflicts(BaseTest): + mock_conn.return_value = mock_ldap(None) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = ReplicationConflictCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # A valid call relies on a success to be set by core +@@ -73,10 +72,9 @@ class TestReplicationConflicts(BaseTest): + mock_conn.return_value = mock_ldap([ldapentry]) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = ReplicationConflictCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + result = self.results.results[0] + +diff --git a/tests/test_ds_ruv.py b/tests/test_ds_ruv.py +index 37070f5..8a3fe84 100644 +--- a/tests/test_ds_ruv.py ++++ b/tests/test_ds_ruv.py +@@ -63,11 +63,10 @@ class TestRUV(BaseTest): + + def test_no_ruvs(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = RUVCheck(registry) + + f.conn = mock_ldap(None) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 0 +@@ -88,11 +87,10 @@ class TestRUV(BaseTest): + ) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = RUVCheck(registry) + + f.conn = mock_ldap(entries) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -123,11 +121,10 @@ class TestRUV(BaseTest): + entries.append(None) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = RUVCheck(registry) + + f.conn = mock_ldap(entries) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +diff --git a/tests/test_ipa_agent.py b/tests/test_ipa_agent.py +index c58c7a6..8021298 100644 +--- a/tests/test_ipa_agent.py ++++ b/tests/test_ipa_agent.py +@@ -76,11 +76,10 @@ class TestNSSAgent(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = IPARAAgent(registry) + + f.conn = mock_ldap([ldapentry]) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -101,11 +100,10 @@ class TestNSSAgent(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = IPARAAgent(registry) + + f.conn = mock_ldap([ldapentry]) +- f.config = config.Config() + self.results = capture_results(f) + result = self.results.results[0] + +@@ -118,10 +116,9 @@ class TestNSSAgent(BaseTest): + mock_load_cert.side_effect = IOError('test') + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = IPARAAgent(registry) + +- f.config = config.Config() + self.results = capture_results(f) + result = self.results.results[0] + +@@ -131,11 +128,10 @@ class TestNSSAgent(BaseTest): + def test_nss_agent_no_entry_found(self): + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = IPARAAgent(registry) + + f.conn = mock_ldap(None) # None == NotFound +- f.config = config.Config() + self.results = capture_results(f) + result = self.results.results[0] + +@@ -158,11 +154,10 @@ class TestNSSAgent(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = IPARAAgent(registry) + + f.conn = mock_ldap([ldapentry, ldapentry2]) +- f.config = config.Config() + self.results = capture_results(f) + result = self.results.results[0] + +@@ -183,11 +178,10 @@ class TestNSSAgent(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config()) + f = IPARAAgent(registry) + + f.conn = mock_ldap([ldapentry]) +- f.config = config.Config() + self.results = capture_results(f) + result = self.results.results[0] + +@@ -208,11 +202,10 @@ class TestNSSAgent(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPARAAgent(registry) + + f.conn = mock_ldap([ldapentry]) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +diff --git a/tests/test_ipa_certfile_expiration.py b/tests/test_ipa_certfile_expiration.py +index d5601c5..43e59b4 100644 +--- a/tests/test_ipa_certfile_expiration.py ++++ b/tests/test_ipa_certfile_expiration.py +@@ -41,10 +41,9 @@ class TestIPACertificateFile(BaseTest): + mock_load_cert.return_value = cert + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertfileExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 28 + self.results = capture_results(f) + +@@ -65,10 +64,9 @@ class TestIPACertificateFile(BaseTest): + mock_load_cert.return_value = cert + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertfileExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 30 + self.results = capture_results(f) + +@@ -90,10 +88,9 @@ class TestIPACertificateFile(BaseTest): + mock_load_cert.return_value = cert + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertfileExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 30 + self.results = capture_results(f) + +diff --git a/tests/test_ipa_certmonger_ca.py b/tests/test_ipa_certmonger_ca.py +index 4eec1ba..3b67784 100644 +--- a/tests/test_ipa_certmonger_ca.py ++++ b/tests/test_ipa_certmonger_ca.py +@@ -4,7 +4,7 @@ + + from util import capture_results, CAInstance + from base import BaseTest +-from ipahealthcheck.core import constants ++from ipahealthcheck.core import constants, config + from ipahealthcheck.ipa.plugin import registry + from ipahealthcheck.ipa.certs import IPACertmongerCA + from unittest.mock import Mock, patch +@@ -24,7 +24,7 @@ class TestCertmonger(BaseTest): + 'dogtag-ipa-ca-renew-agent-reuse' + ] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertmongerCA(registry) + + self.results = capture_results(f) +@@ -44,7 +44,7 @@ class TestCertmonger(BaseTest): + ] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertmongerCA(registry) + + self.results = capture_results(f) +diff --git a/tests/test_ipa_dna.py b/tests/test_ipa_dna.py +index 5450642..5c88c22 100644 +--- a/tests/test_ipa_dna.py ++++ b/tests/test_ipa_dna.py +@@ -31,10 +31,9 @@ class TestDNARange(BaseTest): + def test_dnarange_set(self, mock_manager): + mock_manager.return_value = mock_ReplicationManager(start=1, max=100) + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNARangeCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -52,10 +51,9 @@ class TestDNARange(BaseTest): + def test_dnarange_noset(self, mock_manager): + mock_manager.return_value = mock_ReplicationManager() + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNARangeCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -76,10 +74,9 @@ class TestDNARange(BaseTest): + next=101, + next_max=200) + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNARangeCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +diff --git a/tests/test_ipa_dns.py b/tests/test_ipa_dns.py +index dde4c4d..61e9a27 100644 +--- a/tests/test_ipa_dns.py ++++ b/tests/test_ipa_dns.py +@@ -192,10 +192,9 @@ class TestDNSSystemRecords(BaseTest): + ] + }] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 9 +@@ -235,10 +234,9 @@ class TestDNSSystemRecords(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 17 +@@ -286,10 +284,9 @@ class TestDNSSystemRecords(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 25 +@@ -335,10 +332,9 @@ class TestDNSSystemRecords(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 23 +@@ -389,10 +385,9 @@ class TestDNSSystemRecords(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 25 +@@ -447,10 +442,9 @@ class TestDNSSystemRecords(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 25 +@@ -509,10 +503,9 @@ class TestDNSSystemRecords(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 32 +@@ -545,10 +538,9 @@ class TestDNSSystemRecords(BaseTest): + ] + }] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 9 +@@ -581,10 +573,9 @@ class TestDNSSystemRecords(BaseTest): + ] + }] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPADNSSystemRecordsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 15 +diff --git a/tests/test_ipa_expiration.py b/tests/test_ipa_expiration.py +index 6cd6952..cd7de82 100644 +--- a/tests/test_ipa_expiration.py ++++ b/tests/test_ipa_expiration.py +@@ -30,10 +30,9 @@ class TestExpiration(BaseTest): + set_requests() + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertmongerExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 7 + self.results = capture_results(f) + +@@ -66,10 +65,9 @@ class TestExpiration(BaseTest): + set_requests(remove=0, add=replaceme) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertmongerExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 30 + self.results = capture_results(f) + +@@ -122,10 +120,9 @@ class TestChainExpiration(BaseTest): + ) + ] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACAChainExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 7 + self.results = capture_results(f) + +@@ -160,10 +157,9 @@ class TestChainExpiration(BaseTest): + ) + ] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACAChainExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 7 + self.results = capture_results(f) + +@@ -200,10 +196,9 @@ class TestChainExpiration(BaseTest): + ) + ] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACAChainExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 7 + self.results = capture_results(f) + +@@ -238,10 +233,9 @@ class TestChainExpiration(BaseTest): + ) + ] + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACAChainExpirationCheck(registry) + +- f.config = config.Config() + f.config.cert_expiration_days = 7 + self.results = capture_results(f) + +diff --git a/tests/test_ipa_nssdb.py b/tests/test_ipa_nssdb.py +index 590401c..9def3d1 100644 +--- a/tests/test_ipa_nssdb.py ++++ b/tests/test_ipa_nssdb.py +@@ -48,10 +48,9 @@ class TestNSSDBTrust(BaseTest): + mock_certdb.return_value = mock_CertDB(trust) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertNSSTrust(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 4 +@@ -74,10 +73,9 @@ class TestNSSDBTrust(BaseTest): + mock_certdb.return_value = mock_CertDB(trust) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertNSSTrust(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # The check reports success for those that it found and are correct and +@@ -114,10 +112,9 @@ class TestNSSDBTrust(BaseTest): + mock_certdb.return_value = mock_CertDB(trust) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertNSSTrust(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + result = self.results.results[1] +@@ -149,10 +146,9 @@ class TestNSSDBTrust(BaseTest): + mock_cainstance.return_value = CAInstance(False) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertNSSTrust(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 0 +diff --git a/tests/test_ipa_nssvalidation.py b/tests/test_ipa_nssvalidation.py +index 1e567d8..08135e7 100644 +--- a/tests/test_ipa_nssvalidation.py ++++ b/tests/test_ipa_nssvalidation.py +@@ -38,10 +38,9 @@ class TestNSSValidation(BaseTest): + mock_cainstance.return_value = CAInstance() + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPANSSChainValidation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -68,10 +67,9 @@ class TestNSSValidation(BaseTest): + mock_cainstance.return_value = CAInstance() + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPANSSChainValidation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -97,10 +95,9 @@ class TestNSSValidation(BaseTest): + mock_cainstance.return_value = CAInstance(False) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPANSSChainValidation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +diff --git a/tests/test_ipa_opensslvalidation.py b/tests/test_ipa_opensslvalidation.py +index 0d334cd..e73138c 100644 +--- a/tests/test_ipa_opensslvalidation.py ++++ b/tests/test_ipa_opensslvalidation.py +@@ -30,10 +30,9 @@ class TestOpenSSLValidation(BaseTest): + mock_run.side_effect = run + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPAOpenSSLChainValidation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -59,10 +58,9 @@ class TestOpenSSLValidation(BaseTest): + mock_run.side_effect = run + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPAOpenSSLChainValidation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +diff --git a/tests/test_ipa_revocation.py b/tests/test_ipa_revocation.py +index 39cf3e7..c6423c0 100644 +--- a/tests/test_ipa_revocation.py ++++ b/tests/test_ipa_revocation.py +@@ -54,10 +54,9 @@ class TestRevocation(BaseTest): + set_requests() + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertRevocation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -84,10 +83,9 @@ class TestRevocation(BaseTest): + set_requests() + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertRevocation(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +diff --git a/tests/test_ipa_roles.py b/tests/test_ipa_roles.py +index 453db06..21c0069 100644 +--- a/tests/test_ipa_roles.py ++++ b/tests/test_ipa_roles.py +@@ -18,10 +18,9 @@ class TestCRLManagerRole(BaseTest): + def test_not_crlmanager(self, mock_ca): + mock_ca.return_value = CAInstance(crlgen=False) + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACRLManagerCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -36,10 +35,9 @@ class TestCRLManagerRole(BaseTest): + def test_crlmanager(self, mock_ca): + mock_ca.return_value = CAInstance() + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACRLManagerCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -54,7 +52,7 @@ class TestCRLManagerRole(BaseTest): + class TestRenewalMaster(BaseTest): + def test_renewal_master_not_set(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPARenewalMasterCheck(registry) + + m_api.Command.config_show.side_effect = [{ +@@ -62,7 +60,6 @@ class TestRenewalMaster(BaseTest): + } + }] + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -75,7 +72,7 @@ class TestRenewalMaster(BaseTest): + + def test_not_renewal_master(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPARenewalMasterCheck(registry) + + m_api.Command.config_show.side_effect = [{ +@@ -84,7 +81,6 @@ class TestRenewalMaster(BaseTest): + } + }] + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -97,7 +93,7 @@ class TestRenewalMaster(BaseTest): + + def test_is_renewal_master(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPARenewalMasterCheck(registry) + + m_api.Command.config_show.side_effect = [{ +@@ -106,7 +102,6 @@ class TestRenewalMaster(BaseTest): + } + }] + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +diff --git a/tests/test_ipa_topology.py b/tests/test_ipa_topology.py +index a4ff6d9..11931d9 100644 +--- a/tests/test_ipa_topology.py ++++ b/tests/test_ipa_topology.py +@@ -28,10 +28,9 @@ class TestTopology(BaseTest): + m_api.Command.ca_is_enabled.return_value = {'result': True} + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPATopologyDomainCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -71,10 +70,9 @@ class TestTopology(BaseTest): + m_api.Command.ca_is_enabled.return_value = {'result': True} + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPATopologyDomainCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 3 +@@ -135,10 +133,9 @@ class TestTopology(BaseTest): + m_api.Command.ca_is_enabled.return_value = {'result': True} + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPATopologyDomainCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 3 +@@ -193,10 +190,9 @@ class TestTopology(BaseTest): + m_api.Command.ca_is_enabled.return_value = {'result': True} + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPATopologyDomainCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +diff --git a/tests/test_ipa_tracking.py b/tests/test_ipa_tracking.py +index c40d8f4..4b982d0 100644 +--- a/tests/test_ipa_tracking.py ++++ b/tests/test_ipa_tracking.py +@@ -5,7 +5,7 @@ + from util import capture_results + from base import BaseTest + +-from ipahealthcheck.core import constants ++from ipahealthcheck.core import constants, config + from ipahealthcheck.ipa.plugin import registry + from ipahealthcheck.ipa.certs import IPACertTracking + from unittest.mock import Mock +@@ -27,7 +27,7 @@ class TestTracking(BaseTest): + set_requests() + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertTracking(registry) + + self.results = capture_results(f) +@@ -39,7 +39,7 @@ class TestTracking(BaseTest): + set_requests(remove=0) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertTracking(registry) + + self.results = capture_results(f) +@@ -71,7 +71,7 @@ class TestTracking(BaseTest): + set_requests(add=unknown) + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = IPACertTracking(registry) + + self.results = capture_results(f) +diff --git a/tests/test_ipa_trust.py b/tests/test_ipa_trust.py +index 01fdf3c..cf2281c 100644 +--- a/tests/test_ipa_trust.py ++++ b/tests/test_ipa_trust.py +@@ -109,11 +109,10 @@ class SSSDConfig(): + class TestTrustAgent(BaseTest): + def test_no_trust_agent(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = False + f = IPATrustAgentCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -124,11 +123,10 @@ class TestTrustAgent(BaseTest): + mock_sssd.return_value = SSSDConfig(return_domains=True, + return_ipa_server_mode=True) + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustAgentCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -143,11 +141,10 @@ class TestTrustAgent(BaseTest): + mock_sssd.return_value = SSSDConfig(return_domains=True, + return_ipa_server_mode=False) + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustAgentCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -164,11 +161,10 @@ class TestTrustAgent(BaseTest): + mock_sssd.return_value = SSSDConfig(return_domains=True, + return_ipa_server_mode=None) + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustAgentCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -184,11 +180,10 @@ class TestTrustAgent(BaseTest): + class TestTrustDomains(BaseTest): + def test_no_trust_agent(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = False + f = IPATrustDomainsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -202,11 +197,10 @@ class TestTrustDomains(BaseTest): + mock_run.return_value = run_result + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustDomainsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -230,11 +224,10 @@ class TestTrustDomains(BaseTest): + mock_trust.side_effect = errors.NotFound(reason='bad') + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustDomainsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # There are more than one result I just care about this particular +@@ -279,11 +272,10 @@ class TestTrustDomains(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustDomainsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 3 +@@ -344,11 +336,10 @@ class TestTrustDomains(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustDomainsCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -374,12 +365,11 @@ class TestIPADomain(BaseTest): + def test_ipa_domain_ok(self, mock_sssd): + mock_sssd.return_value = SSSDConfig(provider='ipa') + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + # being a trust agent isn't mandatory, test without + registry.trust_agent = False + f = IPADomainCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + print(self.results.results) +@@ -394,11 +384,10 @@ class TestIPADomain(BaseTest): + def test_ipa_domain_ad(self, mock_sssd): + mock_sssd.return_value = SSSDConfig(provider='ad') + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPADomainCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 4 +@@ -413,11 +402,10 @@ class TestIPADomain(BaseTest): + class TestTrustCatalog(BaseTest): + def test_no_trust_agent(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = False + f = IPATrustCatalogCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -464,11 +452,10 @@ class TestTrustCatalog(BaseTest): + }] + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustCatalogCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 6 +@@ -524,11 +511,10 @@ class Testsidgen(BaseTest): + + def test_no_trust_agent(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = False + f = IPAsidgenpluginCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -544,12 +530,11 @@ class Testsidgen(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPAsidgenpluginCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -576,12 +561,11 @@ class Testsidgen(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPAsidgenpluginCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 2 +@@ -607,11 +591,10 @@ class TestTrustAgentMember(BaseTest): + + def test_no_trust_agent(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = False + f = IPATrustAgentMemberCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -632,12 +615,11 @@ class TestTrustAgentMember(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustAgentMemberCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -660,12 +642,11 @@ class TestTrustAgentMember(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_agent = True + f = IPATrustAgentMemberCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -685,11 +666,10 @@ class TestControllerPrincipal(BaseTest): + + def test_not_trust_controller(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = False + f = IPATrustControllerPrincipalCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -711,12 +691,11 @@ class TestControllerPrincipal(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerPrincipalCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -740,12 +719,11 @@ class TestControllerPrincipal(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerPrincipalCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -765,11 +743,10 @@ class TestControllerService(BaseTest): + + def test_not_trust_controller(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = False + f = IPATrustControllerServiceCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -786,12 +763,11 @@ class TestControllerService(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerServiceCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -813,12 +789,11 @@ class TestControllerService(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerServiceCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -837,11 +812,10 @@ class TestControllerGroupSID(BaseTest): + + def test_not_trust_controller(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = False + f = IPATrustControllerGroupSIDCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -859,12 +833,11 @@ class TestControllerGroupSID(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerGroupSIDCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -888,12 +861,11 @@ class TestControllerGroupSID(BaseTest): + ldapentry[attr] = values + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerGroupSIDCheck(registry) + + f.conn = mock_ldap(ldapentry) +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -914,11 +886,10 @@ class TestControllerConf(BaseTest): + + def test_not_trust_controller(self): + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = False + f = IPATrustControllerConfCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + # Zero because the call was skipped altogether +@@ -934,11 +905,10 @@ class TestControllerConf(BaseTest): + mock_run.return_value = run_result + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = True + f = IPATrustControllerConfCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + assert len(self.results) == 1 +@@ -954,11 +924,10 @@ class TestPackageCheck(BaseTest): + def test_agent_with_package(self): + # Note that this test assumes the import is installed + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = False + registry.trust_agent = True + f = IPATrustPackageCheck(registry) +- f.config = config.Config() + self.results = capture_results(f) + assert len(self.results) == 1 + result = self.results.results[0] +@@ -969,14 +938,13 @@ class TestPackageCheck(BaseTest): + def test_agent_without_package(self): + # Note that this test assumes the import is installed + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + registry.trust_controller = False + registry.trust_agent = True + # Hose up the module so the import fails + save = sys.modules['ipaserver.install'] + sys.modules['ipaserver.install'] = 'foo' + f = IPATrustPackageCheck(registry) +- f.config = config.Config() + self.results = capture_results(f) + assert len(self.results) == 1 + result = self.results.results[0] +diff --git a/tests/test_meta_services.py b/tests/test_meta_services.py +index 6fbea13..b7a71a4 100644 +--- a/tests/test_meta_services.py ++++ b/tests/test_meta_services.py +@@ -7,6 +7,7 @@ from base import BaseTest + + from ipahealthcheck.ipa.plugin import registry + from ipahealthcheck.meta.services import httpd ++from ipahealthcheck.core import config + + + class TestServices(BaseTest): +@@ -19,7 +20,7 @@ class TestServices(BaseTest): + running. + """ + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = httpd(registry) + + self.results = capture_results(f) +diff --git a/tests/test_system_filesystemspace.py b/tests/test_system_filesystemspace.py +index 3317f62..873b977 100644 +--- a/tests/test_system_filesystemspace.py ++++ b/tests/test_system_filesystemspace.py +@@ -28,10 +28,9 @@ class TestFileSystemNotEnoughFreeSpace(BaseTest): + def test_filesystem_near_enospc(self): + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = FileSystemSpaceCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + expected_results = 10 if in_container() else 12 +@@ -64,10 +63,9 @@ class TestFileSystemNotEnoughFreeSpacePercentage(BaseTest): + def test_filesystem_risking_fragmentation(self): + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = FileSystemSpaceCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + expected_results = 10 if in_container() else 12 +@@ -100,10 +98,9 @@ class TestFileSystemEnoughFreeSpace(BaseTest): + def test_filesystem_with_enough_space(self): + + framework = object() +- registry.initialize(framework) ++ registry.initialize(framework, config.Config) + f = FileSystemSpaceCheck(registry) + +- f.config = config.Config() + self.results = capture_results(f) + + expected_results = 10 if in_container() else 12 +-- +2.21.0 + diff --git a/SOURCES/ipahealthcheck.conf b/SOURCES/ipahealthcheck.conf new file mode 100644 index 0000000..ab109a1 --- /dev/null +++ b/SOURCES/ipahealthcheck.conf @@ -0,0 +1 @@ +[default] diff --git a/SPECS/ipa-healthcheck.spec b/SPECS/ipa-healthcheck.spec new file mode 100644 index 0000000..c127f2a --- /dev/null +++ b/SPECS/ipa-healthcheck.spec @@ -0,0 +1,173 @@ +%global project freeipa +%global shortname healthcheck +%global longname ipa%{shortname} +%global debug_package %{nil} +%global python3dir %{_builddir}/python3-%{name}-%{version}-%{release} +%{!?python3_sitelib: %global python3_sitelib %(%{__python3} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} + + +Name: ipa-healthcheck +Version: 0.4 +Release: 4%{?dist} +Summary: Health check tool for IdM +BuildArch: noarch +License: GPLv3 +URL: https://github.com/%{project}/freeipa-healthcheck +Source0: https://github.com/%{project}/%{name}/archive/%{version}.tar.gz#/%{project}-%{shortname}-%{version}.tar.gz +Source1: %{longname}.conf + +Patch0001: 0001-Remove-requirement-for-pytest-runner-since-PyPI-isn-.patch +Patch0002: 0002-Move-main-to-run_healthcheck-for-abstraction-purpose.patch +Patch0003: 0003-Abstract-ServiceCheck-to-not-be-IPA-specific.patch +Patch0004: 0004-Move-the-abstracted-plugin-runner-code-into-a-separa.patch +Patch0005: 0005-Convert-running-healthchecks-into-a-class-and-add-pr.patch +Patch0006: 0006-Move-config-object-from-plugins-to-registry.patch + +Requires: ipa-server +Requires: python3-ipalib +Requires: python3-ipaserver +# cronie-anacron provides anacron +Requires: anacron +Requires: logrotate +Requires(post): systemd-units +Requires: %{name}-core = %{version}-%{release} +BuildRequires: python3-devel +BuildRequires: systemd-devel +%{?systemd_requires} + + +%description +The FreeIPA health check tool provides a set of checks to +proactively detect defects in a FreeIPA cluster. + +%package -n %{name}-core +Summary: Core plugin system for healthcheck +# to allow package downgrades where this subpackage doesn't exist +Obsoletes: %{name} < 0.4 + +%description -n %{name}-core +Core files + + +%prep +%autosetup -p1 -n %{project}-%{shortname}-%{version} + + +%build +%py3_build + + +%install +%py3_install + +mkdir -p %{buildroot}%{_sysconfdir}/%{longname} +install -m644 %{SOURCE1} %{buildroot}%{_sysconfdir}/%{longname} + +mkdir -p %{buildroot}/%{_unitdir} +install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/systemd/ipa-%{shortname}.service %{buildroot}%{_unitdir} +install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/systemd/ipa-%{shortname}.timer %{buildroot}%{_unitdir} + +mkdir -p %{buildroot}/%{_libexecdir}/ipa +install -p -m755 %{_builddir}/%{project}-%{shortname}-%{version}/systemd/ipa-%{shortname}.sh %{buildroot}%{_libexecdir}/ipa/ + +mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d +install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/logrotate/%{longname} %{buildroot}%{_sysconfdir}/logrotate.d + +mkdir -p %{buildroot}/%{_localstatedir}/log/ipa/%{shortname} + +mkdir -p %{buildroot}/%{_mandir}/man1 +mkdir -p %{buildroot}/%{_mandir}/man5 +install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man1/ipa-%{shortname}.1 %{buildroot}%{_mandir}/man1/ +install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man5/%{longname}.conf.5 %{buildroot}%{_mandir}/man5/ + +(cd %{buildroot}/%{python3_sitelib}/ipahealthcheck && find . -type f | \ + grep -v '^./core' | \ + grep -v 'opt-1' | \ + sed -e 's,\.py.*$,.*,g' | sort -u | \ + sed -e 's,\./,%%{python3_sitelib}/ipahealthcheck/,g' ) >healthcheck.list + +%post +%systemd_post ipa-%{shortname}.service + + +%preun +%systemd_preun ipa-%{shortname}.service + + +%postun +%systemd_postun_with_restart ipa-%{shortname}.service + + +%files -f healthcheck.list +%{!?_licensedir:%global license %%doc} +%license COPYING +%doc README.md +%{_bindir}/ipa-%{shortname} +%dir %{_sysconfdir}/%{longname} +%dir %{_localstatedir}/log/ipa/%{shortname} +%config(noreplace) %{_sysconfdir}/%{longname}/%{longname}.conf +%config(noreplace) %{_sysconfdir}/logrotate.d/%{longname} +%{python3_sitelib}/%{longname}-%{version}-*.egg-info/ +%{python3_sitelib}/%{longname}-%{version}-*-nspkg.pth +%{_unitdir}/* +%{_libexecdir}/* +%{_mandir}/man1/* +%{_mandir}/man5/* + +%files -n %{name}-core +%{!?_licensedir:%global license %%doc} +%license COPYING +%doc README.md +%{python3_sitelib}/%{longname}/core/ + + +%changelog +* Thu Jan 16 2020 Rob Crittenden - 0.4-4 +- Allow plugins to read contents from config during initialization (#1784037) + +* Thu Dec 5 2019 Rob Crittenden - 0.4-3 +- Add Obsoletes to core subpackage (#1780121) + +* Mon Dec 2 2019 Rob Crittenden - 0.4-2 +- Abstract processing so core package is standalone (#1771710) + +* Mon Dec 2 2019 Rob Crittenden - 0.4-1 +- Rebase to upstream 0.4 (#1770346) +- Create subpackage to split out core processing (#1771710) +- Correct URL (#1773512) +- Errors not translated to strings (#1752849) +- JSON output not indented by default (#1729043) +- Add dependencies to checks to avoid false-positives (#1727900) +- Verify expected DNS records (#1695125) + +* Mon Aug 12 2019 Rob Crittenden - 0.3-4 +- Lookup AD user by SID and not by hardcoded username (#1739500) + +* Thu Aug 8 2019 Rob Crittenden - 0.3-3 +- The AD trust agent and controller are not being initialized (#1738314) + +* Mon Aug 5 2019 Rob Crittenden - 0.3-2 +- Change DNA plugin to return WARNING if no range is set (#1737492) + +* Mon Jul 29 2019 François Cami - 0.3-1 +- Update to upstream 0.3 (#1701351) +- Add logrotate configs + depend on anacron and logrotate (#1729207) + +* Thu Jul 11 2019 François Cami - 0.2-4 +- Fix ipa-healthcheck.sh installation path (rhbz#1729188) +- Create and own log directory (rhbz#1729188) + +* Tue Apr 30 2019 François Cami - 0.2-3 +- Add python3-lib389 to BRs + +* Tue Apr 30 2019 François Cami - 0.2-2 +- Fix changelog + +* Thu Apr 25 2019 Rob Crittenden - 0.2-1 +- Update to upstream 0.2 + +* Thu Apr 4 2019 François Cami - 0.1-2 +- Explicitly list dependencies + +* Tue Apr 2 2019 François Cami - 0.1-1 +- Initial package import