secator 0.22.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.
- secator/.gitignore +162 -0
- secator/__init__.py +0 -0
- secator/celery.py +453 -0
- secator/celery_signals.py +138 -0
- secator/celery_utils.py +320 -0
- secator/cli.py +2035 -0
- secator/cli_helper.py +395 -0
- secator/click.py +87 -0
- secator/config.py +670 -0
- secator/configs/__init__.py +0 -0
- secator/configs/profiles/__init__.py +0 -0
- secator/configs/profiles/aggressive.yaml +8 -0
- secator/configs/profiles/all_ports.yaml +7 -0
- secator/configs/profiles/full.yaml +31 -0
- secator/configs/profiles/http_headless.yaml +7 -0
- secator/configs/profiles/http_record.yaml +8 -0
- secator/configs/profiles/insane.yaml +8 -0
- secator/configs/profiles/paranoid.yaml +8 -0
- secator/configs/profiles/passive.yaml +11 -0
- secator/configs/profiles/polite.yaml +8 -0
- secator/configs/profiles/sneaky.yaml +8 -0
- secator/configs/profiles/tor.yaml +5 -0
- secator/configs/scans/__init__.py +0 -0
- secator/configs/scans/domain.yaml +31 -0
- secator/configs/scans/host.yaml +23 -0
- secator/configs/scans/network.yaml +30 -0
- secator/configs/scans/subdomain.yaml +27 -0
- secator/configs/scans/url.yaml +19 -0
- secator/configs/workflows/__init__.py +0 -0
- secator/configs/workflows/cidr_recon.yaml +48 -0
- secator/configs/workflows/code_scan.yaml +29 -0
- secator/configs/workflows/domain_recon.yaml +46 -0
- secator/configs/workflows/host_recon.yaml +95 -0
- secator/configs/workflows/subdomain_recon.yaml +120 -0
- secator/configs/workflows/url_bypass.yaml +15 -0
- secator/configs/workflows/url_crawl.yaml +98 -0
- secator/configs/workflows/url_dirsearch.yaml +62 -0
- secator/configs/workflows/url_fuzz.yaml +68 -0
- secator/configs/workflows/url_params_fuzz.yaml +66 -0
- secator/configs/workflows/url_secrets_hunt.yaml +23 -0
- secator/configs/workflows/url_vuln.yaml +91 -0
- secator/configs/workflows/user_hunt.yaml +29 -0
- secator/configs/workflows/wordpress.yaml +38 -0
- secator/cve.py +718 -0
- secator/decorators.py +7 -0
- secator/definitions.py +168 -0
- secator/exporters/__init__.py +14 -0
- secator/exporters/_base.py +3 -0
- secator/exporters/console.py +10 -0
- secator/exporters/csv.py +37 -0
- secator/exporters/gdrive.py +123 -0
- secator/exporters/json.py +16 -0
- secator/exporters/table.py +36 -0
- secator/exporters/txt.py +28 -0
- secator/hooks/__init__.py +0 -0
- secator/hooks/gcs.py +80 -0
- secator/hooks/mongodb.py +281 -0
- secator/installer.py +694 -0
- secator/loader.py +128 -0
- secator/output_types/__init__.py +49 -0
- secator/output_types/_base.py +108 -0
- secator/output_types/certificate.py +78 -0
- secator/output_types/domain.py +50 -0
- secator/output_types/error.py +42 -0
- secator/output_types/exploit.py +58 -0
- secator/output_types/info.py +24 -0
- secator/output_types/ip.py +47 -0
- secator/output_types/port.py +55 -0
- secator/output_types/progress.py +36 -0
- secator/output_types/record.py +36 -0
- secator/output_types/stat.py +41 -0
- secator/output_types/state.py +29 -0
- secator/output_types/subdomain.py +45 -0
- secator/output_types/tag.py +69 -0
- secator/output_types/target.py +38 -0
- secator/output_types/url.py +112 -0
- secator/output_types/user_account.py +41 -0
- secator/output_types/vulnerability.py +101 -0
- secator/output_types/warning.py +30 -0
- secator/report.py +140 -0
- secator/rich.py +130 -0
- secator/runners/__init__.py +14 -0
- secator/runners/_base.py +1240 -0
- secator/runners/_helpers.py +218 -0
- secator/runners/celery.py +18 -0
- secator/runners/command.py +1178 -0
- secator/runners/python.py +126 -0
- secator/runners/scan.py +87 -0
- secator/runners/task.py +81 -0
- secator/runners/workflow.py +168 -0
- secator/scans/__init__.py +29 -0
- secator/serializers/__init__.py +8 -0
- secator/serializers/dataclass.py +39 -0
- secator/serializers/json.py +45 -0
- secator/serializers/regex.py +25 -0
- secator/tasks/__init__.py +8 -0
- secator/tasks/_categories.py +487 -0
- secator/tasks/arjun.py +113 -0
- secator/tasks/arp.py +53 -0
- secator/tasks/arpscan.py +70 -0
- secator/tasks/bbot.py +372 -0
- secator/tasks/bup.py +118 -0
- secator/tasks/cariddi.py +193 -0
- secator/tasks/dalfox.py +87 -0
- secator/tasks/dirsearch.py +84 -0
- secator/tasks/dnsx.py +186 -0
- secator/tasks/feroxbuster.py +93 -0
- secator/tasks/ffuf.py +135 -0
- secator/tasks/fping.py +85 -0
- secator/tasks/gau.py +102 -0
- secator/tasks/getasn.py +60 -0
- secator/tasks/gf.py +36 -0
- secator/tasks/gitleaks.py +96 -0
- secator/tasks/gospider.py +84 -0
- secator/tasks/grype.py +109 -0
- secator/tasks/h8mail.py +75 -0
- secator/tasks/httpx.py +167 -0
- secator/tasks/jswhois.py +36 -0
- secator/tasks/katana.py +203 -0
- secator/tasks/maigret.py +87 -0
- secator/tasks/mapcidr.py +42 -0
- secator/tasks/msfconsole.py +179 -0
- secator/tasks/naabu.py +85 -0
- secator/tasks/nmap.py +487 -0
- secator/tasks/nuclei.py +151 -0
- secator/tasks/search_vulns.py +225 -0
- secator/tasks/searchsploit.py +109 -0
- secator/tasks/sshaudit.py +299 -0
- secator/tasks/subfinder.py +48 -0
- secator/tasks/testssl.py +283 -0
- secator/tasks/trivy.py +130 -0
- secator/tasks/trufflehog.py +240 -0
- secator/tasks/urlfinder.py +100 -0
- secator/tasks/wafw00f.py +106 -0
- secator/tasks/whois.py +34 -0
- secator/tasks/wpprobe.py +116 -0
- secator/tasks/wpscan.py +202 -0
- secator/tasks/x8.py +94 -0
- secator/tasks/xurlfind3r.py +83 -0
- secator/template.py +294 -0
- secator/thread.py +24 -0
- secator/tree.py +196 -0
- secator/utils.py +922 -0
- secator/utils_test.py +297 -0
- secator/workflows/__init__.py +29 -0
- secator-0.22.0.dist-info/METADATA +447 -0
- secator-0.22.0.dist-info/RECORD +150 -0
- secator-0.22.0.dist-info/WHEEL +4 -0
- secator-0.22.0.dist-info/entry_points.txt +2 -0
- secator-0.22.0.dist-info/licenses/LICENSE +60 -0
secator/utils_test.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
import unittest.mock
|
|
7
|
+
|
|
8
|
+
from fp.fp import FreeProxy
|
|
9
|
+
|
|
10
|
+
from secator.definitions import (CIDR_RANGE, DELAY, DEPTH, EMAIL,
|
|
11
|
+
FOLLOW_REDIRECT, HEADER, HOST, IP, MATCH_CODES,
|
|
12
|
+
METHOD, PROXY, RATE_LIMIT, RETRIES,
|
|
13
|
+
THREADS, TIMEOUT, URL, USER_AGENT, USERNAME, PATH,
|
|
14
|
+
DOCKER_IMAGE, GIT_REPOSITORY)
|
|
15
|
+
from secator.loader import get_configs_by_type
|
|
16
|
+
from secator.output_types import EXECUTION_TYPES, STAT_TYPES
|
|
17
|
+
from secator.runners import Command, Task
|
|
18
|
+
from secator.rich import console
|
|
19
|
+
from secator.utils import load_fixture, debug, traceback_as_string
|
|
20
|
+
|
|
21
|
+
#---------#
|
|
22
|
+
# GLOBALS #
|
|
23
|
+
#---------#
|
|
24
|
+
USE_PROXY = bool(int(os.environ.get('USE_PROXY', '0')))
|
|
25
|
+
TEST_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/tests/'
|
|
26
|
+
FIXTURES_DIR = f'{TEST_DIR}/fixtures'
|
|
27
|
+
USE_PROXY = bool(int(os.environ.get('USE_PROXY', '0')))
|
|
28
|
+
TASKS = get_configs_by_type('task')
|
|
29
|
+
WORKFLOWS = get_configs_by_type('workflow')
|
|
30
|
+
SCANS = get_configs_by_type('scan')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#------------#
|
|
34
|
+
# TEST TASKS #
|
|
35
|
+
#------------#
|
|
36
|
+
TEST_TASKS = os.environ.get('TEST_TASKS', '')
|
|
37
|
+
if TEST_TASKS:
|
|
38
|
+
TEST_TASKS = [config for config in TASKS if config.name in TEST_TASKS.split(',')]
|
|
39
|
+
else:
|
|
40
|
+
TEST_TASKS = TASKS
|
|
41
|
+
|
|
42
|
+
#----------------#
|
|
43
|
+
# TEST WORKFLOWS #
|
|
44
|
+
#----------------#
|
|
45
|
+
TEST_WORKFLOWS = os.environ.get('TEST_WORKFLOWS', '')
|
|
46
|
+
if TEST_WORKFLOWS:
|
|
47
|
+
TEST_WORKFLOWS = [config for config in WORKFLOWS if config.name in TEST_WORKFLOWS.split(',')]
|
|
48
|
+
else:
|
|
49
|
+
TEST_WORKFLOWS = WORKFLOWS
|
|
50
|
+
|
|
51
|
+
#------------#
|
|
52
|
+
# TEST SCANS #
|
|
53
|
+
#------------#
|
|
54
|
+
TEST_SCANS = os.environ.get('TEST_SCANS', '')
|
|
55
|
+
if TEST_SCANS:
|
|
56
|
+
TEST_SCANS = [config for config in SCANS if config.name in TEST_SCANS.split(',')]
|
|
57
|
+
else:
|
|
58
|
+
TEST_SCANS = SCANS
|
|
59
|
+
|
|
60
|
+
#-------------------#
|
|
61
|
+
# TEST INPUTS_TASKS #
|
|
62
|
+
#-------------------#
|
|
63
|
+
INPUTS_TASKS = {
|
|
64
|
+
URL: 'https://fake.com',
|
|
65
|
+
HOST: 'fake.com',
|
|
66
|
+
USERNAME: 'test',
|
|
67
|
+
IP: '192.168.1.23',
|
|
68
|
+
CIDR_RANGE: '192.168.1.0/24',
|
|
69
|
+
EMAIL: 'fake@fake.com',
|
|
70
|
+
PATH: '.',
|
|
71
|
+
DOCKER_IMAGE: 'redis:latest',
|
|
72
|
+
GIT_REPOSITORY: 'https://github.com/freelabz/secator',
|
|
73
|
+
'gf': 'http://testphp.vulnweb.com/hpp?pp=1',
|
|
74
|
+
'maigret': 'Linus__Torvalds',
|
|
75
|
+
'searchsploit': 'apache',
|
|
76
|
+
'search_vulns': 'apache 2.4.39',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#---------------------#
|
|
80
|
+
# TEST FIXTURES_TASKS #
|
|
81
|
+
#---------------------#
|
|
82
|
+
FIXTURES_TASKS = {
|
|
83
|
+
Task.get_task_class(task.name): load_fixture(f'{task.name}_output', FIXTURES_DIR)
|
|
84
|
+
for task in TASKS
|
|
85
|
+
if task.name in [t.name for t in TEST_TASKS]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#-----------#
|
|
89
|
+
# TEST OPTS #
|
|
90
|
+
#-----------#
|
|
91
|
+
META_OPTS = {
|
|
92
|
+
HEADER: 'User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1;; Hello: World',
|
|
93
|
+
DELAY: 0,
|
|
94
|
+
DEPTH: 2,
|
|
95
|
+
FOLLOW_REDIRECT: True,
|
|
96
|
+
METHOD: 'GET',
|
|
97
|
+
MATCH_CODES: '200',
|
|
98
|
+
PROXY: FreeProxy(timeout=0.5).get() if USE_PROXY else False,
|
|
99
|
+
RATE_LIMIT: 10000,
|
|
100
|
+
RETRIES: 0,
|
|
101
|
+
THREADS: 50,
|
|
102
|
+
TIMEOUT: 1,
|
|
103
|
+
USER_AGENT: 'Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1',
|
|
104
|
+
|
|
105
|
+
# Individual tasks options
|
|
106
|
+
'bup.mode': 'http_methods',
|
|
107
|
+
'gf.pattern': 'xss',
|
|
108
|
+
'nmap.output_path': load_fixture('nmap_output', FIXTURES_DIR, only_path=True, ext='.xml'), # nmap XML fixture
|
|
109
|
+
'nmap.tcp_connect': True,
|
|
110
|
+
'nmap.version_detection': True,
|
|
111
|
+
'nmap.skip_host_discovery': True,
|
|
112
|
+
'msfconsole.resource': load_fixture('msfconsole_input', FIXTURES_DIR, only_path=True),
|
|
113
|
+
'dirsearch.output_path': load_fixture('dirsearch_output', FIXTURES_DIR, only_path=True),
|
|
114
|
+
'gitleaks_output_path': load_fixture('gitleaks_output', FIXTURES_DIR, only_path=True),
|
|
115
|
+
'maigret.output_path': load_fixture('maigret_output', FIXTURES_DIR, only_path=True),
|
|
116
|
+
'nuclei.template_id': 'prometheus-metrics',
|
|
117
|
+
'wpscan.output_path': load_fixture('wpscan_output', FIXTURES_DIR, only_path=True),
|
|
118
|
+
'h8mail.output_path': load_fixture('h8mail_output', FIXTURES_DIR, only_path=True),
|
|
119
|
+
'h8mail.local_breach': load_fixture('h8mail_breach', FIXTURES_DIR, only_path=True),
|
|
120
|
+
'wpprobe.output_path': load_fixture('wpprobe_output', FIXTURES_DIR, only_path=True),
|
|
121
|
+
'arjun.output_path': load_fixture('arjun_output', FIXTURES_DIR, only_path=True),
|
|
122
|
+
'arjun.wordlist': False,
|
|
123
|
+
'trivy.output_path': load_fixture('trivy_output', FIXTURES_DIR, only_path=True),
|
|
124
|
+
'wafw00f.output_path': load_fixture('wafw00f_output', FIXTURES_DIR, only_path=True),
|
|
125
|
+
'testssl.output_path': load_fixture('testssl_output', FIXTURES_DIR, only_path=True),
|
|
126
|
+
'ssh_audit.output_path': load_fixture('ssh_audit_output', FIXTURES_DIR, only_path=True),
|
|
127
|
+
'x8.wordlist': 'http_params'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def mock_subprocess_popen(output_list):
|
|
132
|
+
mock_process = unittest.mock.MagicMock()
|
|
133
|
+
mock_process.wait.return_value = 0
|
|
134
|
+
mock_process.stdout.readline.side_effect = output_list
|
|
135
|
+
mock_process.pid = None
|
|
136
|
+
mock_process.returncode = 0
|
|
137
|
+
|
|
138
|
+
def mock_popen(*args, **kwargs):
|
|
139
|
+
return mock_process
|
|
140
|
+
|
|
141
|
+
return unittest.mock.patch('subprocess.Popen', mock_popen)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@contextlib.contextmanager
|
|
145
|
+
def mock_command(cls, inputs=[], opts={}, fixture=None, method=''):
|
|
146
|
+
mocks = []
|
|
147
|
+
if isinstance(fixture, dict):
|
|
148
|
+
fixture = [fixture]
|
|
149
|
+
|
|
150
|
+
is_list = isinstance(fixture, list)
|
|
151
|
+
supports_list = next((loader for loader in cls.item_loaders if getattr(loader, 'list', False)), None)
|
|
152
|
+
if is_list:
|
|
153
|
+
if supports_list:
|
|
154
|
+
mocks.append(json.dumps(fixture))
|
|
155
|
+
else:
|
|
156
|
+
for item in fixture:
|
|
157
|
+
if isinstance(item, dict):
|
|
158
|
+
mocks.append(json.dumps(item))
|
|
159
|
+
else:
|
|
160
|
+
mocks.append(item)
|
|
161
|
+
else:
|
|
162
|
+
mocks.append(fixture)
|
|
163
|
+
|
|
164
|
+
with mock_subprocess_popen(mocks):
|
|
165
|
+
command = cls(inputs, **opts)
|
|
166
|
+
if method == 'run':
|
|
167
|
+
yield cls(inputs, **opts).run()
|
|
168
|
+
elif method == 'si':
|
|
169
|
+
yield cls.si([], inputs, **opts)
|
|
170
|
+
elif method in ['s', 'delay']:
|
|
171
|
+
yield getattr(cls, method)(inputs, **opts)
|
|
172
|
+
else:
|
|
173
|
+
yield command
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class CommandOutputTester: # Mixin for unittest.TestCase
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def get_item_str(item):
|
|
180
|
+
return f"Item: {repr(item)}\nItem dict: {json.dumps(item.toDict(), default=str, indent=2)}"
|
|
181
|
+
|
|
182
|
+
def _test_runner_output(
|
|
183
|
+
self,
|
|
184
|
+
runner,
|
|
185
|
+
expected_output_keys=[],
|
|
186
|
+
expected_output_types=[],
|
|
187
|
+
expected_results=[],
|
|
188
|
+
expected_status='SUCCESS',
|
|
189
|
+
empty_results_allowed=False,
|
|
190
|
+
additional_checks=[]):
|
|
191
|
+
|
|
192
|
+
console.print(f'\t[dim]Testing {runner.config.type} {runner.name} ...[/]', end='')
|
|
193
|
+
debug('', sub='unittest')
|
|
194
|
+
debug('-' * 10 + f' RUNNER {runner.name} STARTING ' + '-' * 10, sub='unittest')
|
|
195
|
+
|
|
196
|
+
if not runner.inputs:
|
|
197
|
+
console.print('[dim gold3] skipped (no inputs defined).[/]')
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
if not expected_results and not expected_output_keys and not expected_output_types:
|
|
201
|
+
console.print('[dim gold3] (no outputs defined).[/]', end='')
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
debug(f'{runner.name} starting command: {runner.cmd}', sub='unittest') if isinstance(runner, Command) else None
|
|
205
|
+
|
|
206
|
+
# Run runner
|
|
207
|
+
results = runner.run()
|
|
208
|
+
results_str = "\n".join([repr(r) for r in results])
|
|
209
|
+
debug(f'{runner.name} yielded results\n{results_str}', sub='unittest')
|
|
210
|
+
debug(f'{runner.name} yielded results\n{json.dumps([r.toDict() for r in results], default=str, indent=2)}', sub='unittest.dict', verbose=True) # noqa: E501
|
|
211
|
+
|
|
212
|
+
debug('-' * 10 + f' RUNNER {runner.name} TESTS ' + '-' * 10, sub='unittest')
|
|
213
|
+
|
|
214
|
+
# Add execution types to allowed output types
|
|
215
|
+
expected_output_types.extend(EXECUTION_TYPES + STAT_TYPES)
|
|
216
|
+
|
|
217
|
+
# Check return code
|
|
218
|
+
if isinstance(runner, Command):
|
|
219
|
+
if not runner.ignore_return_code:
|
|
220
|
+
debug(f'{runner.name} should have a 0 return code', sub='unittest')
|
|
221
|
+
self.assertEqual(runner.return_code, 0, f'{runner.name} should have a 0 return code. Runner return code: {runner.return_code}') # noqa: E501
|
|
222
|
+
|
|
223
|
+
# Check results not empty
|
|
224
|
+
if not empty_results_allowed:
|
|
225
|
+
debug(f'{runner.name} should return at least 1 result', sub='unittest')
|
|
226
|
+
self.assertGreater(len(results), 0, f'{runner.name} should return at least 1 result')
|
|
227
|
+
|
|
228
|
+
# Check status
|
|
229
|
+
debug(f'{runner.name} should have the status {expected_status}.', sub='unittest')
|
|
230
|
+
self.assertEqual(runner.status, expected_status, f'{runner.name} should have the status {expected_status}. Errors: {runner.errors}') # noqa: E501
|
|
231
|
+
|
|
232
|
+
# Check results
|
|
233
|
+
failures = []
|
|
234
|
+
debug('-' * 10 + f' RUNNER {runner.name} ITEM TESTS ' + '-' * 10, sub='unittest')
|
|
235
|
+
for item in results:
|
|
236
|
+
item_str = self.get_item_str(item)
|
|
237
|
+
debug('--' * 5, sub='unittest')
|
|
238
|
+
debug(f'{runner.name} item {repr(item)}', sub='unittest')
|
|
239
|
+
debug(f'{runner.name} item [{item.toDict()}]', sub='unittest.item', verbose=True)
|
|
240
|
+
|
|
241
|
+
if expected_output_types:
|
|
242
|
+
debug(f'{runner.name} item should have an output type in {[_._type for _ in expected_output_types]}', sub='unittest') # noqa: E501
|
|
243
|
+
self.assertIn(type(item), expected_output_types, f'{runner.name}: item has an unexpected output type "{type(item)}". Expected types: {expected_output_types}.\n{item_str}') # noqa: E501
|
|
244
|
+
|
|
245
|
+
if expected_output_keys:
|
|
246
|
+
keys = [k for k in list(item.keys()) if not k.startswith('_')]
|
|
247
|
+
debug(f'{runner.name} item should have output keys {keys}', sub='unittest')
|
|
248
|
+
self.assertEqual(
|
|
249
|
+
set(keys).difference(set(expected_output_keys)),
|
|
250
|
+
set(),
|
|
251
|
+
f'{runner.name}: item is missing expected keys {set(expected_output_keys)}.\nItem keys: {keys}.\n{item_str}') # noqa: E501
|
|
252
|
+
|
|
253
|
+
if additional_checks and item.__class__ in additional_checks.get('output_types', {}):
|
|
254
|
+
config = additional_checks['output_types'][item.__class__]
|
|
255
|
+
runner_regex = config.get('runner', '*')
|
|
256
|
+
if not re.match(runner_regex, runner.name):
|
|
257
|
+
continue
|
|
258
|
+
checks = config.get('checks', [])
|
|
259
|
+
for check in checks:
|
|
260
|
+
error = check['error']
|
|
261
|
+
info = check['info']
|
|
262
|
+
func = check['function']
|
|
263
|
+
debug(f'{runner.name} item {info}', sub='unittest')
|
|
264
|
+
try:
|
|
265
|
+
result = func(item)
|
|
266
|
+
if not result:
|
|
267
|
+
failures.append(f'ERROR ({runner.name}): {error}.\n{item_str}')
|
|
268
|
+
except Exception as e:
|
|
269
|
+
failures.append(f'ERROR ({runner.name}): {error}.\n{item_str}\n{traceback_as_string(e)}')
|
|
270
|
+
|
|
271
|
+
# Additional checks failures
|
|
272
|
+
if failures:
|
|
273
|
+
self.fail("\n\n" + "\n\n".join(failures))
|
|
274
|
+
|
|
275
|
+
# Check if runner results in expected results
|
|
276
|
+
if expected_results:
|
|
277
|
+
for result in expected_results:
|
|
278
|
+
debug(f'{runner.name} item should be in expected results {result}.', sub='unittest')
|
|
279
|
+
self.assertIn(result, results, f'{runner.name}: {result} should be in runner results.') # noqa: E501
|
|
280
|
+
|
|
281
|
+
except Exception:
|
|
282
|
+
console.print('[dim red] failed[/]')
|
|
283
|
+
raise
|
|
284
|
+
|
|
285
|
+
console.print('[dim green] ok[/]')
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def clear_modules():
|
|
289
|
+
"""Clear all secator modules imports.
|
|
290
|
+
See https://stackoverflow.com/questions/7460363/re-import-module-under-test-to-lose-context for context.
|
|
291
|
+
"""
|
|
292
|
+
keys_to_delete = []
|
|
293
|
+
for k, _ in sys.modules.items():
|
|
294
|
+
if k.startswith('secator'):
|
|
295
|
+
keys_to_delete.append(k)
|
|
296
|
+
for k in keys_to_delete:
|
|
297
|
+
del sys.modules[k]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from secator.loader import get_configs_by_type
|
|
2
|
+
from secator.runners import Workflow
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DynamicWorkflow(Workflow):
|
|
6
|
+
def __init__(self, config):
|
|
7
|
+
self.config = config
|
|
8
|
+
|
|
9
|
+
def __call__(self, targets, **kwargs):
|
|
10
|
+
hooks = kwargs.pop('hooks', {})
|
|
11
|
+
results = kwargs.pop('results', [])
|
|
12
|
+
context = kwargs.pop('context', {})
|
|
13
|
+
super().__init__(
|
|
14
|
+
config=self.config,
|
|
15
|
+
inputs=targets,
|
|
16
|
+
results=results,
|
|
17
|
+
hooks=hooks,
|
|
18
|
+
context=context,
|
|
19
|
+
run_opts=kwargs)
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DYNAMIC_WORKFLOWS = {}
|
|
24
|
+
for workflow in get_configs_by_type('workflow'):
|
|
25
|
+
instance = DynamicWorkflow(workflow)
|
|
26
|
+
DYNAMIC_WORKFLOWS[workflow.name] = instance
|
|
27
|
+
|
|
28
|
+
globals().update(DYNAMIC_WORKFLOWS)
|
|
29
|
+
__all__ = list(DYNAMIC_WORKFLOWS)
|