Passed
Pull Request — master (#83)
by Jan
19:50 queued 14:06
created

openscap_report.cli   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Test Coverage

Coverage 65.66%

Importance

Changes 0
Metric Value
eloc 160
dl 0
loc 214
rs 10
c 0
b 0
f 0
ccs 65
cts 99
cp 0.6566
wmc 18

7 Methods

Rating   Name   Duplication   Size   Complexity  
A CommandLineAPI.__init__() 0 11 1
A CommandLineAPI.store_file() 0 6 2
A CommandLineAPI.close_files() 0 4 1
A CommandLineAPI._setup_logging() 0 6 1
A CommandLineAPI.load_file() 0 3 1
B DebugSetting.update_settings_with_debug_flags() 0 12 7
A CommandLineAPI.generate_report() 0 10 2

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