import ipa-healthcheck-0.7-3.module+el8.4.0+9007+5084bdd8

This commit is contained in:
CentOS Sources 2021-05-18 02:45:59 -04:00 committed by Andrew Lukoshko
parent 4a16c583ed
commit fdeb5dda89
11 changed files with 1049 additions and 2647 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/freeipa-healthcheck-0.4.tar.gz
SOURCES/freeipa-healthcheck-0.7.tar.gz

View File

@ -1 +1 @@
2e61604c36f6f793612b1e3bb3f5e78df1cb58ac SOURCES/freeipa-healthcheck-0.4.tar.gz
93e4d1554a11ba94947a816b008fb1092abbf591 SOURCES/freeipa-healthcheck-0.7.tar.gz

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)