import ipa-healthcheck-0.7-3.module+el8.4.0+9007+5084bdd8
This commit is contained in:
parent
4a16c583ed
commit
fdeb5dda89
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
SOURCES/freeipa-healthcheck-0.4.tar.gz
|
||||
SOURCES/freeipa-healthcheck-0.7.tar.gz
|
||||
|
||||
@ -1 +1 @@
|
||||
2e61604c36f6f793612b1e3bb3f5e78df1cb58ac SOURCES/freeipa-healthcheck-0.4.tar.gz
|
||||
93e4d1554a11ba94947a816b008fb1092abbf591 SOURCES/freeipa-healthcheck-0.7.tar.gz
|
||||
|
||||
@ -1,106 +0,0 @@
|
||||
From eb87d277442dd1a1076ba9ae74c18ace8cc7dda8 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
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
|
||||
|
||||
630
SOURCES/0002-Remove-ipaclustercheck.patch
Normal file
630
SOURCES/0002-Remove-ipaclustercheck.patch
Normal file
@ -0,0 +1,630 @@
|
||||
From ea9e00e47307b4ab81cc31c37796dd7b6a4c8785 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Thu, 29 Oct 2020 11:49:22 -0400
|
||||
Subject: [PATCH] Remove ipaclustercheck
|
||||
|
||||
Not shipping it from upstream into Fedora just yet
|
||||
---
|
||||
setup.py | 12 +-
|
||||
src/ipaclustercheck/__init__.py | 5 -
|
||||
src/ipaclustercheck/core/__init__.py | 0
|
||||
src/ipaclustercheck/core/main.py | 30 -----
|
||||
src/ipaclustercheck/core/output.py | 67 ------------
|
||||
src/ipaclustercheck/ipa/__init__.py | 0
|
||||
src/ipaclustercheck/ipa/crlmanager.py | 36 ------
|
||||
src/ipaclustercheck/ipa/plugin.py | 114 -------------------
|
||||
src/ipaclustercheck/ipa/ruv.py | 151 --------------------------
|
||||
tests/test_cluster_ruv.py | 106 ------------------
|
||||
10 files changed, 1 insertion(+), 520 deletions(-)
|
||||
delete mode 100644 src/ipaclustercheck/__init__.py
|
||||
delete mode 100644 src/ipaclustercheck/core/__init__.py
|
||||
delete mode 100644 src/ipaclustercheck/core/main.py
|
||||
delete mode 100644 src/ipaclustercheck/core/output.py
|
||||
delete mode 100644 src/ipaclustercheck/ipa/__init__.py
|
||||
delete mode 100644 src/ipaclustercheck/ipa/crlmanager.py
|
||||
delete mode 100644 src/ipaclustercheck/ipa/plugin.py
|
||||
delete mode 100644 src/ipaclustercheck/ipa/ruv.py
|
||||
delete mode 100644 tests/test_cluster_ruv.py
|
||||
|
||||
diff --git a/setup.py b/setup.py
|
||||
index 2b519fc..c25b2d7 100644
|
||||
--- a/setup.py
|
||||
+++ b/setup.py
|
||||
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
|
||||
setup(
|
||||
name='ipahealthcheck',
|
||||
version='0.7',
|
||||
- namespace_packages=['ipahealthcheck', 'ipaclustercheck'],
|
||||
+ namespace_packages=['ipahealthcheck'],
|
||||
package_dir={'': 'src'},
|
||||
# packages=find_packages(where='src'),
|
||||
packages=[
|
||||
@@ -14,14 +14,11 @@ setup(
|
||||
'ipahealthcheck.ipa',
|
||||
'ipahealthcheck.meta',
|
||||
'ipahealthcheck.system',
|
||||
- 'ipaclustercheck.core',
|
||||
- 'ipaclustercheck.ipa',
|
||||
],
|
||||
entry_points={
|
||||
# creates bin/ipahealthcheck
|
||||
'console_scripts': [
|
||||
'ipa-healthcheck = ipahealthcheck.core.main:main',
|
||||
- 'ipa-clustercheck = ipaclustercheck.core.main:main',
|
||||
],
|
||||
# subsystem registries
|
||||
'ipahealthcheck.registry': [
|
||||
@@ -69,13 +66,6 @@ setup(
|
||||
'ipahealthcheck.system': [
|
||||
'filesystemspace = ipahealthcheck.system.filesystemspace',
|
||||
],
|
||||
- 'ipaclustercheck.registry': [
|
||||
- 'ipaclustercheck.ipa = ipaclustercheck.ipa.plugin:registry',
|
||||
- ],
|
||||
- 'ipaclustercheck.ipa': [
|
||||
- 'crl = ipaclustercheck.ipa.crlmanager',
|
||||
- 'ruv = ipaclustercheck.ipa.ruv',
|
||||
- ],
|
||||
},
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3.6',
|
||||
diff --git a/src/ipaclustercheck/__init__.py b/src/ipaclustercheck/__init__.py
|
||||
deleted file mode 100644
|
||||
index 6c91ef7..0000000
|
||||
--- a/src/ipaclustercheck/__init__.py
|
||||
+++ /dev/null
|
||||
@@ -1,5 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-__import__('pkg_resources').declare_namespace(__name__)
|
||||
diff --git a/src/ipaclustercheck/core/__init__.py b/src/ipaclustercheck/core/__init__.py
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/src/ipaclustercheck/core/main.py b/src/ipaclustercheck/core/main.py
|
||||
deleted file mode 100644
|
||||
index 45fda9f..0000000
|
||||
--- a/src/ipaclustercheck/core/main.py
|
||||
+++ /dev/null
|
||||
@@ -1,30 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-import sys
|
||||
-
|
||||
-from ipaclustercheck.core.output import output_registry
|
||||
-from ipahealthcheck.core.core import RunChecks
|
||||
-
|
||||
-
|
||||
-class ClusterChecks(RunChecks):
|
||||
-
|
||||
- def add_options(self):
|
||||
- parser = self.parser
|
||||
- parser.add_argument('--directory', dest='dir',
|
||||
- help='Directory holding healthcheck logs')
|
||||
-
|
||||
- def validate_options(self):
|
||||
- super(ClusterChecks, self).validate_options()
|
||||
-
|
||||
- if self.options.dir is None:
|
||||
- print("--directory containing logs to check is required")
|
||||
- return 1
|
||||
-
|
||||
-
|
||||
-def main():
|
||||
- clusterchecks = ClusterChecks(['ipaclustercheck.registry'],
|
||||
- '/etc/ipa/clustercheck.conf',
|
||||
- output_registry, 'ansible')
|
||||
- sys.exit(clusterchecks.run_healthcheck())
|
||||
diff --git a/src/ipaclustercheck/core/output.py b/src/ipaclustercheck/core/output.py
|
||||
deleted file mode 100644
|
||||
index 2f02b52..0000000
|
||||
--- a/src/ipaclustercheck/core/output.py
|
||||
+++ /dev/null
|
||||
@@ -1,67 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-import json
|
||||
-import sys
|
||||
-from ipahealthcheck.core.output import OutputRegistry, Output
|
||||
-
|
||||
-
|
||||
-output_registry = OutputRegistry()
|
||||
-
|
||||
-class ClusterOutput(Output):
|
||||
- """Base class for writing/display output of cluster results
|
||||
-
|
||||
- severity doesn't apply in this case so exclude those.
|
||||
- """
|
||||
- def __init__(self, options):
|
||||
- self.filename = options.outfile
|
||||
-
|
||||
- def strip_output(self, results):
|
||||
- """Nothing to strip out"""
|
||||
- return [result for result in results.output()]
|
||||
-
|
||||
-
|
||||
-@output_registry
|
||||
-class Ansible(ClusterOutput):
|
||||
- """Output information JSON format for consumption by Ansible
|
||||
-
|
||||
- Required keywords in a Result:
|
||||
- name - unique identifier for the return value
|
||||
-
|
||||
- One of these is required:
|
||||
- value - the return value. Type? I dunno yet
|
||||
- error - if an error was returned
|
||||
- """
|
||||
-
|
||||
- options = (
|
||||
- ('--indent', dict(dest='indent', type=int, default=2,
|
||||
- help='Indention level of JSON output')),
|
||||
- )
|
||||
-
|
||||
- def __init__(self, options):
|
||||
- super(Ansible, self).__init__(options)
|
||||
- self.indent = options.indent
|
||||
-
|
||||
- def generate(self, data):
|
||||
- output = []
|
||||
- for line in data:
|
||||
- kw = line.get('kw')
|
||||
- result = line.get('result')
|
||||
- name = kw.get('name')
|
||||
- value = kw.get('value')
|
||||
- error = kw.get('error')
|
||||
-
|
||||
- if value and error:
|
||||
- value = '%s: %s' % (error, value)
|
||||
- elif error:
|
||||
- value = error
|
||||
-
|
||||
- rval = {'%s' % name: value}
|
||||
- output.append(rval)
|
||||
-
|
||||
- output = json.dumps(output, indent=self.indent)
|
||||
- if self.filename is None:
|
||||
- output += '\n'
|
||||
-
|
||||
- return output
|
||||
diff --git a/src/ipaclustercheck/ipa/__init__.py b/src/ipaclustercheck/ipa/__init__.py
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/src/ipaclustercheck/ipa/crlmanager.py b/src/ipaclustercheck/ipa/crlmanager.py
|
||||
deleted file mode 100644
|
||||
index 6806d74..0000000
|
||||
--- a/src/ipaclustercheck/ipa/crlmanager.py
|
||||
+++ /dev/null
|
||||
@@ -1,36 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-from ipaclustercheck.ipa.plugin import ClusterPlugin, registry, find_checks
|
||||
-from ipahealthcheck.core.plugin import Result, duration
|
||||
-from ipahealthcheck.core import constants
|
||||
-
|
||||
-
|
||||
-@registry
|
||||
-class ClusterCRLManagerCheck(ClusterPlugin):
|
||||
-
|
||||
- @duration
|
||||
- def check(self):
|
||||
- data = self.registry.json
|
||||
- crlmanagers = []
|
||||
-
|
||||
- for fqdn in data.keys():
|
||||
- output = find_checks(data[fqdn], 'ipahealthcheck.ipa.roles',
|
||||
- 'IPACRLManagerCheck')
|
||||
- enabled = output[0].get('kw').get('crlgen_enabled')
|
||||
- if enabled:
|
||||
- crlmanagers.append(fqdn)
|
||||
- if len(crlmanagers) == 0:
|
||||
- yield Result(self, constants.ERROR,
|
||||
- name='crlmanager',
|
||||
- error='No CRL Manager defined')
|
||||
- elif len(crlmanagers) == 1:
|
||||
- yield Result(self, constants.SUCCESS,
|
||||
- name='crlmanager',
|
||||
- value=crlmanagers[0])
|
||||
- else:
|
||||
- yield Result(self, constants.ERROR,
|
||||
- name='crlmanager',
|
||||
- value=','.join(crlmanagers),
|
||||
- error='Multiple CRL Managers defined')
|
||||
diff --git a/src/ipaclustercheck/ipa/plugin.py b/src/ipaclustercheck/ipa/plugin.py
|
||||
deleted file mode 100644
|
||||
index d9b6063..0000000
|
||||
--- a/src/ipaclustercheck/ipa/plugin.py
|
||||
+++ /dev/null
|
||||
@@ -1,114 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-from copy import deepcopy
|
||||
-import json
|
||||
-import logging
|
||||
-from os import listdir
|
||||
-from os.path import isfile, join
|
||||
-
|
||||
-from ipahealthcheck.core.plugin import Plugin, Registry
|
||||
-from ipalib import api
|
||||
-
|
||||
-
|
||||
-logger = logging.getLogger()
|
||||
-
|
||||
-def find_checks(data, source, check):
|
||||
- """Look through the dict for a matching source and check.
|
||||
-
|
||||
- data: dict of source and check output
|
||||
- source: name of source to find
|
||||
- check: name of check to find
|
||||
-
|
||||
- Returns list of contents of source + check or empty list
|
||||
- """
|
||||
- rval = []
|
||||
- for d in data:
|
||||
- if d.get('source') == source and d.get('check') == check:
|
||||
- rval.append(d)
|
||||
-
|
||||
- return rval
|
||||
-
|
||||
-
|
||||
-def get_masters(data):
|
||||
- """
|
||||
- Return the list of known masters
|
||||
-
|
||||
- This is determined from the list of loaded healthcheck logs. It
|
||||
- is possible that mixed versions are used so some may not be
|
||||
- reporting the full list of masters, so check them all, and raise
|
||||
- an exception if the list cannot be determined.
|
||||
- """
|
||||
- test_masters = list(data)
|
||||
- masters = None
|
||||
- for master in test_masters:
|
||||
- output = find_checks(data[master], 'ipahealthcheck.ipa.meta',
|
||||
- 'IPAMetaCheck')
|
||||
- if len(output) == 0:
|
||||
- raise ValueError('Unable to determine full list of masters. '
|
||||
- 'ipahealthcheck.ipa.meta:IPAMetaCheck not '
|
||||
- 'found.')
|
||||
-
|
||||
- masters = output[0].get('kw').get('masters')
|
||||
- if masters:
|
||||
- return masters
|
||||
-
|
||||
- raise ValueError('Unable to determine full list of masters. '
|
||||
- 'None of ipahealthcheck.ipa.meta:IPAMetaCheck '
|
||||
- 'contain masters.')
|
||||
-
|
||||
-
|
||||
-class ClusterPlugin(Plugin):
|
||||
- def __init__(self, registry):
|
||||
- super(ClusterPlugin, self).__init__(registry)
|
||||
-
|
||||
-
|
||||
-class ClusterRegistry(Registry):
|
||||
- def initialize(self, framework, config, options):
|
||||
- super(ClusterRegistry, self).initialize(framework, config, options)
|
||||
-
|
||||
- self.json = {}
|
||||
-
|
||||
- self.load_files(options.dir)
|
||||
-
|
||||
- if not api.isdone('finalize'):
|
||||
- if not api.isdone('bootstrap'):
|
||||
- api.bootstrap(in_server=True,
|
||||
- context='ipahealthcheck',
|
||||
- log=None)
|
||||
- if not api.isdone('finalize'):
|
||||
- api.finalize()
|
||||
-
|
||||
- def load_files(self, dir):
|
||||
- if self.json:
|
||||
- return
|
||||
-
|
||||
- files = [f for f in listdir(dir) if isfile(join(dir, f))]
|
||||
- for file in files:
|
||||
- fname = join(dir, file)
|
||||
- logger.debug("Reading %s", fname)
|
||||
- try:
|
||||
- with open(fname, 'r') as fd:
|
||||
- data = fd.read()
|
||||
- except Exception as e:
|
||||
- logger.error("Unable to read %s: %s", fname, e)
|
||||
- continue
|
||||
-
|
||||
- try:
|
||||
- data = json.loads(data)
|
||||
- except Exception as e:
|
||||
- logger.error("Unable to parse JSON in %s: %s", fname, e)
|
||||
- continue
|
||||
-
|
||||
- meta = find_checks(data, 'ipahealthcheck.meta.core',
|
||||
- 'MetaCheck')
|
||||
- if meta:
|
||||
- fqdn = meta[0].get('kw').get('fqdn')
|
||||
- self.json[fqdn] = deepcopy(data)
|
||||
- else:
|
||||
- logger.error("No fqdn defined in JSON in %s", fname)
|
||||
- continue
|
||||
-
|
||||
-
|
||||
-registry = ClusterRegistry()
|
||||
diff --git a/src/ipaclustercheck/ipa/ruv.py b/src/ipaclustercheck/ipa/ruv.py
|
||||
deleted file mode 100644
|
||||
index 0e51da9..0000000
|
||||
--- a/src/ipaclustercheck/ipa/ruv.py
|
||||
+++ /dev/null
|
||||
@@ -1,151 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-import logging
|
||||
-
|
||||
-from ipaclustercheck.ipa.plugin import (
|
||||
- ClusterPlugin,
|
||||
- registry,
|
||||
- find_checks,
|
||||
- get_masters
|
||||
-)
|
||||
-from ipahealthcheck.core.plugin import Result, duration
|
||||
-from ipahealthcheck.core import constants
|
||||
-from ipalib import api
|
||||
-from ipapython.dn import DN
|
||||
-
|
||||
-
|
||||
-logger = logging.getLogger()
|
||||
-
|
||||
-
|
||||
-@registry
|
||||
-class ClusterRUVCheck(ClusterPlugin):
|
||||
-
|
||||
- # TODO: confirm that all masters are represented, otherwise the
|
||||
- # trustworthiness of dangling RUV is mixed.
|
||||
- #
|
||||
- # gah, need to provide full list of all masters in a check.
|
||||
-
|
||||
- @duration
|
||||
- def check(self):
|
||||
- data = self.registry.json
|
||||
-
|
||||
- # Start with the list of masters from the file(s) collected
|
||||
- # and find a MetaCheck with a full list of masters. For
|
||||
- # backwards compatibility.
|
||||
- try:
|
||||
- masters = get_masters(data)
|
||||
- except ValueError as e:
|
||||
- yield Result(self, constants.ERROR,
|
||||
- name='dangling_ruv',
|
||||
- error=str(e))
|
||||
- return
|
||||
-
|
||||
- if len(data.keys()) < len(masters):
|
||||
- yield Result(self, constants.ERROR,
|
||||
- name='dangling_ruv',
|
||||
- error='Unable to determine list of RUVs, missing '
|
||||
- 'some masters: %s' %
|
||||
- ''.join(set(masters) - set(data.keys())))
|
||||
- return
|
||||
-
|
||||
- # collect the full set of known RUVs for each master
|
||||
- info = {}
|
||||
- for master in masters:
|
||||
- info[master] = {
|
||||
- 'ca': False, # does the host have ca configured?
|
||||
- 'ruvs': set(), # ruvs on the host
|
||||
- 'csruvs': set(), # csruvs on the host
|
||||
- 'clean_ruv': set(), # ruvs to be cleaned from the host
|
||||
- 'clean_csruv': set() # csruvs to be cleaned from the host
|
||||
- }
|
||||
-
|
||||
- for fqdn in data.keys():
|
||||
- outputs = find_checks(data[fqdn], 'ipahealthcheck.ds.ruv',
|
||||
- 'KnownRUVCheck')
|
||||
- for output in outputs:
|
||||
- if not 'suffix' in output.get('kw'):
|
||||
- continue
|
||||
- basedn = DN(output.get('kw').get('suffix'))
|
||||
-
|
||||
- ruvset = set()
|
||||
- ruvtmp = output.get('kw').get('ruvs')
|
||||
- for ruv in ruvtmp:
|
||||
- ruvset.add(tuple(ruv))
|
||||
-
|
||||
- if basedn == DN('o=ipaca'):
|
||||
- info[fqdn]['ca'] = True
|
||||
- info[fqdn]['csruvs'] = ruvset
|
||||
- elif basedn == api.env.basedn:
|
||||
- info[fqdn]['ruvs'] = ruvset
|
||||
- else:
|
||||
- yield Result(self, constants.WARNING,
|
||||
- name='dangling_ruv',
|
||||
- error='Unknown suffix found %s expected %s'
|
||||
- % (basedn, api.env.basedn))
|
||||
-
|
||||
- # Collect the nsDS5ReplicaID for each master
|
||||
- ruvs = set()
|
||||
- csruvs = set()
|
||||
- for fqdn in data.keys():
|
||||
- outputs = find_checks(data[fqdn], 'ipahealthcheck.ds.ruv',
|
||||
- 'RUVCheck')
|
||||
- for output in outputs:
|
||||
- if not 'key' in output.get('kw'):
|
||||
- continue
|
||||
- basedn = DN(output.get('kw').get('key'))
|
||||
- ruv = (fqdn, (output.get('kw').get('ruv')))
|
||||
- if basedn == DN('o=ipaca'):
|
||||
- csruvs.add(ruv)
|
||||
- elif basedn == api.env.basedn:
|
||||
- ruvs.add(ruv)
|
||||
- else:
|
||||
- yield Result(self, constants.WARNING,
|
||||
- name='dangling_ruv',
|
||||
- error='Unknown suffix found %s expected %s'
|
||||
- % (basedn, api.env.basedn))
|
||||
-
|
||||
- dangles = False
|
||||
- # get the dangling RUVs
|
||||
- for master_info in info.values():
|
||||
- for ruv in master_info['ruvs']:
|
||||
- if ruv not in ruvs:
|
||||
- master_info['clean_ruv'].add(ruv)
|
||||
- dangles = True
|
||||
-
|
||||
- # if ca is not configured, there will be no csruvs in master_info
|
||||
- for csruv in master_info['csruvs']:
|
||||
- if csruv not in csruvs:
|
||||
- master_info['clean_csruv'].add(csruv)
|
||||
- dangles = True
|
||||
-
|
||||
- clean_csruvs = set()
|
||||
- clean_ruvs = set()
|
||||
- if dangles:
|
||||
- for master_cn, master_info in info.items():
|
||||
- for ruv in master_info['clean_ruv']:
|
||||
- logger.debug('Dangling RUV id: {id}, hostname: {host}'
|
||||
- .format(id=ruv[1], host=ruv[0]))
|
||||
- clean_ruvs.add(ruv[1])
|
||||
- for csruv in master_info['clean_csruv']:
|
||||
- logger.debug('Dangling CS RUV id: {id}, hostname: {host}'
|
||||
- .format(id=csruv[1], host=csruv[0]))
|
||||
- clean_csruvs.add(csruv[1])
|
||||
-
|
||||
- if clean_ruvs:
|
||||
- yield Result(self, constants.ERROR,
|
||||
- name='dangling_ruv',
|
||||
- value=', '.join(clean_ruvs))
|
||||
- else:
|
||||
- yield Result(self, constants.SUCCESS,
|
||||
- name='dangling_ruv',
|
||||
- value='No dangling RUVs found')
|
||||
- if clean_csruvs:
|
||||
- yield Result(self, constants.ERROR,
|
||||
- name='dangling_csruv',
|
||||
- value=', '.join(clean_csruvs))
|
||||
- else:
|
||||
- yield Result(self, constants.SUCCESS,
|
||||
- name='dangling_csruv',
|
||||
- value='No dangling CS RUVs found')
|
||||
diff --git a/tests/test_cluster_ruv.py b/tests/test_cluster_ruv.py
|
||||
deleted file mode 100644
|
||||
index 7583c84..0000000
|
||||
--- a/tests/test_cluster_ruv.py
|
||||
+++ /dev/null
|
||||
@@ -1,106 +0,0 @@
|
||||
-#
|
||||
-# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
-#
|
||||
-
|
||||
-from base import BaseTest
|
||||
-from util import capture_results
|
||||
-
|
||||
-from ipahealthcheck.core import config
|
||||
-from ipaclustercheck.ipa.plugin import ClusterRegistry
|
||||
-from ipaclustercheck.ipa.ruv import ClusterRUVCheck
|
||||
-
|
||||
-import clusterdata
|
||||
-
|
||||
-
|
||||
-class RUVRegistry(ClusterRegistry):
|
||||
- def load_files(self, dir):
|
||||
- self.json = dir
|
||||
-
|
||||
-
|
||||
-class Options:
|
||||
- def __init__(self, data):
|
||||
- self.data = data
|
||||
-
|
||||
- @property
|
||||
- def dir(self):
|
||||
- return self.data
|
||||
-
|
||||
-
|
||||
-registry = RUVRegistry()
|
||||
-
|
||||
-
|
||||
-class TestClusterRUV(BaseTest):
|
||||
-
|
||||
- def test_no_ruvs(self):
|
||||
- """Single master test that has never created a replica
|
||||
-
|
||||
- This type of master will have no RUVs created at all.
|
||||
- """
|
||||
- framework = object()
|
||||
- registry.initialize(framework, config.Config,
|
||||
- Options(clusterdata.ONE_MASTER))
|
||||
- f = ClusterRUVCheck(registry)
|
||||
-
|
||||
- self.results = capture_results(f)
|
||||
-
|
||||
- assert len(self.results) == 2
|
||||
- result = self.results.results[0]
|
||||
- assert result.kw.get('name') == 'dangling_ruv'
|
||||
- assert result.kw.get('value') == 'No dangling RUVs found'
|
||||
- result = self.results.results[1]
|
||||
- assert result.kw.get('name') == 'dangling_csruv'
|
||||
- assert result.kw.get('value') == 'No dangling CS RUVs found'
|
||||
-
|
||||
- def test_six_ruvs_ok(self):
|
||||
- """Three master test with each having a CA, no dangling
|
||||
- """
|
||||
- framework = object()
|
||||
- registry.initialize(framework, config.Config,
|
||||
- Options(clusterdata.THREE_MASTERS_OK))
|
||||
- f = ClusterRUVCheck(registry)
|
||||
-
|
||||
- self.results = capture_results(f)
|
||||
-
|
||||
- assert len(self.results) == 2
|
||||
- result = self.results.results[0]
|
||||
- assert result.kw.get('name') == 'dangling_ruv'
|
||||
- assert result.kw.get('value') == 'No dangling RUVs found'
|
||||
- result = self.results.results[1]
|
||||
- assert result.kw.get('name') == 'dangling_csruv'
|
||||
- assert result.kw.get('value') == 'No dangling CS RUVs found'
|
||||
-
|
||||
- def test_six_ruvs_ipa_bad(self):
|
||||
- """Three master test with each having a CA, dangling IPA RUV
|
||||
- """
|
||||
- framework = object()
|
||||
- registry.initialize(framework, config.Config,
|
||||
- Options(clusterdata.THREE_MASTERS_BAD_IPA_RUV))
|
||||
- f = ClusterRUVCheck(registry)
|
||||
-
|
||||
- self.results = capture_results(f)
|
||||
-
|
||||
- assert len(self.results) == 2
|
||||
- result = self.results.results[0]
|
||||
- assert result.kw.get('name') == 'dangling_ruv'
|
||||
- assert result.kw.get('value') == '9'
|
||||
- result = self.results.results[1]
|
||||
- assert result.kw.get('name') == 'dangling_csruv'
|
||||
- assert result.kw.get('value') == 'No dangling CS RUVs found'
|
||||
-
|
||||
- def test_six_ruvs_cs_bad(self):
|
||||
- """Three master test with each having a CA, dangling CA RUV
|
||||
- """
|
||||
- framework = object()
|
||||
- registry.initialize(framework, config.Config,
|
||||
- Options(clusterdata.THREE_MASTERS_BAD_CS_RUV))
|
||||
- f = ClusterRUVCheck(registry)
|
||||
-
|
||||
- self.results = capture_results(f)
|
||||
-
|
||||
- assert len(self.results) == 2
|
||||
- result = self.results.results[0]
|
||||
- assert result.kw.get('name') == 'dangling_ruv'
|
||||
- assert result.kw.get('value') == 'No dangling RUVs found'
|
||||
- result = self.results.results[1]
|
||||
- assert result.kw.get('name') == 'dangling_csruv'
|
||||
- assert result.kw.get('value') == '9'
|
||||
--
|
||||
2.25.4
|
||||
|
||||
@ -1,187 +0,0 @@
|
||||
From c40e32b9d0ac49806b8336bd5065350574d29672 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
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
|
||||
|
||||
@ -0,0 +1,303 @@
|
||||
From 886153da7dd1ca1f5d37dd9c1e2141850b7177b2 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Tue, 17 Nov 2020 20:37:52 -0500
|
||||
Subject: [PATCH] Use trust-find and trustdomain-find to identify all AD trusts
|
||||
|
||||
Not all AD domains are visible to trust-find. For each trust
|
||||
iterate over trustdomain-find <domain> to find the complete
|
||||
list of domains.
|
||||
|
||||
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
src/ipahealthcheck/ipa/trust.py | 20 +++--
|
||||
tests/test_ipa_trust.py | 155 ++++++++++++++++++++------------
|
||||
2 files changed, 108 insertions(+), 67 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/ipa/trust.py b/src/ipahealthcheck/ipa/trust.py
|
||||
index 0abe5cd..00971c4 100644
|
||||
--- a/src/ipahealthcheck/ipa/trust.py
|
||||
+++ b/src/ipahealthcheck/ipa/trust.py
|
||||
@@ -42,16 +42,18 @@ def get_trust_domains():
|
||||
|
||||
Each entry is a dictionary representating an AD domain.
|
||||
"""
|
||||
- result = api.Command.trust_find()
|
||||
- results = result['result']
|
||||
trust_domains = []
|
||||
- for result in results:
|
||||
- if result.get('trusttype')[0] == 'Active Directory domain':
|
||||
- domain = dict()
|
||||
- domain['domain'] = result.get('cn')[0]
|
||||
- domain['domainsid'] = result.get('ipanttrusteddomainsid')[0]
|
||||
- domain['netbios'] = result.get('ipantflatname')[0]
|
||||
- trust_domains.append(domain)
|
||||
+ trusts = api.Command.trust_find(pkey_only=True, raw=True)
|
||||
+ for trust in trusts['result']:
|
||||
+ for cn in trust.get('cn'):
|
||||
+ trustdomains = api.Command.trustdomain_find(cn, raw=True)
|
||||
+ for trustdomain in trustdomains['result']:
|
||||
+ domain = dict()
|
||||
+ domain['domain'] = trustdomain.get('cn')[0]
|
||||
+ domain['domainsid'] = trustdomain.get(
|
||||
+ 'ipanttrusteddomainsid')[0]
|
||||
+ domain['netbios'] = trustdomain.get('ipantflatname')[0]
|
||||
+ trust_domains.append(domain)
|
||||
return trust_domains
|
||||
|
||||
|
||||
diff --git a/tests/test_ipa_trust.py b/tests/test_ipa_trust.py
|
||||
index 3c4b947..f3a9f27 100644
|
||||
--- a/tests/test_ipa_trust.py
|
||||
+++ b/tests/test_ipa_trust.py
|
||||
@@ -72,6 +72,56 @@ class mock_ldap_conn:
|
||||
return tuple()
|
||||
|
||||
|
||||
+#
|
||||
+# Construct a setup with two direct trusts and one sub domain
|
||||
+#
|
||||
+def trust_find():
|
||||
+ return [{
|
||||
+ 'result': [
|
||||
+ {
|
||||
+ 'cn': ['ad.example'],
|
||||
+ },
|
||||
+ {
|
||||
+ 'cn': ['child.example'],
|
||||
+ },
|
||||
+ ]
|
||||
+ }]
|
||||
+
|
||||
+
|
||||
+def trustdomain_find():
|
||||
+ return [
|
||||
+ {
|
||||
+ "result": [
|
||||
+ {
|
||||
+ "cn": ["ad.example"],
|
||||
+ "ipantflatname": ["ADROOT"],
|
||||
+ "ipanttrusteddomainsid": ["S-1-5-21-abc"],
|
||||
+ "ipanttrusttype": ["2"],
|
||||
+ "ipanttrustattributes": ["8"],
|
||||
+ },
|
||||
+ {
|
||||
+ "cn": ["child.ad.example"],
|
||||
+ "ipantflatname": ["CHILD.ADROOT"],
|
||||
+ "ipanttrusteddomainsid": ["S-1-5-22-def"],
|
||||
+ "ipanttrusttype": ["2"],
|
||||
+ "ipanttrustattributes": ["1"],
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ {
|
||||
+ "result": [
|
||||
+ {
|
||||
+ "cn": ["child.example"],
|
||||
+ "ipantflatname": ["CHILD"],
|
||||
+ "ipanttrusteddomainsid": ["S-1-5-21-ghi"],
|
||||
+ "ipanttrusttype": ["2"],
|
||||
+ "ipanttrustattributes": ["8"],
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ ]
|
||||
+
|
||||
+
|
||||
class SSSDDomain:
|
||||
def __init__(self, return_ipa_server_mode=True, provider='ipa'):
|
||||
self.return_ipa_server_mode = return_ipa_server_mode
|
||||
@@ -246,31 +296,17 @@ class TestTrustDomains(BaseTest):
|
||||
dlresult.returncode = 0
|
||||
dlresult.error_log = ''
|
||||
dlresult.output = 'implicit_files\nipa.example\nad.example\n' \
|
||||
- 'child.example\n'
|
||||
+ 'child.ad.example\nchild.example\n'
|
||||
olresult = namedtuple('run', ['returncode', 'error_log'])
|
||||
olresult.returncode = 0
|
||||
olresult.error_log = ''
|
||||
olresult.output = 'Online status: Online\n\n'
|
||||
|
||||
- mock_run.side_effect = [dlresult, olresult, olresult]
|
||||
+ mock_run.side_effect = [dlresult, olresult, olresult, olresult]
|
||||
|
||||
# get_trust_domains()
|
||||
- m_api.Command.trust_find.side_effect = [{
|
||||
- 'result': [
|
||||
- {
|
||||
- 'cn': ['ad.example'],
|
||||
- 'ipantflatname': ['ADROOT'],
|
||||
- 'ipanttrusteddomainsid': ['S-1-5-21-abc'],
|
||||
- 'trusttype': ['Active Directory domain'],
|
||||
- },
|
||||
- {
|
||||
- 'cn': ['child.example'],
|
||||
- 'ipantflatname': ['ADROOT'],
|
||||
- 'ipanttrusteddomainsid': ['S-1-5-21-def'],
|
||||
- 'trusttype': ['Active Directory domain'],
|
||||
- },
|
||||
- ]
|
||||
- }]
|
||||
+ m_api.Command.trust_find.side_effect = trust_find()
|
||||
+ m_api.Command.trustdomain_find.side_effect = trustdomain_find()
|
||||
|
||||
framework = object()
|
||||
registry.initialize(framework, config.Config)
|
||||
@@ -279,15 +315,17 @@ class TestTrustDomains(BaseTest):
|
||||
|
||||
self.results = capture_results(f)
|
||||
|
||||
- assert len(self.results) == 3
|
||||
+ assert len(self.results) == 4
|
||||
|
||||
result = self.results.results[0]
|
||||
assert result.result == constants.SUCCESS
|
||||
assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
assert result.check == 'IPATrustDomainsCheck'
|
||||
assert result.kw.get('key') == 'domain-list'
|
||||
- assert result.kw.get('trust_domains') == 'ad.example, child.example'
|
||||
- assert result.kw.get('sssd_domains') == 'ad.example, child.example'
|
||||
+ assert result.kw.get('trust_domains') == \
|
||||
+ 'ad.example, child.ad.example, child.example'
|
||||
+ assert result.kw.get('sssd_domains') == \
|
||||
+ 'ad.example, child.ad.example, child.example'
|
||||
|
||||
result = self.results.results[1]
|
||||
assert result.result == constants.SUCCESS
|
||||
@@ -301,6 +339,13 @@ class TestTrustDomains(BaseTest):
|
||||
assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
assert result.check == 'IPATrustDomainsCheck'
|
||||
assert result.kw.get('key') == 'domain-status'
|
||||
+ assert result.kw.get('domain') == 'child.ad.example'
|
||||
+
|
||||
+ result = self.results.results[3]
|
||||
+ assert result.result == constants.SUCCESS
|
||||
+ assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
+ assert result.check == 'IPATrustDomainsCheck'
|
||||
+ assert result.kw.get('key') == 'domain-status'
|
||||
assert result.kw.get('domain') == 'child.example'
|
||||
|
||||
@patch('ipapython.ipautil.run')
|
||||
@@ -319,22 +364,8 @@ class TestTrustDomains(BaseTest):
|
||||
mock_run.side_effect = [dlresult, olresult, olresult]
|
||||
|
||||
# get_trust_domains()
|
||||
- m_api.Command.trust_find.side_effect = [{
|
||||
- 'result': [
|
||||
- {
|
||||
- 'cn': ['ad.example'],
|
||||
- 'ipantflatname': ['ADROOT'],
|
||||
- 'ipanttrusteddomainsid': ['S-1-5-21-abc'],
|
||||
- 'trusttype': ['Active Directory domain'],
|
||||
- },
|
||||
- {
|
||||
- 'cn': ['child.example'],
|
||||
- 'ipantflatname': ['ADROOT'],
|
||||
- 'ipanttrusteddomainsid': ['S-1-5-21-def'],
|
||||
- 'trusttype': ['Active Directory domain'],
|
||||
- },
|
||||
- ]
|
||||
- }]
|
||||
+ m_api.Command.trust_find.side_effect = trust_find()
|
||||
+ m_api.Command.trustdomain_find.side_effect = trustdomain_find()
|
||||
|
||||
framework = object()
|
||||
registry.initialize(framework, config.Config)
|
||||
@@ -350,7 +381,8 @@ class TestTrustDomains(BaseTest):
|
||||
assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
assert result.check == 'IPATrustDomainsCheck'
|
||||
assert result.kw.get('key') == 'domain-list'
|
||||
- assert result.kw.get('trust_domains') == 'ad.example, child.example'
|
||||
+ assert result.kw.get('trust_domains') == \
|
||||
+ 'ad.example, child.ad.example, child.example'
|
||||
assert result.kw.get('sssd_domains') == 'child.example'
|
||||
|
||||
result = self.results.results[1]
|
||||
@@ -428,29 +460,16 @@ class TestTrustCatalog(BaseTest):
|
||||
ds2result.output = 'Active servers:\nAD Global Catalog: ' \
|
||||
'root-dc.ad.vm\nAD Domain Controller: root-dc.ad.vm\n' \
|
||||
|
||||
- mock_run.side_effect = [dsresult, ds2result]
|
||||
+ mock_run.side_effect = [dsresult, ds2result, ds2result]
|
||||
mock_getnamebysid.side_effect = [
|
||||
{'S-1-5-21-abc-500': {'name': 'admin@ad.example', 'type': 3}},
|
||||
+ {'S-1-5-21-ghi-500': {'name': 'admin@child.ad.example', 'type': 3}},
|
||||
{'S-1-5-21-def-500': {'name': 'admin@child.example', 'type': 3}}
|
||||
]
|
||||
|
||||
# get_trust_domains()
|
||||
- m_api.Command.trust_find.side_effect = [{
|
||||
- 'result': [
|
||||
- {
|
||||
- 'cn': ['ad.example'],
|
||||
- 'ipantflatname': ['ADROOT'],
|
||||
- 'ipanttrusteddomainsid': ['S-1-5-21-abc'],
|
||||
- 'trusttype': ['Active Directory domain'],
|
||||
- },
|
||||
- {
|
||||
- 'cn': ['child.example'],
|
||||
- 'ipantflatname': ['ADROOT'],
|
||||
- 'ipanttrusteddomainsid': ['S-1-5-21-def'],
|
||||
- 'trusttype': ['Active Directory domain'],
|
||||
- },
|
||||
- ]
|
||||
- }]
|
||||
+ m_api.Command.trust_find.side_effect = trust_find()
|
||||
+ m_api.Command.trustdomain_find.side_effect = trustdomain_find()
|
||||
|
||||
framework = object()
|
||||
registry.initialize(framework, config.Config)
|
||||
@@ -459,7 +478,7 @@ class TestTrustCatalog(BaseTest):
|
||||
|
||||
self.results = capture_results(f)
|
||||
|
||||
- assert len(self.results) == 6
|
||||
+ assert len(self.results) == 9
|
||||
|
||||
result = self.results.results[0]
|
||||
assert result.result == constants.SUCCESS
|
||||
@@ -487,20 +506,40 @@ class TestTrustCatalog(BaseTest):
|
||||
assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
assert result.check == 'IPATrustCatalogCheck'
|
||||
assert result.kw.get('key') == 'Domain Security Identifier'
|
||||
- assert result.kw.get('sid') == 'S-1-5-21-def'
|
||||
+ assert result.kw.get('sid') == 'S-1-5-22-def'
|
||||
|
||||
result = self.results.results[4]
|
||||
assert result.result == constants.SUCCESS
|
||||
assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
assert result.check == 'IPATrustCatalogCheck'
|
||||
assert result.kw.get('key') == 'AD Global Catalog'
|
||||
- assert result.kw.get('domain') == 'child.example'
|
||||
+ assert result.kw.get('domain') == 'child.ad.example'
|
||||
|
||||
result = self.results.results[5]
|
||||
assert result.result == constants.SUCCESS
|
||||
assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
assert result.check == 'IPATrustCatalogCheck'
|
||||
assert result.kw.get('key') == 'AD Domain Controller'
|
||||
+
|
||||
+ result = self.results.results[6]
|
||||
+ assert result.result == constants.SUCCESS
|
||||
+ assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
+ assert result.check == 'IPATrustCatalogCheck'
|
||||
+ assert result.kw.get('key') == 'Domain Security Identifier'
|
||||
+ assert result.kw.get('sid') == 'S-1-5-21-ghi'
|
||||
+
|
||||
+ result = self.results.results[7]
|
||||
+ assert result.result == constants.SUCCESS
|
||||
+ assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
+ assert result.check == 'IPATrustCatalogCheck'
|
||||
+ assert result.kw.get('key') == 'AD Global Catalog'
|
||||
+ assert result.kw.get('domain') == 'child.example'
|
||||
+
|
||||
+ result = self.results.results[8]
|
||||
+ assert result.result == constants.SUCCESS
|
||||
+ assert result.source == 'ipahealthcheck.ipa.trust'
|
||||
+ assert result.check == 'IPATrustCatalogCheck'
|
||||
+ assert result.kw.get('key') == 'AD Domain Controller'
|
||||
assert result.kw.get('domain') == 'child.example'
|
||||
|
||||
|
||||
--
|
||||
2.25.4
|
||||
|
||||
@ -1,579 +0,0 @@
|
||||
From 952fc6f6dee99523360c9826ad865086cd31474f Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
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
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
From 5efeafa16a893cb6277ece4d573184bb64ee2744 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Mon, 30 Nov 2020 15:06:03 -0500
|
||||
Subject: [PATCH] result names are not translated when reading input from json
|
||||
file
|
||||
|
||||
The strings were being retained so when processing the results to
|
||||
determine the return code it was always a 1 because none of
|
||||
the values were being translated. It was always comparing
|
||||
the string like 'SUCCESS' to constants.SUCCESS which is 0.
|
||||
|
||||
https://bugzilla.redhat.com/show_bug.cgi?id=1866558
|
||||
---
|
||||
src/ipahealthcheck/core/constants.py | 20 +++++++++++++++++++-
|
||||
src/ipahealthcheck/core/plugin.py | 4 ++--
|
||||
tests/test_results.py | 8 ++++++++
|
||||
3 files changed, 29 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/ipahealthcheck/core/constants.py b/src/ipahealthcheck/core/constants.py
|
||||
index a55469c..b6ee029 100644
|
||||
--- a/src/ipahealthcheck/core/constants.py
|
||||
+++ b/src/ipahealthcheck/core/constants.py
|
||||
@@ -36,7 +36,25 @@ def getLevelName(level):
|
||||
is passed in instead the corresponding string representation is
|
||||
returned.
|
||||
"""
|
||||
- return _levelToName.get(level) or _nameToLevel.get(level) or level
|
||||
+ name = _levelToName.get(level) or _nameToLevel.get(level)
|
||||
+ if name is not None:
|
||||
+ return name
|
||||
+
|
||||
+ return level
|
||||
+
|
||||
+
|
||||
+def getLevel(name):
|
||||
+ """
|
||||
+ Translate between level text and their numeric constants
|
||||
+
|
||||
+ If the level is one of the predefined levels then returns the
|
||||
+ corresponding number.
|
||||
+ """
|
||||
+ level = _nameToLevel.get(name)
|
||||
+ if level is not None:
|
||||
+ return level
|
||||
+
|
||||
+ return name
|
||||
|
||||
|
||||
CONFIG_FILE = '/etc/ipahealthcheck/ipahealthcheck.conf'
|
||||
diff --git a/src/ipahealthcheck/core/plugin.py b/src/ipahealthcheck/core/plugin.py
|
||||
index 7ac923a..26dddd4 100644
|
||||
--- a/src/ipahealthcheck/core/plugin.py
|
||||
+++ b/src/ipahealthcheck/core/plugin.py
|
||||
@@ -6,7 +6,7 @@ import uuid
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
|
||||
-from ipahealthcheck.core.constants import getLevelName
|
||||
+from ipahealthcheck.core.constants import getLevelName, getLevel
|
||||
|
||||
|
||||
def duration(f):
|
||||
@@ -204,7 +204,7 @@ def json_to_results(data):
|
||||
results = Results()
|
||||
|
||||
for line in data:
|
||||
- result = line.pop('result')
|
||||
+ result = getLevel(line.pop('result'))
|
||||
source = line.pop('source')
|
||||
check = line.pop('check')
|
||||
duration = line.pop('duration')
|
||||
diff --git a/tests/test_results.py b/tests/test_results.py
|
||||
index dd6e8fd..99c18d7 100644
|
||||
--- a/tests/test_results.py
|
||||
+++ b/tests/test_results.py
|
||||
@@ -69,3 +69,11 @@ def test_Result():
|
||||
assert x['result'] in (constants.getLevelName(constants.SUCCESS),
|
||||
constants.getLevelName(constants.CRITICAL))
|
||||
assert len(x['kw']) == 0
|
||||
+
|
||||
+
|
||||
+def test_getLevel():
|
||||
+ assert constants.getLevel('SUCCESS') == constants.SUCCESS
|
||||
+ assert constants.getLevel('WARNING') == constants.WARNING
|
||||
+ assert constants.getLevel('ERROR') == constants.ERROR
|
||||
+ assert constants.getLevel('CRITICAL') == constants.CRITICAL
|
||||
+ assert constants.getLevel('FOO') == 'FOO'
|
||||
--
|
||||
2.25.4
|
||||
|
||||
@ -1,228 +0,0 @@
|
||||
From af5c169151f0697d34954ac5c9dd0686dc73ef3f Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,8 +7,8 @@
|
||||
|
||||
|
||||
Name: ipa-healthcheck
|
||||
Version: 0.4
|
||||
Release: 6%{?dist}
|
||||
Version: 0.7
|
||||
Release: 3%{?dist}
|
||||
Summary: Health check tool for IdM
|
||||
BuildArch: noarch
|
||||
License: GPLv3
|
||||
@ -17,16 +17,15 @@ Source0: https://github.com/%{project}/%{name}/archive/%{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
|
||||
Patch0002: 0002-Remove-ipaclustercheck.patch
|
||||
Patch0003: 0003-Use-trust-find-and-trustdomain-find-to-identify-all-.patch
|
||||
Patch0004: 0004-result-names-are-not-translated-when-reading-input-f.patch
|
||||
|
||||
Requires: %{name}-core = %{version}-%{release}
|
||||
Requires: ipa-server
|
||||
Requires: python3-ipalib
|
||||
Requires: python3-ipaserver
|
||||
Requires: python3-lib389
|
||||
# cronie-anacron provides anacron
|
||||
Requires: anacron
|
||||
Requires: logrotate
|
||||
@ -77,9 +76,9 @@ install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/logrotate/%{lon
|
||||
|
||||
mkdir -p %{buildroot}/%{_localstatedir}/log/ipa/%{shortname}
|
||||
|
||||
mkdir -p %{buildroot}/%{_mandir}/man1
|
||||
mkdir -p %{buildroot}/%{_mandir}/man8
|
||||
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/man8/ipa-%{shortname}.8 %{buildroot}%{_mandir}/man8/
|
||||
install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man5/%{longname}.conf.5 %{buildroot}%{_mandir}/man5/
|
||||
|
||||
(cd %{buildroot}/%{python3_sitelib}/ipahealthcheck && find . -type f | \
|
||||
@ -113,7 +112,7 @@ install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man5/%{long
|
||||
%{python3_sitelib}/%{longname}-%{version}-*-nspkg.pth
|
||||
%{_unitdir}/*
|
||||
%{_libexecdir}/*
|
||||
%{_mandir}/man1/*
|
||||
%{_mandir}/man8/*
|
||||
%{_mandir}/man5/*
|
||||
|
||||
%files -n %{name}-core
|
||||
@ -124,6 +123,21 @@ install -p -m644 %{_builddir}/%{project}-%{shortname}-%{version}/man/man5/%{long
|
||||
|
||||
|
||||
%changelog
|
||||
* Fri Dec 4 2020 Rob Crittenden <rcritten@redhat.com> - 0.7-3
|
||||
- Translate result names when reading input from a json file (#1866558)
|
||||
|
||||
* Tue Nov 3 2020 Rob Crittenden <rcritten@redhat.com> - 0.7-2
|
||||
- Fix collection of AD trust domains (#1891505)
|
||||
|
||||
* Tue Nov 3 2020 Rob Crittenden <rcritten@redhat.com> - 0.7-1
|
||||
- Update to upstream 0.7 (#1891850)
|
||||
- Include Directory Server healthchecks (#1824193)
|
||||
- Document that default output format is JSON (#1780328)
|
||||
- Fix return value on exit with --input-file (#1866558)
|
||||
- Fix examples in man page (#1809215)
|
||||
- Replace man page reference to output-format with output-type (#1780303)
|
||||
- Add dependencies on services to avoid false positives (#1780510)
|
||||
|
||||
* Wed Aug 19 2020 Rob Crittenden <rcritten@redhat.com> - 0.4-6
|
||||
- The core subpackage can be installed standalone, drop the Requires
|
||||
on the base package. (#1852244)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user