Skip to content

Commit 728b5e1

Browse files
authored
Doug/improve timeout and error handling (#40)
# Change log * Fixed an issue where if the top level package id in sources didn't exist it could cause the CLI to fail with an unhandled exception * Added a configurable timeout for requests * Added the new flag to the documentation
1 parent d769d0c commit 728b5e1

File tree

5 files changed

+62
-25
lines changed

5 files changed

+62
-25
lines changed

Diff for: README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Socket Security CLI was created to enable integrations with other tools like
88
socketcli [-h] [--api_token API_TOKEN] [--repo REPO] [--branch BRANCH] [--committer COMMITTER] [--pr_number PR_NUMBER]
99
[--commit_message COMMIT_MESSAGE] [--default_branch] [--target_path TARGET_PATH] [--scm {api,github,gitlab}] [--sbom-file SBOM_FILE]
1010
[--commit-sha COMMIT_SHA] [--generate-license GENERATE_LICENSE] [-v] [--enable-debug] [--enable-json] [--disable-overview]
11-
[--disable-security-issue] [--files FILES] [--ignore-commit-files]
11+
[--disable-security-issue] [--files FILES] [--ignore-commit-files] [--timeout]
1212
````
1313

1414
If you don't want to provide the Socket API Token every time then you can use the environment variable `SOCKET_SECURITY_API_KEY`
@@ -38,3 +38,4 @@ If you don't want to provide the Socket API Token every time then you can use th
3838
| --files | | False | | If provided in the format of `["file1", "file2"]` will be used to determine if there have been supported file changes. This is used if it isn't a git repo and you would like to only run if it supported files have changed. |
3939
| --ignore-commit-files | | False | False | If enabled then the CLI will ignore what files are changed in the commit and look for all manifest files |
4040
| --disable-blocking | | False | False | Disables failing checks and will only exit with an exit code of 0 |
41+
| --timeout | | False | 1200 | The timeout per request for the CLI |

Diff for: socketsecurity/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
__author__ = 'socket.dev'
2-
__version__ = '1.0.40'
2+
__version__ = '1.0.41'

Diff for: socketsecurity/core/__init__.py

+42-22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import logging
22
from pathlib import PurePath
3-
3+
from requests.exceptions import ReadTimeout
44
import requests
55
from urllib.parse import urlencode
66
import base64
77
import json
88
from socketsecurity.core.exceptions import (
9-
APIFailure, APIKeyMissing, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound, APICloudflareError
9+
APIFailure,
10+
APIKeyMissing,
11+
APIAccessDenied,
12+
APIInsufficientQuota,
13+
APIResourceNotFound,
14+
APICloudflareError,
15+
RequestTimeoutExceeded
1016
)
1117
from socketsecurity import __version__
1218
from socketsecurity.core.licenses import Licenses
@@ -182,15 +188,18 @@ def do_request(
182188
verify = True
183189
if allow_unverified_ssl:
184190
verify = False
185-
response = requests.request(
186-
method.upper(),
187-
url,
188-
headers=headers,
189-
data=payload,
190-
files=files,
191-
timeout=timeout,
192-
verify=verify
193-
)
191+
try:
192+
response = requests.request(
193+
method.upper(),
194+
url,
195+
headers=headers,
196+
data=payload,
197+
files=files,
198+
timeout=timeout,
199+
verify=verify
200+
)
201+
except ReadTimeout:
202+
raise RequestTimeoutExceeded(f"Configured timeout {timeout} reached for request for path {url}")
194203
output_headers = headers.copy()
195204
output_headers['Authorization'] = "API_KEY_REDACTED"
196205
output = {
@@ -794,15 +803,18 @@ def get_source_data(package: Package, packages: dict) -> list:
794803
else:
795804
for top_id in package.topLevelAncestors:
796805
top_package: Package
797-
top_package = packages[top_id]
798-
manifests = ""
799-
top_purl = f"{top_package.type}/{top_package.name}@{top_package.version}"
800-
for manifest_data in top_package.manifestFiles:
801-
manifest_file = manifest_data.get("file")
802-
manifests += f"{manifest_file};"
803-
manifests = manifests.rstrip(";")
804-
source = (top_purl, manifests)
805-
introduced_by.append(source)
806+
top_package = packages.get(top_id)
807+
if top_package:
808+
manifests = ""
809+
top_purl = f"{top_package.type}/{top_package.name}@{top_package.version}"
810+
for manifest_data in top_package.manifestFiles:
811+
manifest_file = manifest_data.get("file")
812+
manifests += f"{manifest_file};"
813+
manifests = manifests.rstrip(";")
814+
source = (top_purl, manifests)
815+
introduced_by.append(source)
816+
else:
817+
log.debug(f"Unable to get top level package info for {top_id}")
806818
return introduced_by
807819

808820
@staticmethod
@@ -841,21 +853,29 @@ def create_sbom_dict(sbom: list) -> dict:
841853
"""
842854
packages = {}
843855
top_level_count = {}
856+
top_levels = {}
844857
for item in sbom:
845858
package = Package(**item)
846859
if package.id in packages:
847-
print("Duplicate package?")
860+
log.debug("Duplicate package?")
848861
else:
849862
package = Core.get_license_details(package)
850863
packages[package.id] = package
851864
for top_id in package.topLevelAncestors:
852865
if top_id not in top_level_count:
853866
top_level_count[top_id] = 1
867+
top_levels[top_id] = [package.id]
854868
else:
855869
top_level_count[top_id] += 1
870+
if package.id not in top_levels[top_id]:
871+
top_levels[top_id].append(package.id)
856872
if len(top_level_count) > 0:
857873
for package_id in top_level_count:
858-
packages[package_id].transitives = top_level_count[package_id]
874+
if package_id not in packages:
875+
details = top_levels.get(package_id)
876+
log.debug(f"Orphaned top level package id {package_id} for packages {details}")
877+
else:
878+
packages[package_id].transitives = top_level_count[package_id]
859879
return packages
860880

861881
@staticmethod

Diff for: socketsecurity/core/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ class APIInsufficientQuota(Exception):
3636
class APIResourceNotFound(Exception):
3737
"""Raised when access is denied to the API"""
3838
pass
39+
40+
class RequestTimeoutExceeded(Exception):
41+
"""Raised when access is denied to the API"""
42+
pass

Diff for: socketsecurity/socketcli.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@
161161
default=False
162162
)
163163

164+
parser.add_argument(
165+
'--timeout',
166+
default=1200,
167+
help='Timeout configuration for each request. Defaults to 1200 and applies to each unique HTTP request',
168+
required=False,
169+
type=float
170+
)
171+
172+
173+
164174

165175
def output_console_comments(diff_report: Diff, sbom_file_name: str = None) -> None:
166176
if diff_report.id != "NO_DIFF_RAN":
@@ -252,6 +262,8 @@ def main_code():
252262
ignore_commit_files = arguments.ignore_commit_files
253263
disable_blocking = arguments.disable_blocking
254264
allow_unverified = arguments.allow_unverified
265+
timeout = arguments.timeout
266+
255267
if disable_blocking:
256268
global blocking_disabled
257269
blocking_disabled = True
@@ -308,7 +320,7 @@ def main_code():
308320
default_branch = scm.is_default_branch
309321

310322
base_api_url = os.getenv("BASE_API_URL") or None
311-
core = Core(token=api_token, request_timeout=1200, base_api_url=base_api_url, allow_unverified=allow_unverified)
323+
core = Core(token=api_token, request_timeout=timeout, base_api_url=base_api_url, allow_unverified=allow_unverified)
312324
no_change = True
313325
if ignore_commit_files:
314326
no_change = False

0 commit comments

Comments
 (0)