Passed
Pull Request — master (#70)
by Jan
07:01
created

openscap_report.cli.CommandLineAPI.load_file()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nop 1
crap 1
1 1
import argparse
2 1
import logging
3 1
from dataclasses import dataclass
4 1
from sys import exit as sys_exit
5 1
from sys import stdin, stdout
6
7 1
from lxml.etree import XMLSyntaxError
8
9 1
from . import __version__
10 1
from .html_report import ReportGenerator
11 1
from .old_html_report_style import OldOSCAPReportGenerator
12 1
from .scap_results_parser import SCAPResultsParser
13
14 1
DESCRIPTION = ("Generates an HTML report from an ARF (or XCCDF Result) file with results of "
15
               "a SCAP-compatible utility scan. Unless the --output option is specified "
16
               "the report will be written to the standard output.")
17 1
LOG_LEVES_DESCRIPTION = (
18
    "LOG LEVELS:\n"
19
    "\tDEBUG - Detailed information, typically of interest only for diagnosing problems.\n"
20
    "\tINFO - A confirmation that things are working as expected.\n"
21
    "\tWARING -  An indication that something unexpected happened, or a signal of"
22
    " a possible problem in the future. The software is still working as expected.\n"
23
    "\tERROR - Due to a more serious problems, the software has not been able to perform "
24
    "its function to the full extent.\n"
25
    "\tCRITICAL - A serious error, indicating that the program itself may be unable "
26
    "to continue operating.\n"
27
)
28 1
DEBUG_FLAGS_DESCRIPTION = (
29
    "DEBUG FLAGS:\n"
30
    "\tNO-MINIFY - The HTML report will not be minified.\n"
31
    "\tBUTTON-SHOW-ALL-RULES - Adds a button to the HTML report for expanding all rules.\n"
32
    "\tONLINE-CSS - Use the latest online version of Patternfly CSS/JS in the HTML report\n"
33
    "\tBUTTON-SHOW-ALL-OVAL-TEST-DETAILS - Adds a button to the HTML report for expanding all OVAL test details."
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (113/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
34
)
35
36 1
MASSAGE_FORMAT = '%(levelname)s: %(message)s'
37 1
EXPECTED_ERRORS = (XMLSyntaxError, )
38 1
EXIT_FAILURE_CODE = 1
39 1
EXIT_SUCCESS_CODE = 0
40
41
42 1
def prepare_parser():
43 1
    parser = argparse.ArgumentParser(
44
        prog="oscap-report",
45
        formatter_class=argparse.RawTextHelpFormatter,
46
        description=DESCRIPTION,
47
        add_help=False,
48
    )
49 1
    parser.add_argument(
50
        "--version",
51
        action="version",
52
        version="%(prog)s " + __version__,
53
        help="Show program's version number and exit.")
54 1
    parser.add_argument(
55
        '-h',
56
        '--help',
57
        action='help',
58
        default=argparse.SUPPRESS,
59
        help='Show this help message and exit.')
60 1
    parser.add_argument(
61
        'FILE',
62
        type=argparse.FileType("r"),
63
        nargs='?',
64
        default=stdin,
65
        help="ARF (XCCDF) file or stdin if not provided.")
66 1
    parser.add_argument(
67
        "-o",
68
        "--output",
69
        action="store",
70
        type=argparse.FileType("wb+", 0),
71
        default=stdout,
72
        help="write the report to a file instead of the standard output.")
73 1
    parser.add_argument(
74
        "--log-file",
75
        action="store",
76
        default=None,
77
        help="write the log to a file instead of stderr.")
78 1
    parser.add_argument(
79
        "--log-level",
80
        action="store",
81
        default="WARNING",
82
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
83
        help=(
84
            "write debug information to the log up to the LOG_LEVEL."
85
            f"\n{LOG_LEVES_DESCRIPTION}")
86
    )
87 1
    parser.add_argument(
88
        "-f",
89
        "--format",
90
        action="store",
91
        default="HTML",
92
        choices=["HTML", "OLD-STYLE-HTML"],
93
        help="FORMAT: %(choices)s (default: %(default)s)."
94
    )
95 1
    parser.add_argument(
96
        "-d",
97
        "--debug",
98
        action="store",
99
        nargs='+',
100
        default=[""],
101
        choices=[
102
            "NO-MINIFY",
103
            "BUTTON-SHOW-ALL-RULES",
104
            "ONLINE-CSS",
105
            "BUTTON-SHOW-ALL-OVAL-TEST-DETAILS"
106
        ],
107
        help=(
108
            "extra HTML generation options for debugging"
109
            f"{DEBUG_FLAGS_DESCRIPTION}")
110
    )
111 1
    return parser
112
113
114 1
class CommandLineAPI():  # pylint: disable=R0902
115 1
    def __init__(self):
116 1
        self.arguments = prepare_parser().parse_args()
117 1
        self.log_file = self.arguments.log_file
118 1
        self.log_level = self.arguments.log_level
119 1
        self.debug_flags = self.arguments.debug
120 1
        self._setup_logging()
121 1
        logging.debug("Args: %s", self.arguments)
122 1
        self.report_file = self.arguments.FILE
123 1
        self.output_file = self.arguments.output
124 1
        self.output_format = self.arguments.format.upper()
125 1
        self.debug_setting = DebugSetting()
126
127 1
    def _setup_logging(self):
128 1
        logging.basicConfig(
129
            format=MASSAGE_FORMAT,
130
            filename=self.log_file,
131
            filemode='w',
132
            level=self.log_level.upper()
133
        )
134
135 1
    def generate_report(self, report_parser):
136 1
        logging.info("Generate report")
137 1
        if self.output_format == "OLD-STYLE-HTML":
138
            report_generator = OldOSCAPReportGenerator(report_parser)
139
            return report_generator.generate_html_report()
140 1
        report_generator = ReportGenerator(report_parser)
141
142 1
        self.debug_setting.update_settings_with_debug_flags(self.debug_flags)
143
144 1
        return report_generator.generate_html_report(self.debug_setting)
145
146 1
    def load_file(self):
147 1
        logging.info("Loading file: %s", self.report_file)
148 1
        return self.report_file.read().encode()
149
150 1
    def store_file(self, data):
151 1
        logging.info("Store report")
152 1
        if self.output_file.name == "<stdout>":
153
            logging.info("Output is stdout, converting bytes output to str")
154
            data = data.read().decode("utf-8")
155 1
        self.output_file.writelines(data)
156
157 1
    def close_files(self):
158 1
        logging.info("Close files")
159 1
        self.report_file.close()
160 1
        self.output_file.close()
161
162
163 1
@dataclass
164 1
class DebugSetting():
165 1
    no_minify: bool = False
166 1
    options_require_debug_script: tuple = ("BUTTON-SHOW-ALL-RULES", )
167 1
    include_debug_script: bool = False
168 1
    button_show_all_rules: bool = False
169 1
    use_online_css: bool = False
170 1
    button_show_all_oval_test_details: bool = False
171
172 1
    def update_settings_with_debug_flags(self, debug_flags):
173 1
        for flag in debug_flags:
174 1
            if flag in self.options_require_debug_script:
175
                self.include_debug_script = True
176 1
            if flag == "NO-MINIFY":
177
                self.no_minify = True
178 1
            if flag == "BUTTON-SHOW-ALL-RULES":
179
                self.button_show_all_rules = True
180 1
            if flag == "ONLINE-CSS":
181
                self.use_online_css = True
182 1
            if flag == "BUTTON-SHOW-ALL-OVAL-TEST-DETAILS":
183
                self.button_show_all_oval_test_details = True
184
185
186 1
def main():
187
    exit_code = EXIT_SUCCESS_CODE
188
    api = CommandLineAPI()
189
    arf_report = api.load_file()
190
191
    logging.info("Parse file")
192
    try:
193
        parser = SCAPResultsParser(arf_report)
194
195
        report = api.generate_report(parser)
196
197
        api.store_file(report)
198
    except EXPECTED_ERRORS as error:
199
        logging.fatal("%s", error)
200
        exit_code = EXIT_FAILURE_CODE
201
    api.close_files()
202
    sys_exit(exit_code)
203
204
205 1
if __name__ == '__main__':
206
    main()
207