openscap_report.cli   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Test Coverage

Coverage 63.74%

Importance

Changes 0
Metric Value
eloc 153
dl 0
loc 215
rs 10
c 0
b 0
f 0
ccs 58
cts 91
cp 0.6374
wmc 14

8 Methods

Rating   Name   Duplication   Size   Complexity  
A CommandLineAPI.__init__() 0 11 1
A CommandLineAPI.store_file() 0 6 2
A CommandLineAPI.get_report_generator() 0 7 1
A CommandLineAPI.generate_report() 0 7 1
A CustomHelpFormatter._format_action_invocation() 0 14 3
A CommandLineAPI.close_files() 0 4 1
A CommandLineAPI._setup_logging() 0 6 1
A CommandLineAPI.load_file() 0 3 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
A main() 0 17 2
B prepare_parser() 0 70 1
1
# Copyright 2022, Red Hat, Inc.
2
# SPDX-License-Identifier: GPL-2.0-or-later
3
4 1
import argparse
5 1
import logging
6 1
import sys
7 1
from sys import exit as sys_exit
8
9 1
from lxml.etree import XMLSyntaxError
10
11 1
from . import __version__
12 1
from .debug_settings import DebugSetting
13 1
from .report_generators import (HTMLReportGenerator,
14
                                JSONEverythingReportGenerator,
15
                                JSONReportGenerator)
16 1
from .scap_results_parser import NotSupportedReportingFormat, SCAPResultsParser
17
18 1
DESCRIPTION = ("Generates an HTML report from an ARF (or XCCDF Result) file with results of "
19
               "a SCAP-compatible utility scan. Unless the --output option is specified "
20
               "the report will be written to the standard output.")
21 1
LOG_LEVELS_DESCRIPTION = """
22
LOG LEVELS:
23
    DEBUG - Detailed information, typically of interest only for diagnosing problems.
24
25
    INFO - A confirmation that things are working as expected.
26
27
    WARNING -  An indication that something unexpected happened, or a signal of a possible problem in the future. The software is still working as expected.
28
29
    ERROR - Due to a more serious problems, the software has not been able to perform its function to the full extent.
30
31
    CRITICAL - A serious error, indicating that the program itself may be unable to continue operating.
32
"""
33
34 1
DEBUG_FLAGS_DESCRIPTION = """
35
DEBUG FLAGS:
36
    NO-MINIFY - The HTML report will not be minified.
37
38
    BUTTON-SHOW-ALL-RULES - Adds a button to the HTML report for expanding all rules.
39
40
    ONLINE-CSS - Use the latest online version of Patternfly CSS/JS in the HTML report.
41
42
    BUTTON-SHOW-ALL-RULES-AND-OVAL-TEST-DETAILS - Adds a button to the HTML report for expanding all rules and all OVAL test details.
43
"""
44
45 1
MASSAGE_FORMAT = '%(levelname)s: %(message)s'
46 1
EXPECTED_ERRORS = (XMLSyntaxError, NotSupportedReportingFormat)
47 1
EXIT_FAILURE_CODE = 1
48 1
EXIT_SUCCESS_CODE = 0
49
50
51 1
class CustomHelpFormatter(argparse.RawTextHelpFormatter):
52 1
    def _format_action_invocation(self, action):
53
        if not action.option_strings:
54
            metavar, = self._metavar_formatter(action, action.dest)(1)
55
            return metavar
56
57
        parts = []
58
        if action.nargs == 0:
59
            parts.extend(action.option_strings)
60
        else:
61
            default = action.dest.upper()
62
            args_string = self._format_args(action, default)
63
            options_string = ", ".join(action.option_strings)
64
            parts.append(f'{options_string} {args_string}')
65
        return ',  '.join(parts)
66
67
68 1
def prepare_parser():
69 1
    parser = argparse.ArgumentParser(
70
        prog="oscap-report",
71
        formatter_class=CustomHelpFormatter,
72
        description=DESCRIPTION,
73
        add_help=False,
74
    )
75 1
    parser.add_argument(
76
        "--version",
77
        action="version",
78
        version="%(prog)s " + __version__,
79
        help="Show program's version number and exit.")
80 1
    parser.add_argument(
81
        '-h',
82
        '--help',
83
        action='help',
84
        default=argparse.SUPPRESS,
85
        help='Show this help message and exit.')
