Skip to content

Commit 7179ede

Browse files
committed
Trim repo-creation errors to the API reason; bump to 2.11.10
The SDK appends the request path and the response headers to APIAccessDenied's message, so logging the exception as-is printed a multi-line header dump at the default log level. APIInsufficientPermissions carries no message at all, which left the line ending in ": ". Log the first line, falling back to the exception type. Quota and organization-not-allowed errors are also 403s, so the repo:create hint no longer fires for those. Bump to 2.11.10: v2.11.9 is already taken by an existing tag and GitHub release (#434), even though that release never reached PyPI.
1 parent aa2299b commit 7179ede

6 files changed

Lines changed: 47 additions & 10 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 2.11.9
3+
## 2.11.10
44

55
### Fixed: report why a new repository couldn't be created
66

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.11.9"
9+
version = "2.11.10"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

‎socketsecurity/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.11.9'
2+
__version__ = '2.11.10'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

‎socketsecurity/core/__init__.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
from socketsecurity.config import CliConfig
2121
from git import Repo
2222
from socketdev import socketdev
23-
from socketdev.exceptions import APIFailure
23+
from socketdev.exceptions import (
24+
APIFailure,
25+
APIInsufficientQuota,
26+
APIOrganizationNotAllowed,
27+
)
2428
from socketdev.fullscans import DiffArtifacts, FullScanParams, SocketArtifact
2529
from socketdev.org import Organization
2630
from socketdev.repos import RepositoryInfo
@@ -1534,8 +1538,11 @@ def get_repo_info(self, repo_slug: str, default_branch: str = "socket-default-br
15341538

15351539
except APIFailure as e:
15361540
status = f" (HTTP {e.status_code})" if e.status_code else ""
1537-
log.error(f"API failure while creating repository {repo_slug}{status}: {e}")
1538-
if e.status_code == 403:
1541+
# The SDK appends the request path and response headers after the first line,
1542+
# and some subclasses carry no message at all.
1543+
reason = str(e).split("\n", 1)[0] or type(e).__name__
1544+
log.error(f"API failure while creating repository {repo_slug}{status}: {reason}")
1545+
if e.status_code == 403 and not isinstance(e, (APIInsufficientQuota, APIOrganizationNotAllowed)):
15391546
log.error("A 403 here usually means the API token is missing the repo:create scope.")
15401547
sys.exit(2) # Exit here with code 2. Code 1 indicates a successfully-detected security issue.
15411548

‎tests/core/test_sdk_methods.py‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import logging
22

33
import pytest
4-
from socketdev.exceptions import APIAccessDenied, APIFailure, APIResourceNotFound
4+
from socketdev.exceptions import (
5+
APIAccessDenied,
6+
APIFailure,
7+
APIInsufficientPermissions,
8+
APIInsufficientQuota,
9+
APIResourceNotFound,
10+
)
511
from socketdev.fullscans import FullScanParams, FullScanStreamResponse, ScanType
612

713
from socketsecurity.config import CliConfig
@@ -50,8 +56,10 @@ def test_get_repo_info(core, mock_sdk_with_responses):
5056
def test_get_repo_info_logs_why_repo_creation_failed(core, mock_sdk_with_responses, caplog):
5157
"""A repo that isn't in Socket yet and can't be created exits 2 with the reason logged"""
5258
mock_sdk_with_responses.repos.repo.side_effect = APIResourceNotFound(status_code=404)
59+
# Mirrors the SDK, which appends the request path and response headers to the message
5360
mock_sdk_with_responses.repos.post.side_effect = APIAccessDenied(
54-
"Insufficient permissions", status_code=403
61+
"Insufficient permissions\nPath: https://api.socket.dev/v0/orgs/test/repos\n\nHeaders:\nx-req: 1",
62+
status_code=403,
5563
)
5664

5765
with caplog.at_level(logging.INFO, logger="socketdev"), pytest.raises(SystemExit) as exit_info:
@@ -60,10 +68,32 @@ def test_get_repo_info_logs_why_repo_creation_failed(core, mock_sdk_with_respons
6068
assert exit_info.value.code == 2
6169
messages = [record.getMessage() for record in caplog.records]
6270
assert "Failed to get repository new-repo, attempting to create it" in messages
63-
assert any("creating repository new-repo (HTTP 403)" in message for message in messages)
71+
assert "API failure while creating repository new-repo (HTTP 403): Insufficient permissions" in messages
6472
assert any("repo:create" in message for message in messages)
6573

6674

75+
@pytest.mark.parametrize(
76+
("error", "expected_reason", "expects_scope_hint"),
77+
[
78+
(APIInsufficientPermissions(status_code=403), "APIInsufficientPermissions", True),
79+
(APIInsufficientQuota(status_code=403), "APIInsufficientQuota", False),
80+
],
81+
)
82+
def test_get_repo_info_repo_creation_failure_reason(
83+
core, mock_sdk_with_responses, caplog, error, expected_reason, expects_scope_hint
84+
):
85+
"""Message-less SDK errors still name a reason, and only permission 403s get the scope hint"""
86+
mock_sdk_with_responses.repos.repo.side_effect = APIResourceNotFound(status_code=404)
87+
mock_sdk_with_responses.repos.post.side_effect = error
88+
89+
with caplog.at_level(logging.INFO, logger="socketdev"), pytest.raises(SystemExit):
90+
core.get_repo_info("new-repo")
91+
92+
messages = [record.getMessage() for record in caplog.records]
93+
assert f"API failure while creating repository new-repo (HTTP 403): {expected_reason}" in messages
94+
assert any("repo:create" in message for message in messages) is expects_scope_hint
95+
96+
6797
def test_get_repo_info_restores_log_level_after_creating_repo(
6898
core, mock_sdk_with_responses, repo_info_response, caplog
6999
):

‎uv.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)