Passed
Pull Request — master (#370)
by Jaspar
01:23
created

create-consolidated-report.gmp.parse_args()   B

Complexity

Conditions 1

Size

Total Lines 55
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 38
nop 1
dl 0
loc 55
rs 8.968
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2021 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: GPL-3.0-or-later
5
#
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19
from uuid import UUID
20
from typing import List
21
from datetime import date
22
from argparse import ArgumentParser, RawTextHelpFormatter
23
from lxml import etree as e
24
from gvm.xml import pretty_print
25
26
from gvmtools.helper import generate_uuid, error_and_exit
27
28
HELP_TEXT = (
29
    'This script creates a consolidated report and imports it to the GSM.'
30
    ' Usable with gvm-script (gvm-tools)'
31
)
32
33
34
def parse_tags(tags: List):
35
    """Parsing and validating the given tags
36
37
    tags (List): A list containing tags:
38
                 name, tag-id, name=value
39
40
    Returns a list containing tag="name", tag_id="id" ...
41
    """
42
    filter_tags = []
43
    for tag in tags:
44
        try:
45
            UUID(tag, version=4)
46
            filter_tags.append('tag_id="{}"'.format(tag))
47
        except ValueError:
48
            filter_tags.append('tag="{}"'.format(tag))
49
50
    return filter_tags
51
52
53
def parse_period(period: List):
54
    """Parsing and validating the given time period
55
56
    period (List): A list with two entries containing
57
                   dates in the format yyyy/mm/dd
58
59
    Returns two date-objects containing the passed dates
60
    """
61
    try:
62
        s_year, s_month, s_day = map(int, period[0].split('/'))
63
    except ValueError as e:
64
        error_and_exit(
65
            'Start date [{}] is not a correct date format:\n{}'.format(
66
                period[0], e.args[0]
67
            )
68
        )
69
    try:
70
        e_year, e_month, e_day = map(int, period[1].split('/'))
71
    except ValueError as e:
72
        error_and_exit(
73
            'End date [{}] is not a correct date format:\n{}'.format(
74
                period[1], e.args[0]
75
            )
76
        )
77
78
    try:
79
        period_start = date(s_year, s_month, s_day)
80
    except ValueError as e:
81
        error_and_exit('Start date: {}'.format(e.args[0]))
82
83
    try:
84
        period_end = date(e_year, e_month, e_day)
85
    except ValueError as e:
86
        error_and_exit('End date: {}'.format(e.args[0]))
87
88
    if period_end < period_start:
89
        error_and_exit('The start date seems to after the end date.')
90
91
    return period_start, period_end
92
93
94
def parse_args(args):  # pylint: disable=unused-argument
95
    """ Parsing args ... """
96
97
    parser = ArgumentParser(
98
        prefix_chars='+',
99
        add_help=False,
100
        formatter_class=RawTextHelpFormatter,
101
        description=HELP_TEXT,
102
    )
103
104
    parser.add_argument(
105
        '+h',
106
        '++help',
107
        action='help',
108
        help='Show this help message and exit.',
109
    )
110
111
    parser.add_argument(
112
        '+p',
113
        '++period',
114
        nargs=2,
115
        type=str,
116
        required=True,
117
        dest='period',
118
        help=(
119
            'Choose a time period that is filtering the tasks.\n'
120
            'Use the date format YYYY/MM/DD.'
121
        ),
122
    )
123
124
    parser.add_argument(
125
        '+t',
126
        '++tags',
127
        nargs='+',
128
        type=str,
129
        dest='tags',
130
        help=(
131
            'Filter the tasks by given tag(s).\n'
132
            'If you pass more than on tag, they will be concatenated with '
133
            or '\n'
134
            'You can pass tag names, tag ids or tag name=value to this argument'
135
        ),
136
    )
137
138
    parser.add_argument(
139
        '+f',
140
        '++filter',
141
        nargs='+',
142
        type=str,
143
        dest='filter',
144
        help='Filter the results by given filter(s).',
145
    )
146
147
    script_args, _ = parser.parse_known_args()
148
    return script_args
149
150
151
def generate_task_filter(period_start, period_end, tags: List):
152
    """Generate the tasks filter
153
154
    period_start: the start date
155
    period_end: the end date
156
    tags: list of tags for the filter
157
158
    Returns an task filter string
159
    """
160
    task_filter = 'rows=-1 '
161
    period_filter = 'created>{0} and created<{1}'.format(
162
        period_start.isoformat(), period_end.isoformat()
163
    )
164
    filter_parts = []
165
    if tags:
166
        for tag in tags:
167
            filter_parts.append('{} and {}'.format(period_filter, tag))
168
169
        tags_filter = ' or '.join(filter_parts)
170
        task_filter += tags_filter
171
    else:
172
        task_filter += period_filter
173
174
    return task_filter
175
176
177
def get_last_reports_from_tasks(gmp, task_filter: str):
178
    """Get the last reports from the tasks in the given time period
179
180
    gmp: the GMP object
181
    task_filter: task filter string
182
183
    """
184
185
    print('Filtering the task with the filter term [{}]'.format(task_filter))
186
187
    tasks_xml = gmp.get_tasks(filter=task_filter)
188
    reports = []
189
    for report in tasks_xml.xpath('task/last_report/report/@id'):
190
        reports.append(str(report))
191
192
    # remove duplicates ... just in case
193
    reports = list(dict.fromkeys(reports))
194
195
    return reports
196
197
198
def combine_reports(gmp, reports: List, filter_term: str):
199
    """Combining the filtered ports, results and hosts of the given
200
    report ids into one new report.
201
202
    gmp: the GMP object
203
    reports (List): List of report_ids
204
    filter_term (str): the result filter string
205
    """
206
207
    new_uuid = generate_uuid()
208
    combined_report = e.Element(
209
        'report',
210
        {
211
            'id': new_uuid,
212
            'format_id': 'd5da9f67-8551-4e51-807b-b6a873d70e34',
213
            'extension': 'xml',
214
            'content_type': 'text/xml',
215
        },
216
    )
217
    report_elem = e.Element('report', {'id': new_uuid})
218
219
    ports_elem = e.Element('ports', {'start': '1', 'max': '-1'})
220
    results_elem = e.Element('results', {'start': '1', 'max': '-1'})
221
    combined_report.append(report_elem)
222
    report_elem.append(ports_elem)
223
    report_elem.append(results_elem)
224
225
    for report in reports:
226
        current_report = gmp.get_report(
227
            report, filter=filter_term, details=True
228
        )[0]
229
        for port in current_report.xpath('report/ports/port'):
230
            ports_elem.append(port)
231
        for result in current_report.xpath('report/results/result'):
232
            results_elem.append(result)
233
        for host in current_report.xpath('host'):
234
            report_elem.append(host)
235
236
    return combined_report
237
238
239
def send_report(gmp, combined_report, period_start, period_end):
240
    """Creating a container task and sending the combined report to the GSM
241
242
    gmp: the GMP object
243
    combined_report: the combined report xml object
244
    period_start: the start date
245
    period_end: the end date
246
    """
247
248
    task_name = 'Consolidated Report [{} - {}]'.format(period_start, period_end)
249
250
    res = gmp.create_container_task(
251
        name=task_name, comment='Created with gvm-tools.'
252
    )
253
254
    task_id = res.xpath('//@id')[0]
255
256
    combined_report = e.tostring(combined_report)
257
258
    res = gmp.import_report(combined_report, task_id=task_id)
259
260
    return res.xpath('//@id')[0]
261
262
263
def main(gmp, args):
264
    # pylint: disable=undefined-variable
265
266
    parsed_args = parse_args(args=args)
267
268
    period_start, period_end = parse_period(period=parsed_args.period)
269
270
    print(
271
        'Combining reports from tasks within the time period [{}, {}]'.format(
272
            period_start, period_end
273
        )
274
    )
275
276
    filter_tags = None
277
    if parsed_args.tags:
278
        filter_tags = parse_tags(tags=parsed_args.tags)
279
280
    # Generate Task Filter
281
    task_filter = generate_task_filter(
282
        period_start=period_start,
283
        period_end=period_end,
284
        tags=filter_tags,
285
    )
286
287
    # Find reports
288
    reports = get_last_reports_from_tasks(gmp=gmp, task_filter=task_filter)
289
290
    print("Combining {} found reports.".format(len(reports)))
291
292
    filter_term = ''
293
    if parsed_args.filter:
294
        filter_term = ' '.join(parsed_args.filter)
295
        print(
296
            'Filtering the results by the following filter term [{}]'.format(
297
                filter_term
298
            )
299
        )
300
    else:
301
        print('No result filter given.')
302
303
    # Combine the reports
304
    combined_report = combine_reports(
305
        gmp=gmp, reports=reports, filter_term=filter_term
306
    )
307
308
    # Import the generated report to GSM
309
    send_report(
310
        gmp=gmp,
311
        combined_report=combined_report,
312
        period_start=period_start,
313
        period_end=period_end,
314
    )
315
316
317
if __name__ == '__gmp__':
318
    main(gmp, args)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable args does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable gmp does not seem to be defined.
Loading history...
319