defectdojo-cli2 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- defectdojo_cli2/__init__.py +7 -0
- defectdojo_cli2/__main__.py +45 -0
- defectdojo_cli2/engagements.py +373 -0
- defectdojo_cli2/findings.py +745 -0
- defectdojo_cli2/tests.py +442 -0
- defectdojo_cli2/util.py +28 -0
- defectdojo_cli2-0.0.0.dist-info/AUTHORS +1 -0
- defectdojo_cli2-0.0.0.dist-info/LICENSE +21 -0
- defectdojo_cli2-0.0.0.dist-info/METADATA +20 -0
- defectdojo_cli2-0.0.0.dist-info/RECORD +14 -0
- defectdojo_cli2-0.0.0.dist-info/WHEEL +5 -0
- defectdojo_cli2-0.0.0.dist-info/entry_points.txt +2 -0
- defectdojo_cli2-0.0.0.dist-info/pbr.json +1 -0
- defectdojo_cli2-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import argparse
|
|
3
|
+
from defectdojo_cli2 import Findings
|
|
4
|
+
from defectdojo_cli2 import Engagements
|
|
5
|
+
from defectdojo_cli2 import Tests
|
|
6
|
+
from defectdojo_cli2 import __version__
|
|
7
|
+
|
|
8
|
+
# Multilevel argparse based on https://chase-seibert.github.io/blog/2014/03/21/python-multilevel-argparse.html
|
|
9
|
+
class DefectDojoCLI(object):
|
|
10
|
+
def parse_cli_args(self):
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
description='CLI wrapper for DefectDojo using APIv2',
|
|
13
|
+
usage='''defectdojo <command> [<args>]
|
|
14
|
+
|
|
15
|
+
You can use the following commands:
|
|
16
|
+
findings Operations related to findings (findings --help for more details)
|
|
17
|
+
engagements Operations related to engagements (engagements --help for more details)
|
|
18
|
+
tests Operations related to tests (tests --help for more details)
|
|
19
|
+
''')
|
|
20
|
+
parser.add_argument('command', help='Command to run')
|
|
21
|
+
parser.add_argument('-v', '--version', action='version', version='%(prog)s_cli v' + __version__)
|
|
22
|
+
# Parse_args defaults to [1:] for args, but you need to
|
|
23
|
+
# exclude the rest of the args too, or validation will fail
|
|
24
|
+
args = parser.parse_args(sys.argv[1:2])
|
|
25
|
+
if not hasattr(self, '_'+args.command):
|
|
26
|
+
print('Unrecognized command')
|
|
27
|
+
parser.print_help()
|
|
28
|
+
exit(1)
|
|
29
|
+
# Use dispatch pattern to invoke method with same name (that starts with _)
|
|
30
|
+
getattr(self, '_'+args.command)()
|
|
31
|
+
|
|
32
|
+
def _findings(self):
|
|
33
|
+
Findings().parse_cli_args()
|
|
34
|
+
|
|
35
|
+
def _engagements(self):
|
|
36
|
+
Engagements().parse_cli_args()
|
|
37
|
+
|
|
38
|
+
def _tests(self):
|
|
39
|
+
Tests().parse_cli_args()
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
DefectDojoCLI().parse_cli_args()
|
|
43
|
+
|
|
44
|
+
if __name__ == '__main__':
|
|
45
|
+
main()
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import argparse
|
|
5
|
+
import requests
|
|
6
|
+
from unittest.mock import PropertyMock
|
|
7
|
+
from defectdojo_cli2.util import Util
|
|
8
|
+
from defectdojo_cli2.tests import Tests
|
|
9
|
+
|
|
10
|
+
class Engagements(object):
|
|
11
|
+
def parse_cli_args(self):
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
description='Perform <sub_command> related to engagements on DefectDojo',
|
|
14
|
+
usage='''defectdojo engagements <sub_command> [<args>]
|
|
15
|
+
|
|
16
|
+
You can use the following sub_commands:
|
|
17
|
+
create Create an engagement (engagements create --help for more details)
|
|
18
|
+
close Close an engagement (engagements close --help for more details)
|
|
19
|
+
update Update an engagement (engagements update --help for more details)
|
|
20
|
+
''')
|
|
21
|
+
parser.add_argument('sub_command', help='Sub_command to run')
|
|
22
|
+
# Get sub_command
|
|
23
|
+
args = parser.parse_args(sys.argv[2:3])
|
|
24
|
+
if not hasattr(self, '_'+args.sub_command):
|
|
25
|
+
print('Unrecognized sub_command')
|
|
26
|
+
parser.print_help()
|
|
27
|
+
exit(1)
|
|
28
|
+
# Use dispatch pattern to invoke method with same name (that starts with _)
|
|
29
|
+
getattr(self, '_'+args.sub_command)()
|
|
30
|
+
|
|
31
|
+
def create(self, url, api_key, name, desc, product_id, lead_id,
|
|
32
|
+
start_date=None, end_date=None, engagement_type=None,
|
|
33
|
+
status=None, build_id=None, repo_url=None, branch_tag=None,
|
|
34
|
+
commit_hash=None, product_version=None, tracker=None,
|
|
35
|
+
tag=None, local_dedup=None, **kwargs):
|
|
36
|
+
# Prepare JSON data to be send
|
|
37
|
+
request_json = dict()
|
|
38
|
+
API_URL = url+'/api/v2'
|
|
39
|
+
ENGAGEMENTS_URL = API_URL+'/engagements/'
|
|
40
|
+
request_json['name'] = name
|
|
41
|
+
request_json['description'] = desc
|
|
42
|
+
request_json['product'] = product_id
|
|
43
|
+
request_json['lead'] = lead_id
|
|
44
|
+
if start_date is not None:
|
|
45
|
+
request_json['target_start'] = start_date
|
|
46
|
+
if end_date is not None:
|
|
47
|
+
request_json['target_end'] = end_date
|
|
48
|
+
if engagement_type is not None:
|
|
49
|
+
request_json['engagement_type'] = engagement_type
|
|
50
|
+
if status is not None:
|
|
51
|
+
request_json['status'] = status
|
|
52
|
+
if build_id is not None:
|
|
53
|
+
request_json['build_id'] = build_id
|
|
54
|
+
if repo_url is not None:
|
|
55
|
+
request_json['source_code_management_uri'] = repo_url
|
|
56
|
+
if branch_tag is not None:
|
|
57
|
+
request_json['branch_tag'] = branch_tag
|
|
58
|
+
if commit_hash is not None:
|
|
59
|
+
request_json['commit_hash'] = commit_hash
|
|
60
|
+
if product_version is not None:
|
|
61
|
+
request_json['version'] = product_version
|
|
62
|
+
if tracker is not None:
|
|
63
|
+
request_json['tracker'] = tracker
|
|
64
|
+
if tag is not None:
|
|
65
|
+
request_json['tags'] = tag
|
|
66
|
+
if local_dedup is not None:
|
|
67
|
+
if local_dedup == 'true':
|
|
68
|
+
request_json['deduplication_on_engagement'] = True
|
|
69
|
+
else:
|
|
70
|
+
request_json['deduplication_on_engagement'] = False
|
|
71
|
+
request_json = json.dumps(request_json)
|
|
72
|
+
|
|
73
|
+
# Make the request
|
|
74
|
+
response = Util().request_apiv2('POST', ENGAGEMENTS_URL, api_key, data=request_json)
|
|
75
|
+
return response
|
|
76
|
+
|
|
77
|
+
def _create(self):
|
|
78
|
+
# Read user-supplied arguments
|
|
79
|
+
parser = argparse.ArgumentParser(description='Create an engagement on DefectDojo',
|
|
80
|
+
usage='defectdojo engagements create [<args>]')
|
|
81
|
+
optional = parser._action_groups.pop()
|
|
82
|
+
required = parser.add_argument_group('required arguments')
|
|
83
|
+
required.add_argument('--url',
|
|
84
|
+
help='DefectDojo URL', required=True)
|
|
85
|
+
required.add_argument('--api_key',
|
|
86
|
+
help='API v2 Key', required=True)
|
|
87
|
+
required.add_argument('--name',
|
|
88
|
+
help='Engagement name', required=True)
|
|
89
|
+
required.add_argument('--desc',
|
|
90
|
+
help='Engagement description',
|
|
91
|
+
required=True, metavar='DESCRIPTION')
|
|
92
|
+
required.add_argument('--product_id',
|
|
93
|
+
help='ID of the product on which the engagement will be created',
|
|
94
|
+
required=True)
|
|
95
|
+
required.add_argument('--lead_id',
|
|
96
|
+
help='ID of the user responsible for this engagement',
|
|
97
|
+
required=True)
|
|
98
|
+
optional.add_argument('--start_date',
|
|
99
|
+
help='Engagement starting date (default=TODAY)',
|
|
100
|
+
metavar='YYYY-MM-DD', default=datetime.now().strftime('%Y-%m-%d'))
|
|
101
|
+
optional.add_argument('--end_date',
|
|
102
|
+
help='Engagement ending date (default=TODAY)',
|
|
103
|
+
metavar='YYYY-MM-DD', default=datetime.now().strftime('%Y-%m-%d'))
|
|
104
|
+
optional.add_argument('--type',
|
|
105
|
+
help='Engagement type (default = "CI/CD")',
|
|
106
|
+
choices=['Interactive', 'CI/CD'], default='CI/CD')
|
|
107
|
+
optional.add_argument('--status',
|
|
108
|
+
help='Engagement status (default = "In Progress")',
|
|
109
|
+
choices=['Not Started', 'Blocked', 'Cancelled',
|
|
110
|
+
'Completed', 'In Progress', 'On Hold',
|
|
111
|
+
'Waiting for Resource'],
|
|
112
|
+
default='In Progress')
|
|
113
|
+
optional.add_argument('--build_id',
|
|
114
|
+
help='Build ID')
|
|
115
|
+
optional.add_argument('--repo_url',
|
|
116
|
+
help='Link to source code management')
|
|
117
|
+
optional.add_argument('--branch_tag',
|
|
118
|
+
help='Tag or branch of the product the engagement tested',
|
|
119
|
+
metavar='TAG_OR_BRANCH')
|
|
120
|
+
optional.add_argument('--commit_hash',
|
|
121
|
+
help='Commit HASH')
|
|
122
|
+
optional.add_argument('--product_version',
|
|
123
|
+
help='Version of the product the engagement tested')
|
|
124
|
+
optional.add_argument('--tracker',
|
|
125
|
+
help='Link to epic or ticket system with changes to version.')
|
|
126
|
+
optional.add_argument(
|
|
127
|
+
'--tag',
|
|
128
|
+
help='Engagement tag (can be used multiple times)',
|
|
129
|
+
action='append'
|
|
130
|
+
)
|
|
131
|
+
optional.add_argument(
|
|
132
|
+
'--local_dedup',
|
|
133
|
+
help='If enabled deduplication will only mark a finding in '
|
|
134
|
+
'this engagement as duplicate of another finding if both '
|
|
135
|
+
'findings are in this engagement. If disabled, deduplication '
|
|
136
|
+
'is on the product level. (default = false)',
|
|
137
|
+
choices=['true', 'false'],
|
|
138
|
+
default='false'
|
|
139
|
+
)
|
|
140
|
+
parser._action_groups.append(optional)
|
|
141
|
+
# Parse out arguments ignoring the first three (because we're inside a sub_command)
|
|
142
|
+
args = vars(parser.parse_args(sys.argv[3:]))
|
|
143
|
+
|
|
144
|
+
# Adjust args
|
|
145
|
+
if args['type'] is not None:
|
|
146
|
+
# Rename key from 'type' to 'engagement_type' to match the argument of self.create
|
|
147
|
+
args['engagement_type'] = args.pop('type')
|
|
148
|
+
|
|
149
|
+
# Create engagement
|
|
150
|
+
response = self.create(**args)
|
|
151
|
+
|
|
152
|
+
# Pretty print JSON response
|
|
153
|
+
Util().default_output(response, sucess_status_code=201)
|
|
154
|
+
|
|
155
|
+
def close(self, url, api_key, engagement_id, **kwargs):
|
|
156
|
+
# Prepare parameters
|
|
157
|
+
API_URL = url+'/api/v2'
|
|
158
|
+
ENGAGEMENTS_URL = API_URL+'/engagements/'
|
|
159
|
+
ENGAGEMENTS_ID_URL = ENGAGEMENTS_URL+engagement_id
|
|
160
|
+
ENGAGEMENTS_CLOSE_URL = ENGAGEMENTS_ID_URL+'/close/'
|
|
161
|
+
# Make the request
|
|
162
|
+
response = Util().request_apiv2('POST', ENGAGEMENTS_CLOSE_URL, api_key)
|
|
163
|
+
return response
|
|
164
|
+
|
|
165
|
+
def _close(self):
|
|
166
|
+
# Read user-supplied arguments
|
|
167
|
+
parser = argparse.ArgumentParser(description='Close an engagement on DefectDojo',
|
|
168
|
+
usage='defectdojo engagements close ENGAGEMENT_ID')
|
|
169
|
+
required = parser.add_argument_group('required arguments')
|
|
170
|
+
parser.add_argument('engagement_id', help='ID of the engagement to be closed')
|
|
171
|
+
required.add_argument('--url', help='DefectDojo URL', required=True)
|
|
172
|
+
required.add_argument('--api_key', help='API v2 Key', required=True)
|
|
173
|
+
# Parse out arguments ignoring the first three (because we're inside a sub_command)
|
|
174
|
+
args = vars(parser.parse_args(sys.argv[3:]))
|
|
175
|
+
|
|
176
|
+
# Close engagement
|
|
177
|
+
response = self.close(**args)
|
|
178
|
+
|
|
179
|
+
# DefectDojo doesnt has an output when a engagement is successfully closed so we need to create one
|
|
180
|
+
if response.status_code == 200:
|
|
181
|
+
type(response).text = PropertyMock(return_value='{"return": "sucess"}')
|
|
182
|
+
# Pretty print JSON response
|
|
183
|
+
Util().default_output(response, sucess_status_code=200)
|
|
184
|
+
|
|
185
|
+
def update(self, url, api_key, engagement_id, name=None, desc=None, product_id=None, lead_id=None,
|
|
186
|
+
start_date=None, end_date=None, engagement_type=None, repo_url=None, branch_tag=None,
|
|
187
|
+
product_version=None, status=None, **kwargs):
|
|
188
|
+
# Prepare JSON data to be send
|
|
189
|
+
request_json = dict()
|
|
190
|
+
API_URL = url+'/api/v2'
|
|
191
|
+
ENGAGEMENTS_URL = API_URL+'/engagements/'
|
|
192
|
+
ENGAGEMENTS_ID_URL = ENGAGEMENTS_URL+engagement_id+'/'
|
|
193
|
+
if name is not None:
|
|
194
|
+
request_json['name'] = name
|
|
195
|
+
if desc is not None:
|
|
196
|
+
request_json['description'] = desc
|
|
197
|
+
if product_id is not None:
|
|
198
|
+
request_json['product'] = product_id
|
|
199
|
+
if lead_id is not None:
|
|
200
|
+
request_json['lead'] = lead_id
|
|
201
|
+
if start_date is not None:
|
|
202
|
+
request_json['target_start'] = start_date
|
|
203
|
+
if end_date is not None:
|
|
204
|
+
request_json['target_end'] = end_date
|
|
205
|
+
if engagement_type is not None:
|
|
206
|
+
request_json['engagement_type'] = engagement_type
|
|
207
|
+
if repo_url is not None:
|
|
208
|
+
request_json['source_code_management_uri'] = repo_url
|
|
209
|
+
if branch_tag is not None:
|
|
210
|
+
request_json['branch_tag'] = branch_tag
|
|
211
|
+
if product_version is not None:
|
|
212
|
+
request_json['version'] = product_version
|
|
213
|
+
if status is not None:
|
|
214
|
+
request_json['status'] = status
|
|
215
|
+
request_json = json.dumps(request_json)
|
|
216
|
+
|
|
217
|
+
# Make the request
|
|
218
|
+
response = Util().request_apiv2('PATCH', ENGAGEMENTS_ID_URL, api_key, data=request_json)
|
|
219
|
+
return response
|
|
220
|
+
|
|
221
|
+
def _update(self):
|
|
222
|
+
# Read user-supplied arguments
|
|
223
|
+
parser = argparse.ArgumentParser(description='Update a engagement on DefectDojo',
|
|
224
|
+
usage='defectdojo engagements update ENGAGEMENT_ID [<args>]')
|
|
225
|
+
optional = parser._action_groups.pop()
|
|
226
|
+
required = parser.add_argument_group('required arguments')
|
|
227
|
+
parser.add_argument('engagement_id', help='ID of the engagement to be updated')
|
|
228
|
+
required.add_argument('--url', help='DefectDojo URL', required=True)
|
|
229
|
+
required.add_argument('--api_key', help='API v2 Key', required=True)
|
|
230
|
+
optional.add_argument('--name', help='Engagement name')
|
|
231
|
+
optional.add_argument('--desc', help='Engagement description', metavar='DESCRIPTION')
|
|
232
|
+
optional.add_argument('--product_id', help='ID of the product the engagement belongs to')
|
|
233
|
+
optional.add_argument('--lead_id', help='ID of the user responsible for this engagement')
|
|
234
|
+
optional.add_argument('--start_date', help='Engagement starting date', metavar='YYYY-MM-DD')
|
|
235
|
+
optional.add_argument('--end_date', help='Engagement ending date', metavar='YYYY-MM-DD')
|
|
236
|
+
optional.add_argument('--type', help='Engagement type', choices=['Interactive', 'CI/CD'])
|
|
237
|
+
optional.add_argument('--repo_url', help='Link to source code')
|
|
238
|
+
optional.add_argument('--branch_tag', help='Tag or branch of the product the engagement tested',
|
|
239
|
+
metavar='TAG_OR_BRANCH')
|
|
240
|
+
optional.add_argument('--product_version', help='Version of the product the engagement tested')
|
|
241
|
+
optional.add_argument('--status', help='Engagement status',
|
|
242
|
+
choices=['Not Started', 'Blocked', 'Cancelled', 'Completed', 'In Progress',
|
|
243
|
+
'On Hold', 'Waiting for Resource'])
|
|
244
|
+
parser._action_groups.append(optional)
|
|
245
|
+
# Parse out arguments ignoring the first three (because we're inside a sub_command)
|
|
246
|
+
args = vars(parser.parse_args(sys.argv[3:]))
|
|
247
|
+
|
|
248
|
+
# Update engagement
|
|
249
|
+
response = self.update(**args)
|
|
250
|
+
|
|
251
|
+
# Pretty print JSON response
|
|
252
|
+
Util().default_output(response, sucess_status_code=200)
|
|
253
|
+
|
|
254
|
+
def list(self, url, api_key, name=None, product_id=None, **kwargs):
|
|
255
|
+
# Create parameters to be requested
|
|
256
|
+
request_params = dict()
|
|
257
|
+
API_URL = url+'/api/v2'
|
|
258
|
+
ENGAGEMENTS_URL = API_URL+'/engagements/'
|
|
259
|
+
if name is not None:
|
|
260
|
+
request_params['name'] = name
|
|
261
|
+
if product_id is not None:
|
|
262
|
+
request_params['product'] = product_id
|
|
263
|
+
|
|
264
|
+
# Make the request
|
|
265
|
+
response = Util().request_apiv2('GET', ENGAGEMENTS_URL, api_key, params=request_params)
|
|
266
|
+
return response
|
|
267
|
+
|
|
268
|
+
def _list(self):
|
|
269
|
+
# Read user-supplied arguments
|
|
270
|
+
parser = argparse.ArgumentParser(description='List an engagement on DefectDojo',
|
|
271
|
+
usage='defectdojo engagements list [<args>]')
|
|
272
|
+
optional = parser._action_groups.pop()
|
|
273
|
+
required = parser.add_argument_group('required arguments')
|
|
274
|
+
required.add_argument(
|
|
275
|
+
'--url',
|
|
276
|
+
help='DefectDojo URL',
|
|
277
|
+
required=True
|
|
278
|
+
)
|
|
279
|
+
required.add_argument(
|
|
280
|
+
'--api_key',
|
|
281
|
+
help='API v2 Key',
|
|
282
|
+
required=True
|
|
283
|
+
)
|
|
284
|
+
optional.add_argument(
|
|
285
|
+
'--name',
|
|
286
|
+
help='Engagement name'
|
|
287
|
+
)
|
|
288
|
+
optional.add_argument(
|
|
289
|
+
'--product_id',
|
|
290
|
+
help='Product ID'
|
|
291
|
+
)
|
|
292
|
+
parser._action_groups.append(optional)
|
|
293
|
+
# Parse out arguments ignoring the first three (because we're inside a sub_command)
|
|
294
|
+
args = vars(parser.parse_args(sys.argv[3:]))
|
|
295
|
+
|
|
296
|
+
# Update engagement
|
|
297
|
+
response = self.list(**args)
|
|
298
|
+
|
|
299
|
+
# Pretty print JSON response
|
|
300
|
+
Util().default_output(response, sucess_status_code=200)
|
|
301
|
+
|
|
302
|
+
def reopen(self, url, api_key, engagement_id, **kwargs):
|
|
303
|
+
# Prepare parameters
|
|
304
|
+
API_URL = url+'/api/v2'
|
|
305
|
+
ENGAGEMENTS_URL = API_URL+'/engagements/'
|
|
306
|
+
ENGAGEMENTS_ID_URL = ENGAGEMENTS_URL+engagement_id
|
|
307
|
+
ENGAGEMENTS_CLOSE_URL = ENGAGEMENTS_ID_URL+'/reopen/'
|
|
308
|
+
# Make the request
|
|
309
|
+
response = Util().request_apiv2('POST', ENGAGEMENTS_CLOSE_URL, api_key)
|
|
310
|
+
return response
|
|
311
|
+
|
|
312
|
+
def _reopen(self):
|
|
313
|
+
# Read user-supplied arguments
|
|
314
|
+
parser = argparse.ArgumentParser(description='Reopen an engagement on DefectDojo',
|
|
315
|
+
usage='defectdojo engagements reopen ENGAGEMENT_ID')
|
|
316
|
+
required = parser.add_argument_group('required arguments')
|
|
317
|
+
parser.add_argument('engagement_id', help='ID of the engagement to be reopened')
|
|
318
|
+
required.add_argument('--url', help='DefectDojo URL', required=True)
|
|
319
|
+
required.add_argument('--api_key', help='API v2 Key', required=True)
|
|
320
|
+
# Parse out arguments ignoring the first three (because we're inside a sub_command)
|
|
321
|
+
args = vars(parser.parse_args(sys.argv[3:]))
|
|
322
|
+
|
|
323
|
+
# Close engagement
|
|
324
|
+
response = self.reopen(**args)
|
|
325
|
+
|
|
326
|
+
# DefectDojo doesnt has an output when a engagement is successfully reopened so we need to create one
|
|
327
|
+
if response.status_code == 200:
|
|
328
|
+
type(response).text = PropertyMock(return_value='{"return": "sucess"}')
|
|
329
|
+
# Pretty print JSON response
|
|
330
|
+
Util().default_output(response, sucess_status_code=200)
|
|
331
|
+
|
|
332
|
+
def get_engagements_by_test_tags(self, url, api_key, tags, tags_operator):
|
|
333
|
+
request_params = dict()
|
|
334
|
+
request_params['url'] = url
|
|
335
|
+
request_params['api_key'] = api_key
|
|
336
|
+
|
|
337
|
+
if tags_operator == 'union': # Default behaviour
|
|
338
|
+
# Make a request to API to list all tests with the tags we're looking for
|
|
339
|
+
request_params['tag'] = tags
|
|
340
|
+
response = Tests().list(**request_params)
|
|
341
|
+
# Parse output
|
|
342
|
+
json_out = json.loads(response.text)
|
|
343
|
+
results = json_out['results']
|
|
344
|
+
# Create set of all engagements from the response
|
|
345
|
+
engagement_set = set()
|
|
346
|
+
for test in results:
|
|
347
|
+
engagement_set.add(str(test['engagement']))
|
|
348
|
+
# Transform set to list
|
|
349
|
+
engagement_list = list(engagement_set)
|
|
350
|
+
|
|
351
|
+
elif tags_operator == 'intersect':
|
|
352
|
+
engagement_list_of_sets = list()
|
|
353
|
+
for tag in tags:
|
|
354
|
+
# Make a request to API to list all tests with the tags we're looking for
|
|
355
|
+
request_params['tag'] = tag
|
|
356
|
+
response = Tests().list(**request_params)
|
|
357
|
+
# Parse output
|
|
358
|
+
json_out = json.loads(response.text)
|
|
359
|
+
results = json_out['results']
|
|
360
|
+
# Create set of all engagements from the response
|
|
361
|
+
engagement_set = set()
|
|
362
|
+
for test in results:
|
|
363
|
+
engagement_set.add(str(test['engagement']))
|
|
364
|
+
# Add set of engagement to list
|
|
365
|
+
engagement_list_of_sets.append(engagement_set)
|
|
366
|
+
|
|
367
|
+
# Get intersection between all sets
|
|
368
|
+
engagement_intersection = engagement_list_of_sets[0]
|
|
369
|
+
for engagement_set in engagement_list_of_sets[1:]:
|
|
370
|
+
engagement_intersection.intersection_update(engagement_set)
|
|
371
|
+
engagement_list = engagement_intersection
|
|
372
|
+
|
|
373
|
+
return list(engagement_list)
|