Passed
Pull Request — master (#103)
by Jan
11:32 queued 05:27
created

CommandLineAPI._setup_logging()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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