Passed
Pull Request — master (#102)
by Jan
05:13
created

openscap_report.cli   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Test Coverage

Coverage 63.74%

Importance

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

7 Methods

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