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