Passed
Pull Request — master (#105)
by Jan
16:46 queued 09:55
created

openscap_report.cli.main()   A

Complexity

Conditions 2

Size

Total Lines 17
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2.0393

Importance

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