URL: https://github.com/SSSD/sssd/pull/5913 Author: justin-stephenson Title: #5913: Analyzer: Remove python-click dependency Action: opened
PR body: """ As python-click will not be in RHEL9, switch to using the builtin argparse python module. """ To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/5913/head:pr5913 git checkout pr5913
From 9ba686c5c807a4b712bde0c41a2a4e13e24ed01e Mon Sep 17 00:00:00 2001 From: Justin Stephenson <jstep...@redhat.com> Date: Tue, 7 Dec 2021 10:21:36 -0500 Subject: [PATCH] Analyzer: Remove python-click dependency As python-click will not be in RHEL9, switch to using the builtin argparse python module. --- contrib/sssd.spec.in | 3 +- src/tools/analyzer/modules/request.py | 129 +++++++++++--------- src/tools/analyzer/sss_analyze.py | 167 +++++++++++++++++++++++--- 3 files changed, 227 insertions(+), 72 deletions(-) diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 7f75b1b5a3..d327143576 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -221,9 +221,8 @@ Requires: sssd-common = %{version}-%{release} Requires: python3-sss = %{version}-%{release} Requires: python3-sssdconfig = %{version}-%{release} Requires: libsss_certmap = %{version}-%{release} -# required by sss_analyze +# for logger=journald support with sss_analyze Requires: python3-systemd -Requires: python3-click Recommends: sssd-dbus %description tools diff --git a/src/tools/analyzer/modules/request.py b/src/tools/analyzer/modules/request.py index 098a9197bb..ff9592e308 100644 --- a/src/tools/analyzer/modules/request.py +++ b/src/tools/analyzer/modules/request.py @@ -1,67 +1,91 @@ import re import copy -import click import logging +import argparse from enum import Enum from source_files import Files from source_journald import Journald +from sss_analyze import SubparsersAction +from sss_analyze import Option +from sss_analyze import Analyzer logger = logging.getLogger() -@click.group(help="Request module") -def request(): - pass - - -@request.command() -@click.option("-v", "--verbose", is_flag=True, help="Enables verbose output") -@click.option("--pam", is_flag=True, help="Filter only PAM requests") -@click.pass_obj -def list(ctx, verbose, pam): - analyzer = RequestAnalyzer() - source = analyzer.load(ctx) - analyzer.list_requests(source, verbose, pam) - - -@request.command() -@click.argument("cid", nargs=1, type=int, required=True) -@click.option("--merge", is_flag=True, help="Merge logs together sorted" - " by timestamp (requires debug_microseconds = True)") -@click.option("--cachereq", is_flag=True, help="Include cache request " - "related logs") -@click.option("--pam", is_flag=True, help="Track only PAM requests") -@click.pass_obj -def show(ctx, cid, merge, cachereq, pam): - analyzer = RequestAnalyzer() - source = analyzer.load(ctx) - analyzer.track_request(source, cid, merge, cachereq, pam) - - class RequestAnalyzer: """ A request analyzer module, handles request tracking logic and analysis. Parses input generated from a source Reader. """ + module_parser = None consumed_logs = [] done = "" + list_opts = [ + Option('--verbose', 'Verbose output', bool, '-v'), + Option('--pam', 'Filter only PAM requests', bool), + ] + + show_opts = [ + Option('cid', 'Track request with this ID', int), + Option('--cachereq', 'Include cache request logs', bool), + Option('--merge', 'Merge logs together sorted by timestamp', bool), + Option('--pam', 'Track only PAM requests', bool), + ] + + def print_module_help(self, args): + """ + Print the module parser help output + + Args: + args (Namespace): argparse parsed arguments + """ + self.module_parser.print_help() + + def setup_args(self, parser_grp): + """ + Setup module parser, subcommands, and options - def load(self, ctx): + Args: + parser_grp (argparse.Action): Parser group to nest + module and subcommands under + """ + desc = "Analyze request tracking module" + self.module_parser = parser_grp.add_parser('request', + description=desc, + help='Request tracking') + + subparser = self.module_parser.add_subparsers(title=None, + dest='subparser', + action=SubparsersAction, + metavar='COMMANDS') + + cli = Analyzer() + subcmd_grp = subparser.add_parser_group('Operation Modes') + cli.add_subcommand(subcmd_grp, 'list', 'List recent requests', + self.list_requests, self.list_opts) + cli.add_subcommand(subcmd_grp, 'show', 'Track individual request ID', + self.track_request, self.show_opts) + + self.module_parser.set_defaults(func=self.print_module_help) + + return self.module_parser + + def load(self, args): """ Load the appropriate source reader. Args: - ctx (click.ctx): command line state object + args (Namespace): argparse parsed arguments Returns: Instantiated source object """ - if ctx.source == "journald": + if args.source == "journald": import source_journald source = Journald() else: - source = Files(ctx.logdir) + source = Files(args.logdir) return source def matched_line(self, source, patterns): @@ -184,25 +208,21 @@ def print_formatted(self, line, verbose): print(" - " + id) self.done = cr - def list_requests(self, source, verbose, pam): + def list_requests(self, args): """ List component (default: NSS) responder requests Args: - line (str): line to process - source (Reader): source Reader object - verbose (bool): True if --verbose cli option is provided, enables - verbose output - pam (bool): True if --pam cli option is provided, list requests - in the PAM responder only + args (Namespace): populated argparse namespace """ + source = self.load(args) component = source.Component.NSS resp = "nss" patterns = ['\[cmd'] patterns.append("(cache_req_send|cache_req_process_input|" "cache_req_search_send)") consume = True - if pam: + if args.pam: component = source.Component.PAM resp = "pam" @@ -214,20 +234,17 @@ def list_requests(self, source, verbose, pam): if isinstance(source, Journald): print(line) else: - self.print_formatted(line, verbose) + self.print_formatted(line, args.verbose) - def track_request(self, source, cid, merge, cachereq, pam): + def track_request(self, args): """ Print Logs pertaining to individual SSSD client request Args: - source (Reader): source Reader object - cid (int): client ID number to show - merge (bool): True when --merge is provided, merge logs together - by timestamp - pam (bool): True if --pam cli option is provided, track requests - in the PAM responder + args (Namespace): populated argparse namespace """ + source = self.load(args) + cid = args.cid resp_results = False be_results = False component = source.Component.NSS @@ -235,7 +252,7 @@ def track_request(self, source, cid, merge, cachereq, pam): pattern = [f'REQ_TRACE.*\[CID #{cid}\\]'] pattern.append(f"\[CID #{cid}\\].*connected") - if pam: + if args.pam: component = source.Component.PAM resp = "pam" pam_data_regex = f'pam_print_data.*\[CID #{cid}\]' @@ -243,13 +260,13 @@ def track_request(self, source, cid, merge, cachereq, pam): logger.info(f"******** Checking {resp} responder for Client ID" f" {cid} *******") source.set_component(component) - if cachereq: + if args.cachereq: cr_id_regex = 'CR #[0-9]+' cr_ids = self.get_linked_ids(source, pattern, cr_id_regex) [pattern.append(f'{id}\:') for id in cr_ids] for match in self.matched_line(source, pattern): - resp_results = self.consume_line(match, source, merge) + resp_results = self.consume_line(match, source, args.merge) logger.info(f"********* Checking Backend for Client ID {cid} ********") pattern = [f'REQ_TRACE.*\[sssd.{resp} CID #{cid}\]'] @@ -261,12 +278,12 @@ def track_request(self, source, cid, merge, cachereq, pam): pattern.clear() [pattern.append(f'\\{id}') for id in be_ids] - if pam: + if args.pam: pattern.append(pam_data_regex) for match in self.matched_line(source, pattern): - be_results = self.consume_line(match, source, merge) + be_results = self.consume_line(match, source, args.merge) - if merge: + if args.merge: # sort by date/timestamp sorted_list = sorted(self.consumed_logs, key=lambda s: s.split(')')[0]) diff --git a/src/tools/analyzer/sss_analyze.py b/src/tools/analyzer/sss_analyze.py index 89684a3f75..3063cc0af9 100755 --- a/src/tools/analyzer/sss_analyze.py +++ b/src/tools/analyzer/sss_analyze.py @@ -1,27 +1,166 @@ #!/usr/bin/env python -import click +import argparse import source_files from modules import request -class Analyzer(object): - def __init__(self, source="files", logdir="/var/log/sssd/"): - self.source = source - self.logdir = logdir +# Based on patch from https://bugs.python.org/issue9341 +class SubparsersAction(argparse._SubParsersAction): + """ + Provide a subparser action that can create subparsers with ability of + grouping arguments. + It is based on the patch from: -@click.group(help="Analyzer tool to assist with SSSD Log parsing") -@click.option('--source', default='files') -@click.option('--logdir', default='/var/log/sssd/', help="SSSD Log directory " - "to parse log files from") -@click.pass_context -def cli(ctx, source, logdir): - ctx.obj = Analyzer(source, logdir) + - https://bugs.python.org/issue9341 + """ + + class _PseudoGroup(argparse.Action): + + def __init__(self, container, title): + super().__init__(option_strings=[], dest=title) + self.container = container + self._choices_actions = [] + + def add_parser(self, name, **kwargs): + # add the parser to the main Action, but move the pseudo action + # in the group's own list + parser = self.container.add_parser(name, **kwargs) + choice_action = self.container._choices_actions.pop() + self._choices_actions.append(choice_action) + return parser + + def _get_subactions(self): + return self._choices_actions + + def add_parser_group(self, title): + # the formatter can handle recursive subgroups + grp = SubparsersAction._PseudoGroup(self, title) + self._choices_actions.append(grp) + return grp + + def add_parser_group(self, title): + """ + Add new parser group. + + :param title: Title. + :type title: str + :return: Parser group that can have additional parsers attached. + :rtype: ``argparse.Action`` extended with ``add_parser`` method + """ + grp = self._PseudoGroup(self, title) + self._choices_actions.append(grp) + return grp + + +class Option: + """ + Group option attributes for command/subcommand options + """ + def __init__(self, name, help_msg, opt_type, short_opt=None): + self.name = name + self.short_opt = short_opt + self.help_msg = help_msg + self.opt_type = opt_type + + +class Analyzer: + def add_subcommand(self, subcmd_grp, name, help_msg, func, opts): + """ + Add subcommand to existing subcommand group + + Args: + name(str): Subcommand name + help_msg(str): Help message for subcommand + func(function): Function to call on execution + opts(list of Object()): List of Option objects to add to subcommand + """ + # Create parser + req_parser = subcmd_grp.add_parser(name, help=help_msg) + + # Add subcommand options + self._add_subcommand_options(req_parser, opts) + + # Execute func() when argument is called + req_parser.set_defaults(func=func) + + def _add_subcommand_options(self, parser, opts): + """ + Add subcommand options to subcommand parser + + Args: + parser(str): Subcommand group parser + opts(list of Object()): List of Option objects to add to subcommand + """ + for opt in opts: + if opt.opt_type is bool: + if opt.short_opt is None: + parser.add_argument(opt.name, help=opt.help_msg, + action='store_true') + else: + parser.add_argument(opt.name, opt.short_opt, + help=opt.help_msg, action='store_true') + if opt.opt_type is int: + parser.add_argument(opt.name, help=opt.help_msg, + type=int) + + def load_modules(self, parser, parser_grp): + """ + Initialize analyzer modules from modules/* + + Args: + parser (ArgumentParser): Base parser object + parser_grp (argparse.Action): Parser group that can have + additional parsers attached. + """ + # Currently only the 'request' module exists + req = request.RequestAnalyzer() + + module_parser = req.setup_args(parser_grp) + + def setup_args(self): + """ + Top-level argument setup function. + Setup analyzer argument parsers and subcommand parser/options. + + Returns: + parser (ArgumentParser): Base parser object + """ + # top level parser + formatter = argparse.RawTextHelpFormatter + parser = argparse.ArgumentParser(description='Analyzer tool to assist ' + 'with SSSD log parsing', + formatter_class=formatter) + parser.add_argument('--source', default='files', choices=['files', + 'journald']) + parser.add_argument('--logdir', default='/var/log/sssd/', + help='SSSD Log directory to parse log files from') + + # Modules parser group + subparser = parser.add_subparsers(title=None, + action=SubparsersAction, + metavar='COMMANDS') + parser_grp = subparser.add_parser_group('Modules') + + # Load modules, subcommands are added in module.setup_args() + self.load_modules(parser, parser_grp) + + return parser + + def main(self): + parser = self.setup_args() + args = parser.parse_args() + + if not hasattr(args, 'func'): + parser.print_help() + return 0 + + args.func(args) if __name__ == '__main__': - cli.add_command(request.request) - cli() + analyzer = Analyzer() + analyzer.main()
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure