conviso-ast 3.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.
- conviso_ast-3.0.0.data/scripts/flow_bash_completer.sh +21 -0
- conviso_ast-3.0.0.data/scripts/flow_fish_completer.fish +1 -0
- conviso_ast-3.0.0.data/scripts/flow_zsh_completer.sh +32 -0
- conviso_ast-3.0.0.dist-info/METADATA +37 -0
- conviso_ast-3.0.0.dist-info/RECORD +128 -0
- conviso_ast-3.0.0.dist-info/WHEEL +5 -0
- conviso_ast-3.0.0.dist-info/entry_points.txt +3 -0
- conviso_ast-3.0.0.dist-info/top_level.txt +1 -0
- convisoappsec/__init__.py +0 -0
- convisoappsec/common/__init__.py +5 -0
- convisoappsec/common/box.py +251 -0
- convisoappsec/common/cleaner.py +78 -0
- convisoappsec/common/docker.py +399 -0
- convisoappsec/common/exceptions.py +8 -0
- convisoappsec/common/git_data_parser.py +76 -0
- convisoappsec/common/graphql/__init__.py +0 -0
- convisoappsec/common/graphql/error_handlers.py +75 -0
- convisoappsec/common/graphql/errors.py +16 -0
- convisoappsec/common/graphql/low_client.py +51 -0
- convisoappsec/common/retry_handler.py +40 -0
- convisoappsec/common/strings.py +8 -0
- convisoappsec/flow/__init__.py +3 -0
- convisoappsec/flow/api.py +104 -0
- convisoappsec/flow/cleaner.py +118 -0
- convisoappsec/flow/graphql_api/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/client.py +18 -0
- convisoappsec/flow/graphql_api/beta/models/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/models/issues/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/models/issues/container.py +72 -0
- convisoappsec/flow/graphql_api/beta/models/issues/iac.py +6 -0
- convisoappsec/flow/graphql_api/beta/models/issues/normalize.py +13 -0
- convisoappsec/flow/graphql_api/beta/models/issues/sast.py +53 -0
- convisoappsec/flow/graphql_api/beta/models/issues/sca.py +78 -0
- convisoappsec/flow/graphql_api/beta/resources_api.py +142 -0
- convisoappsec/flow/graphql_api/beta/schemas/__init__.py +0 -0
- convisoappsec/flow/graphql_api/beta/schemas/mutations/__init__.py +61 -0
- convisoappsec/flow/graphql_api/beta/schemas/resolvers/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/client.py +46 -0
- convisoappsec/flow/graphql_api/v1/models/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/models/asset.py +14 -0
- convisoappsec/flow/graphql_api/v1/models/issues.py +16 -0
- convisoappsec/flow/graphql_api/v1/models/project.py +35 -0
- convisoappsec/flow/graphql_api/v1/resources_api.py +489 -0
- convisoappsec/flow/graphql_api/v1/schemas/__init__.py +0 -0
- convisoappsec/flow/graphql_api/v1/schemas/mutations/__init__.py +212 -0
- convisoappsec/flow/graphql_api/v1/schemas/resolvers/__init__.py +180 -0
- convisoappsec/flow/source_code_scanner/__init__.py +9 -0
- convisoappsec/flow/source_code_scanner/exceptions.py +2 -0
- convisoappsec/flow/source_code_scanner/scc.py +68 -0
- convisoappsec/flow/source_code_scanner/source_code_scanner.py +177 -0
- convisoappsec/flow/util/__init__.py +7 -0
- convisoappsec/flow/util/ci_provider.py +99 -0
- convisoappsec/flow/util/metrics.py +16 -0
- convisoappsec/flow/util/source_code_compressor.py +22 -0
- convisoappsec/flow/version_control_system_adapter.py +528 -0
- convisoappsec/flow/version_searchers/__init__.py +9 -0
- convisoappsec/flow/version_searchers/sorted_by_versioning_style.py +85 -0
- convisoappsec/flow/version_searchers/timebased_version_seacher.py +39 -0
- convisoappsec/flow/version_searchers/version_searcher_result.py +33 -0
- convisoappsec/flow/versioning_style/__init__.py +0 -0
- convisoappsec/flow/versioning_style/semantic_versioning.py +44 -0
- convisoappsec/flowcli/__init__.py +3 -0
- convisoappsec/flowcli/__main__.py +4 -0
- convisoappsec/flowcli/assets/__init__.py +4 -0
- convisoappsec/flowcli/assets/create.py +88 -0
- convisoappsec/flowcli/assets/entrypoint.py +20 -0
- convisoappsec/flowcli/assets/ls.py +63 -0
- convisoappsec/flowcli/ast/__init__.py +3 -0
- convisoappsec/flowcli/ast/entrypoint.py +427 -0
- convisoappsec/flowcli/common.py +175 -0
- convisoappsec/flowcli/companies/__init__.py +0 -0
- convisoappsec/flowcli/companies/ls.py +25 -0
- convisoappsec/flowcli/container/__init__.py +3 -0
- convisoappsec/flowcli/container/entrypoint.py +17 -0
- convisoappsec/flowcli/container/run.py +306 -0
- convisoappsec/flowcli/context.py +49 -0
- convisoappsec/flowcli/deploy/__init__.py +0 -0
- convisoappsec/flowcli/deploy/create/__init__.py +4 -0
- convisoappsec/flowcli/deploy/create/context.py +12 -0
- convisoappsec/flowcli/deploy/create/entrypoint.py +31 -0
- convisoappsec/flowcli/deploy/create/with_/__init__.py +3 -0
- convisoappsec/flowcli/deploy/create/with_/entrypoint.py +20 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/__init__.py +4 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/context.py +11 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/entrypoint.py +30 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/__init__.py +4 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/entrypoint.py +21 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/time_.py +84 -0
- convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/versioning_style.py +115 -0
- convisoappsec/flowcli/deploy/create/with_/values.py +133 -0
- convisoappsec/flowcli/entrypoint.py +103 -0
- convisoappsec/flowcli/environment_checker.py +45 -0
- convisoappsec/flowcli/findings/__init__.py +4 -0
- convisoappsec/flowcli/findings/create/__init__.py +4 -0
- convisoappsec/flowcli/findings/create/entrypoint.py +18 -0
- convisoappsec/flowcli/findings/create/with_/__init__.py +3 -0
- convisoappsec/flowcli/findings/create/with_/entrypoint.py +19 -0
- convisoappsec/flowcli/findings/create/with_/version_tracker.py +93 -0
- convisoappsec/flowcli/findings/entrypoint.py +19 -0
- convisoappsec/flowcli/findings/import_sarif/__init__.py +4 -0
- convisoappsec/flowcli/findings/import_sarif/entrypoint.py +430 -0
- convisoappsec/flowcli/help_option.py +18 -0
- convisoappsec/flowcli/iac/__init__.py +3 -0
- convisoappsec/flowcli/iac/entrypoint.py +17 -0
- convisoappsec/flowcli/iac/run.py +328 -0
- convisoappsec/flowcli/requirements_verifier.py +132 -0
- convisoappsec/flowcli/sast/__init__.py +3 -0
- convisoappsec/flowcli/sast/entrypoint.py +17 -0
- convisoappsec/flowcli/sast/run.py +485 -0
- convisoappsec/flowcli/sbom/__init__.py +3 -0
- convisoappsec/flowcli/sbom/entrypoint.py +17 -0
- convisoappsec/flowcli/sbom/generate.py +235 -0
- convisoappsec/flowcli/sca/__init__.py +3 -0
- convisoappsec/flowcli/sca/entrypoint.py +17 -0
- convisoappsec/flowcli/sca/run.py +479 -0
- convisoappsec/flowcli/vulnerability/__init__.py +3 -0
- convisoappsec/flowcli/vulnerability/assert_security_rules.py +201 -0
- convisoappsec/flowcli/vulnerability/container_vulnerability_manager.py +175 -0
- convisoappsec/flowcli/vulnerability/entrypoint.py +18 -0
- convisoappsec/flowcli/vulnerability/rules_schema.json +53 -0
- convisoappsec/flowcli/vulnerability/run.py +487 -0
- convisoappsec/logger.py +29 -0
- convisoappsec/sast/__init__.py +0 -0
- convisoappsec/sast/decision.py +45 -0
- convisoappsec/sast/sastbox.py +296 -0
- convisoappsec/version.py +1 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import tarfile
|
|
3
|
+
import docker
|
|
4
|
+
import os
|
|
5
|
+
import click
|
|
6
|
+
import json
|
|
7
|
+
import docker.errors
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from contextlib import suppress
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from convisoappsec.flowcli.context import pass_flow_context
|
|
12
|
+
from convisoappsec.flow import GitAdapter
|
|
13
|
+
|
|
14
|
+
bitbucket = os.getenv('BITBUCKET_CLONE_DIR')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SASTBox(object):
|
|
18
|
+
REGISTRY = 'public.ecr.aws/convisoappsec'
|
|
19
|
+
REPOSITORY_NAME = 'sastbox_v2'
|
|
20
|
+
DEFAULT_TAG = 'unstable'
|
|
21
|
+
CONTAINER_CODE_DIR = bitbucket or '/code'
|
|
22
|
+
CONTAINER_REPORTS_DIR = '/tmp'
|
|
23
|
+
WORKSPACE_REPORT_PATH = CONTAINER_CODE_DIR
|
|
24
|
+
JSON_REPORT_PATTERN = 'output.json'
|
|
25
|
+
SUCCESS_EXIT_CODE = 1
|
|
26
|
+
USER_ENV_VAR = "USER"
|
|
27
|
+
|
|
28
|
+
def __init__(self, registry=None, repository_name=None, tag=None):
|
|
29
|
+
self.docker = docker.from_env(
|
|
30
|
+
version="auto"
|
|
31
|
+
)
|
|
32
|
+
self.container = None
|
|
33
|
+
self.registry = registry or self.REGISTRY
|
|
34
|
+
self.repository_name = repository_name or self.REPOSITORY_NAME
|
|
35
|
+
self.tag = tag or self.DEFAULT_TAG
|
|
36
|
+
|
|
37
|
+
def login(self, password, username='AWS'):
|
|
38
|
+
login_args = {
|
|
39
|
+
'registry': self.REGISTRY,
|
|
40
|
+
'username': username,
|
|
41
|
+
'password': password,
|
|
42
|
+
'reauth': True,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
login_result = self.docker.login(**login_args)
|
|
46
|
+
return login_result
|
|
47
|
+
|
|
48
|
+
def run_scan_diff(self, code_dir, current_commit, previous_commit, log=None):
|
|
49
|
+
return self._scan_diff(code_dir, current_commit, previous_commit, log)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def size(self):
|
|
53
|
+
try:
|
|
54
|
+
registry_data = self.docker.images.get_registry_data(
|
|
55
|
+
self.image
|
|
56
|
+
)
|
|
57
|
+
descriptor = registry_data.attrs.get('Descriptor', {})
|
|
58
|
+
return descriptor.get('size') * 1024 * 1024
|
|
59
|
+
except docker.errors.APIError:
|
|
60
|
+
return 6300 * 1024 * 1024
|
|
61
|
+
|
|
62
|
+
def pull(self):
|
|
63
|
+
size = self.size
|
|
64
|
+
layers = {}
|
|
65
|
+
for line in self.docker.api.pull(
|
|
66
|
+
self.repository, tag=self.tag, stream=True, decode=True
|
|
67
|
+
):
|
|
68
|
+
status = line.get('status', '')
|
|
69
|
+
detail = line.get('progressDetail', {})
|
|
70
|
+
|
|
71
|
+
if status == 'Downloading':
|
|
72
|
+
with suppress(Exception):
|
|
73
|
+
layer_id = line.get('id')
|
|
74
|
+
layer = layers.get(layer_id, {})
|
|
75
|
+
layer.update(detail)
|
|
76
|
+
layers[layer_id] = layer
|
|
77
|
+
|
|
78
|
+
for layer in layers.values():
|
|
79
|
+
current = layer.get('current')
|
|
80
|
+
total = layer.get('total')
|
|
81
|
+
|
|
82
|
+
if (current / total) > 0.98 and not layer.get('done'):
|
|
83
|
+
yield current
|
|
84
|
+
layer.update({'done': True})
|
|
85
|
+
|
|
86
|
+
yield size
|
|
87
|
+
|
|
88
|
+
def _scan_diff(self, code_dir, current_commit, previous_commit, log):
|
|
89
|
+
environment = {
|
|
90
|
+
'PREVIOUS_COMMIT': previous_commit,
|
|
91
|
+
'CURRENT_COMMIT': current_commit,
|
|
92
|
+
'SASTBOX_REPORTS_DIR': self.CONTAINER_REPORTS_DIR,
|
|
93
|
+
'SASTBOX_REPORT_DIR': '/tmp',
|
|
94
|
+
'SASTBOX_REPORT_PATTERN': '*.sarif',
|
|
95
|
+
'SASTBOX_CODE_DIR': self.CONTAINER_CODE_DIR,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
command_parts = [
|
|
99
|
+
'ruby', 'manager/sastbox_cli.rb',
|
|
100
|
+
'-c', self.CONTAINER_CODE_DIR,
|
|
101
|
+
'-a',
|
|
102
|
+
'-o', '/tmp/output.sarif',
|
|
103
|
+
f'--diff={previous_commit},{current_commit}',
|
|
104
|
+
'&&',
|
|
105
|
+
'cp', '$(find "$SASTBOX_REPORT_DIR" -name "$SASTBOX_REPORT_PATTERN")', '$SASTBOX_REPORTS_DIR'
|
|
106
|
+
]
|
|
107
|
+
command = ' '.join(command_parts)
|
|
108
|
+
|
|
109
|
+
# Configure container creation
|
|
110
|
+
create_args = {
|
|
111
|
+
'image': self.image,
|
|
112
|
+
'entrypoint': ['sh', '-c'],
|
|
113
|
+
'command': [command],
|
|
114
|
+
'tty': True,
|
|
115
|
+
'detach': True,
|
|
116
|
+
'environment': environment,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
try:
|
|
121
|
+
self.container = self.docker.containers.create(**create_args)
|
|
122
|
+
except docker.errors.APIError as e:
|
|
123
|
+
raise RuntimeError(f"Failed to create container: {e}")
|
|
124
|
+
|
|
125
|
+
# Create and upload source code tarball
|
|
126
|
+
source_code_tarball_file = tempfile.TemporaryFile()
|
|
127
|
+
try:
|
|
128
|
+
source_code_tarball = tarfile.open(mode="w|gz", fileobj=source_code_tarball_file)
|
|
129
|
+
source_code_tarball.add(
|
|
130
|
+
name=code_dir,
|
|
131
|
+
arcname=self.CONTAINER_CODE_DIR,
|
|
132
|
+
filter=lambda tarinfo: tarinfo if not tarinfo.name.endswith('.zip') else None
|
|
133
|
+
)
|
|
134
|
+
source_code_tarball.close()
|
|
135
|
+
source_code_tarball_file.seek(0)
|
|
136
|
+
try:
|
|
137
|
+
self.container.put_archive("/", source_code_tarball_file)
|
|
138
|
+
except docker.errors.APIError as e:
|
|
139
|
+
raise RuntimeError(f"Failed to upload tarball: {e}")
|
|
140
|
+
finally:
|
|
141
|
+
source_code_tarball_file.close()
|
|
142
|
+
|
|
143
|
+
# Start the container and stream logs
|
|
144
|
+
try:
|
|
145
|
+
self.container.start()
|
|
146
|
+
except docker.errors.APIError as e:
|
|
147
|
+
raise RuntimeError(f"Failed to start container: {e}")
|
|
148
|
+
|
|
149
|
+
for line in self.container.logs(stream=True):
|
|
150
|
+
if log:
|
|
151
|
+
log(line, new_line=False)
|
|
152
|
+
|
|
153
|
+
self.recovery_technologies_file()
|
|
154
|
+
|
|
155
|
+
wait_result = self.container.wait()
|
|
156
|
+
status_code = wait_result.get('StatusCode')
|
|
157
|
+
|
|
158
|
+
if status_code != self.SUCCESS_EXIT_CODE:
|
|
159
|
+
logs = self.container.logs().decode('utf-8')
|
|
160
|
+
raise RuntimeError(f"SASTBox exited with status code {status_code}\nLogs:\n{logs}")
|
|
161
|
+
|
|
162
|
+
# Retrieve and extract reports
|
|
163
|
+
try:
|
|
164
|
+
chunks, _ = self.container.get_archive(self.CONTAINER_REPORTS_DIR)
|
|
165
|
+
except docker.errors.APIError as e:
|
|
166
|
+
raise RuntimeError(f"Failed to retrieve reports: {e}")
|
|
167
|
+
|
|
168
|
+
reports_tarball_file = tempfile.TemporaryFile()
|
|
169
|
+
try:
|
|
170
|
+
for chunk in chunks:
|
|
171
|
+
reports_tarball_file.write(chunk)
|
|
172
|
+
tempdir = tempfile.mkdtemp()
|
|
173
|
+
reports_tarball_file.seek(0)
|
|
174
|
+
reports_tarball = tarfile.open(mode="r|", fileobj=reports_tarball_file)
|
|
175
|
+
reports_tarball.extractall(path=tempdir)
|
|
176
|
+
reports_tarball.close()
|
|
177
|
+
finally:
|
|
178
|
+
reports_tarball_file.close()
|
|
179
|
+
|
|
180
|
+
# Verify reports exist and return their paths
|
|
181
|
+
reports = self._list_reports_paths(tempdir)
|
|
182
|
+
if not reports:
|
|
183
|
+
raise RuntimeError("No reports found in the container")
|
|
184
|
+
|
|
185
|
+
except docker.errors.APIError as e:
|
|
186
|
+
raise RuntimeError(f"Failed to retrieve reports: {e}")
|
|
187
|
+
|
|
188
|
+
return reports
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def repository(self):
|
|
192
|
+
return "{registry}/{repository_name}".format(
|
|
193
|
+
registry=self.registry,
|
|
194
|
+
repository_name=self.repository_name,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def image(self):
|
|
199
|
+
return "{repository}:{tag}".format(
|
|
200
|
+
repository=self.repository,
|
|
201
|
+
tag=self.tag,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def __del__(self):
|
|
205
|
+
with suppress(Exception):
|
|
206
|
+
self.container.remove(v=True, force=True)
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def _list_reports_paths(cls, root_dir):
|
|
210
|
+
root_dir = root_dir + cls.CONTAINER_REPORTS_DIR
|
|
211
|
+
sastbox_reports_dir = Path(root_dir)
|
|
212
|
+
|
|
213
|
+
for report in sastbox_reports_dir.glob(cls.JSON_REPORT_PATTERN):
|
|
214
|
+
yield report
|
|
215
|
+
|
|
216
|
+
def recovery_technologies_file(self):
|
|
217
|
+
""" Method to recover a fingerprint file inside the container with founded technology """
|
|
218
|
+
try:
|
|
219
|
+
generator_object, _ = self.container.get_archive('/tmp/')
|
|
220
|
+
file_content = b"".join(generator_object)
|
|
221
|
+
file_content_stream = BytesIO(file_content)
|
|
222
|
+
tar = tarfile.open(fileobj=file_content_stream)
|
|
223
|
+
file_names = tar.getnames()
|
|
224
|
+
|
|
225
|
+
fingerprint_file = next(
|
|
226
|
+
(file for file in file_names if file.startswith('tmp/fingerprint') and file.endswith('.json')), None)
|
|
227
|
+
|
|
228
|
+
if not fingerprint_file:
|
|
229
|
+
log_func("No file starting with 'fingerprint' and ending with '.json' found.")
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
actual_filename = fingerprint_file.split('/')[-1]
|
|
233
|
+
|
|
234
|
+
generator_object, _ = self.container.get_archive(f'/tmp/{actual_filename}')
|
|
235
|
+
file_content = b"".join(generator_object)
|
|
236
|
+
file_content_stream = BytesIO(file_content)
|
|
237
|
+
tar = tarfile.open(fileobj=file_content_stream)
|
|
238
|
+
file_data = tar.extractfile(actual_filename)
|
|
239
|
+
content = json.loads(file_data.read())
|
|
240
|
+
technologies = content['result']['technologies']
|
|
241
|
+
except Exception as error:
|
|
242
|
+
msg = "\U0001F4AC Something goes wrong when trying to recover the technologies, continuing ..."
|
|
243
|
+
log_func(msg)
|
|
244
|
+
technologies = []
|
|
245
|
+
|
|
246
|
+
if technologies is None:
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
self.update_asset_technologies(technologies=technologies)
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
@pass_flow_context
|
|
253
|
+
@click.pass_context
|
|
254
|
+
def update_asset_technologies(flow_context, context, technologies):
|
|
255
|
+
"""
|
|
256
|
+
Update technologies on asset.
|
|
257
|
+
Args:
|
|
258
|
+
flow_context (dict): Flow context containing parameters.
|
|
259
|
+
context (object): Object containing necessary methods (e.g., create_conviso_graphql_client).
|
|
260
|
+
technologies (list): List of technologies to be updated.
|
|
261
|
+
Returns:
|
|
262
|
+
dict: Response from the API call.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
# this prevents a broken execution when something goes wrong.
|
|
266
|
+
try:
|
|
267
|
+
git_adapter = GitAdapter(flow_context.params['repository_dir'])
|
|
268
|
+
repo_url = git_adapter.repo_url()
|
|
269
|
+
|
|
270
|
+
company_id = flow_context.params.get('company_id')
|
|
271
|
+
asset_id = flow_context.params.get('asset_id')
|
|
272
|
+
asset_name = flow_context.params.get('asset_name')
|
|
273
|
+
unwanted_technologies = {
|
|
274
|
+
'unknown', 'json', 'text', 'ini', 'diff', 'xml', 'markdown', 'csv', 'gemfile.lock', 'html+erb',
|
|
275
|
+
'javascript+erb', 'robots.txt', 'yaml', 'batchfile', 'java properties', 'svg', 'json with comments'
|
|
276
|
+
}
|
|
277
|
+
updated_technologies = [tech for tech in technologies if tech not in unwanted_technologies]
|
|
278
|
+
conviso_api = context.create_conviso_graphql_client()
|
|
279
|
+
|
|
280
|
+
response = conviso_api.assets.update_asset(
|
|
281
|
+
company_id=int(company_id),
|
|
282
|
+
asset_id=asset_id,
|
|
283
|
+
asset_name=asset_name,
|
|
284
|
+
technologies=updated_technologies,
|
|
285
|
+
repo_url=repo_url
|
|
286
|
+
)
|
|
287
|
+
except Exception as error:
|
|
288
|
+
msg = "\U0001F4AC Something goes wrong when trying to send technologies to the CP, continuing... {error}".format(error=error)
|
|
289
|
+
log_func(msg)
|
|
290
|
+
|
|
291
|
+
response = None
|
|
292
|
+
|
|
293
|
+
return response
|
|
294
|
+
|
|
295
|
+
def log_func(msg, new_line=True):
|
|
296
|
+
click.echo(msg, nl=new_line, err=True)
|
convisoappsec/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '3.0.0'
|