86 1
    parser.add_argument(
87
        'FILE',
88
        type=argparse.FileType("r"),
89
        nargs='?',
90
        default=sys.stdin,
91
        help="ARF (XCCDF) file or stdin if not provided.")
92 1
    parser.add_argument(
93
        "-o",
94
        "--output",
95
        action="store",
96
        type=argparse.FileType("wb+", 0),
97
        default=sys.stdout,
98
        help="write the report to a file instead of the standard output.")
99 1
    parser.add_argument(
100
        "--log-file",
101
        action="store",
102
        default=None,
103
        help="write the log to a file instead of stderr.")
104 1
    parser.add_argument(
105
        "--log-level",
106
        action="store",
107
        default="WARNING",
108
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
109
        help=(
110
            "write debug information to the log up to the LOG_LEVEL."
111
            f"\n{LOG_LEVELS_DESCRIPTION}")
112
    )
113 1
    parser.add_argument(
114
        "-f",
115
        "--format",
116
        action="store",
117
        default="HTML",
118
        choices=["HTML", "JSON", "JSON-EVERYTHING"],
119
        help="FORMAT: %(choices)s"
120
    )
121 1
    parser.add_argument(
122
        "-d",
123
        "--debug",
124
        action="store",
125
        nargs='+',
126
        default=[""],
127
        choices=[
128
            "NO-MINIFY",
129
            "ONLINE-CSS",
130
            "BUTTON-SHOW-ALL-RULES",
131
            "BUTTON-SHOW-ALL-RULES-AND-OVAL-TEST-DETAILS"
132
        ],
133
        help=(
134
            "extra HTML generation options for debugging."
135
            f"\n{DEBUG_FLAGS_DESCRIPTION}")
136
    )
137 1
    return parser
138
139
140 1
class CommandLineAPI():  # pylint: disable=R0902
141 1
    def __init__(self):
142 1
        self.arguments = prepare_parser().parse_args()
143 1
        self.log_file = self.arguments.log_file
144 1
        self.log_level = self.arguments.log_level
145 1
        self.debug_flags = self.arguments.debug
146 1
        self._setup_logging()
147 1
        logging.debug("Args: %s", self.arguments)
148 1
        self.report_file = self.arguments.FILE
149 1
        self.output_file = self.arguments.output
150 1
        self.output_format = self.arguments.format.upper()
151 1
        self.debug_setting = DebugSetting()
152
153 1
    def _setup_logging(self):
154 1
        logging.basicConfig(
155
            format=MASSAGE_FORMAT,
156
            filename=self.log_file,
157
            filemode='w',
158
            level=self.log_level.upper()
159
        )
160
161 1
    def get_report_generator(self, report_parser):
162
        dict_of_report_generators = {
163
            "HTML": HTMLReportGenerator,
164
            "JSON": JSONReportGenerator,
165
            "JSON-EVERYTHING": JSONEverythingReportGenerator,
166
        }
167
        return dict_of_report_generators[self.output_format](report_parser)
168
169 1
    def generate_report(self, report_parser):
170
        logging.info("Generate report")
171
        report_generator = self.get_report_generator(report_parser)
172
173
        self.debug_setting.update_settings_with_debug_flags(self.debug_flags)
174
175
        return report_generator.generate_report(self.debug_setting)
176
177 1
    def load_file(self):
178 1
        logging.info("Loading file: %s", self.report_file)
179 1
        return self.report_file.read().encode()
180
181 1
    def store_file(self, data):
182 1
        logging.info("Store report")
183 1
        if self.output_file.name == "<stdout>":
184
            logging.info("Output is stdout, converting bytes output to str")
185
            data = data.read().decode("utf-8")
186 1
        self.output_file.writelines(data)
187
188 1
    def close_files(self):
189 1
        logging.info("Close files")
190 1
        self.report_file.close()
191 1
        self.output_file.close()
192
193
194 1
def main():
195
    exit_code = EXIT_SUCCESS_CODE
196
    api = CommandLineAPI()
197
    arf_report = api.load_file()
198
199
    logging.info("Parse file")
200
    try:
201
        parser = SCAPResultsParser(arf_report)
202
203
        report = api.generate_report(parser)
204
205
        api.store_file(report)
206
    except EXPECTED_ERRORS as error:
207
        logging.fatal("%s", error)
208
        exit_code = EXIT_FAILURE_CODE
209
    api.close_files()
210
    sys_exit(exit_code)
211
212
213 1
if __name__ == '__main__':
214
    main()
215