Passed
Pull Request — master (#93)
by Jan
06:07
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, JSONReportGenerator,
14
                                OldStyleHTMLReportGenerator)
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", "JSON"],
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 get_report_generator(self, report_parser):
140 1
        dict_of_report_generators = {
141
            "HTML": HTMLReportGenerator,
142
            "OLD-STYLE-HTML": OldStyleHTMLReportGenerator,
143
            "JSON": JSONReportGenerator,
144
        }
145 1
        return dict_of_report_generators[self.output_format](report_parser)
146
147 1
    def generate_report(self, report_parser):
148 1
        logging.info("Generate report")
149 1
        report_generator = self.get_report_generator(report_parser)
150
151 1
        self.debug_setting.update_settings_with_debug_flags(self.debug_flags)
152
153 1
        return report_generator.generate_report(self.debug_setting)
154
155 1
    def load_file(self):
156 1
        logging.info("Loading file: %s", self.report_file)
157 1
        return self.report_file.read().encode()
158
159 1
    def store_file(self, data):
160 1
        logging.info("Store report")
161 1
        if self.output_file.name == "<stdout>":
162 1
            logging.info("Output is stdout, converting bytes output to str")
163 1
            data = data.read().decode("utf-8")
164 1
        self.output_file.writelines(data)
165
166 1
    def close_files(self):
167 1
        logging.info("Close files")
168 1
        self.report_file.close()
169 1
        self.output_file.close()
170
171
172 1
def main():
173 1
    exit_code = EXIT_SUCCESS_CODE
174 1
    api = CommandLineAPI()
175 1
    arf_report = api.load_file()
176
177 1
    logging.info("Parse file")
178 1
    try:
179 1
        parser = SCAPResultsParser(arf_report)
180
181 1
        report = api.generate_report(parser)
182
183 1
        api.store_file(report)
184
    except EXPECTED_ERRORS as error:
185
        logging.fatal("%s", error)
186
        exit_code = EXIT_FAILURE_CODE
187 1
    api.close_files()
188 1
    sys_exit(exit_code)
189
190
191 1
if __name__ == '__main__':
192
    main()
193