Passed
Push — main ( 5a7e80...db113e )
by Jan
06:22 queued 14s
created

openscap_report.cli.main()   A

Complexity

Conditions 2

Size

Total Lines 17
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 5.2029

Importance

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