Passed
Pull Request — master (#65)
by Jan
23:23 queued 14:42
created

openscap_report.cli.CommandLineAPI.__init__()   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

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