Skip to content

Commit 666d16e

Browse files
committed
Preserve comparison and scan identities
1 parent 6b113c0 commit 666d16e

10 files changed

Lines changed: 1121 additions & 131 deletions

‎socketsecurity/core/__init__.py‎

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727

2828
from socketsecurity import USER_AGENT, __version__
2929
from socketsecurity.core.classes import Alert, Diff, FullScan, Issue, Package, Purl
30+
from socketsecurity.core.diff_result import (
31+
DiffScanResult,
32+
PackageComparisonResult,
33+
create_diff_scan_result,
34+
)
3035
from socketsecurity.core.exceptions import APIResourceNotFound
3136

3237
from .resource_utils import check_file_count_against_ulimit
@@ -1313,7 +1318,7 @@ def create_full_scan_with_report_url(
13131318
base_socket = "https://socket.dev/dashboard/org"
13141319
diff.report_url = f"{base_socket}/{self.config.org_slug}/sbom/{new_full_scan.id}"
13151320
diff.diff_url = diff.report_url
1316-
diff.id = new_full_scan.id
1321+
diff.set_scan_ids(new_full_scan.id)
13171322
diff.is_full_scan = True
13181323

13191324
needs_alerts = (
@@ -1957,12 +1962,12 @@ def get_license_text_via_purl(self, packages: dict[str, Package], batch_size: in
19571962

19581963
return packages
19591964

1960-
def get_diff_scan_artifacts(
1965+
def _get_diff_scan_result(
19611966
self,
19621967
head_full_scan_id: str,
19631968
new_full_scan_id: str,
19641969
external_href: Optional[str] = None
1965-
) -> DiffArtifacts:
1970+
) -> DiffScanResult:
19661971
"""Compare two full scans via the diff-scans endpoints, polling for the result.
19671972
19681973
Creates a diff-scan resource from the two full scan IDs, then polls
@@ -2110,10 +2115,19 @@ def get_diff_scan_artifacts(
21102115
last_interval = interval
21112116
interval = min(interval * DIFF_SCAN_POLL_BACKOFF_MULTIPLIER, DIFF_SCAN_POLL_MAX_INTERVAL_SECONDS)
21122117

2113-
return DiffArtifacts.from_dict({
2114-
key: artifacts_dict.get(key) or []
2115-
for key in ("added", "removed", "unchanged", "replaced", "updated")
2116-
})
2118+
return create_diff_scan_result(artifacts_dict, diff_scan_id)
2119+
2120+
def get_diff_scan_artifacts(
2121+
self,
2122+
head_full_scan_id: str,
2123+
new_full_scan_id: str,
2124+
external_href: Optional[str] = None
2125+
) -> DiffArtifacts:
2126+
return self._get_diff_scan_result(
2127+
head_full_scan_id,
2128+
new_full_scan_id,
2129+
external_href=external_href,
2130+
).artifacts
21172131

21182132
def _requires_unchanged_artifacts(self) -> bool:
21192133
"""Whether any enabled output reads the unchanged half of a comparison.
@@ -2148,13 +2162,13 @@ def _requires_unchanged_artifacts(self) -> bool:
21482162
or getattr(config, "legal_format", "socket") == "fossa"
21492163
)
21502164

2151-
def get_added_and_removed_packages(
2165+
def _get_package_comparison(
21522166
self,
21532167
head_full_scan_id: str,
21542168
new_full_scan_id: str,
21552169
include_license_details: bool = False,
21562170
external_href: Optional[str] = None
2157-
) -> Tuple[Dict[str, Package], Dict[str, Package], Dict[str, Package]]:
2171+
) -> PackageComparisonResult:
21582172
"""
21592173
Get packages that were added and removed between scans.
21602174
@@ -2190,14 +2204,14 @@ def get_added_and_removed_packages(
21902204
with the primary diff-scan resource
21912205
21922206
Returns:
2193-
Tuple of (added_packages, removed_packages) dictionaries
2207+
Added, removed, and all-package maps plus the completed diff ID
21942208
"""
21952209

21962210
log.info(f"Comparing scans - Head scan ID: {head_full_scan_id}, New scan ID: {new_full_scan_id}")
21972211
diff_start = time.time()
2198-
diff_artifacts = None
2212+
comparison = None
21992213
try:
2200-
diff_artifacts = self.get_diff_scan_artifacts(
2214+
comparison = self._get_diff_scan_result(
22012215
head_full_scan_id,
22022216
new_full_scan_id,
22032217
external_href=external_href,
@@ -2215,7 +2229,7 @@ def get_added_and_removed_packages(
22152229
f"reason={type(error).__name__}"
22162230
)
22172231

2218-
if diff_artifacts is None:
2232+
if comparison is None:
22192233
try:
22202234
diff_artifacts = (
22212235
self.sdk.fullscans.stream_diff(
@@ -2226,6 +2240,10 @@ def get_added_and_removed_packages(
22262240
include_license_details=str(include_license_details).lower()
22272241
).data.artifacts
22282242
)
2243+
comparison = DiffScanResult(
2244+
artifacts=diff_artifacts,
2245+
diff_scan_id=None,
2246+
)
22292247
except APIFailure as e:
22302248
log.error(f"API Error: {e}")
22312249
# API failures are mapped to the configured infrastructure exit code by cli().
@@ -2245,7 +2263,7 @@ def get_added_and_removed_packages(
22452263
marker_found = False
22462264
buckets: Dict[str, List] = {}
22472265
for name in ("added", "removed", "unchanged", "replaced", "updated"):
2248-
bucket = getattr(diff_artifacts, name)
2266+
bucket = getattr(comparison.artifacts, name)
22492267
buckets[name] = [a for a in bucket if not Core.is_invalid_facts_marker(a)]
22502268
marker_found = marker_found or len(buckets[name]) != len(bucket)
22512269
Core.warn_if_invalid_facts_marker(marker_found)
@@ -2304,7 +2322,27 @@ def get_added_and_removed_packages(
23042322
else:
23052323
log.debug("Skipping PURL endpoint call (--generate-license not set)")
23062324

2307-
return added_packages, removed_packages, packages
2325+
return PackageComparisonResult(
2326+
added_packages=added_packages,
2327+
removed_packages=removed_packages,
2328+
packages=packages,
2329+
diff_scan_id=comparison.diff_scan_id,
2330+
)
2331+
2332+
def get_added_and_removed_packages(
2333+
self,
2334+
head_full_scan_id: str,
2335+
new_full_scan_id: str,
2336+
include_license_details: bool = False,
2337+
external_href: Optional[str] = None
2338+
) -> Tuple[Dict[str, Package], Dict[str, Package], Dict[str, Package]]:
2339+
result = self._get_package_comparison(
2340+
head_full_scan_id,
2341+
new_full_scan_id,
2342+
include_license_details=include_license_details,
2343+
external_href=external_href,
2344+
)
2345+
return result.added_packages, result.removed_packages, result.packages
23082346

23092347
def create_new_diff(
23102348
self,
@@ -2456,16 +2494,15 @@ def create_new_diff(
24562494
# the response and risks the truncation crash on large repos. The
24572495
# user flag still controls the dashboard report URL below; it just no
24582496
# longer gates this internal diff payload.
2459-
(
2460-
added_packages,
2461-
removed_packages,
2462-
packages
2463-
) = self.get_added_and_removed_packages(
2497+
comparison = self._get_package_comparison(
24642498
head_full_scan_id,
24652499
new_full_scan.id,
24662500
include_license_details=False,
24672501
external_href=external_href,
24682502
)
2503+
added_packages = comparison.added_packages
2504+
removed_packages = comparison.removed_packages
2505+
packages = comparison.packages
24692506

24702507
# Separate unchanged packages from added/removed for --strict-blocking support
24712508
unchanged_packages = {
@@ -2477,13 +2514,16 @@ def create_new_diff(
24772514
diff.packages = packages
24782515

24792516
base_socket = "https://socket.dev/dashboard/org"
2480-
diff.id = new_full_scan.id
2517+
diff.set_scan_ids(
2518+
new_full_scan.id,
2519+
old_scan_id=head_full_scan_id,
2520+
diff_scan_id=comparison.diff_scan_id,
2521+
)
24812522

24822523
report_url = f"{base_socket}/{self.config.org_slug}/sbom/{diff.id}"
24832524
if not params.include_license_details:
24842525
report_url += "?include_license_details=false"
24852526
diff.report_url = report_url
2486-
diff.new_scan_id = new_full_scan.id
24872527

24882528
if head_full_scan_id is not None:
24892529
diff.diff_url = f"{base_socket}/{self.config.org_slug}/diff/{head_full_scan_id}/{diff.id}"

‎socketsecurity/core/alert_selection.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ def clone_diff_with_selected_alerts(diff: Diff, selected_alerts: List[Issue]) ->
3838
)
3939
selected_diff.id = getattr(diff, "id", "")
4040
selected_diff.report_url = getattr(diff, "report_url", "")
41-
selected_diff.new_scan_id = getattr(diff, "new_scan_id", "")
41+
selected_diff.new_scan_id = getattr(diff, "new_scan_id", None)
42+
selected_diff.old_scan_id = getattr(diff, "old_scan_id", None)
43+
selected_diff.diff_scan_id = getattr(diff, "diff_scan_id", None)
4244
return selected_diff
4345

4446

‎socketsecurity/core/classes.py‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,9 @@ class Diff:
571571
sbom: str
572572
report_url: str
573573
diff_url: str
574-
new_scan_id: str
574+
new_scan_id: Optional[str]
575+
old_scan_id: Optional[str]
576+
diff_scan_id: Optional[str]
575577
is_full_scan: bool
576578
alerts_fetched: bool
577579

@@ -599,6 +601,24 @@ def __init__(self, **kwargs):
599601
self.is_full_scan = False
600602
if not hasattr(self, "alerts_fetched"):
601603
self.alerts_fetched = False
604+
if not hasattr(self, "new_scan_id"):
605+
self.new_scan_id = None
606+
if not hasattr(self, "old_scan_id"):
607+
self.old_scan_id = None
608+
if not hasattr(self, "diff_scan_id"):
609+
self.diff_scan_id = None
610+
611+
def set_scan_ids(
612+
self,
613+
new_scan_id: str,
614+
*,
615+
old_scan_id: Optional[str] = None,
616+
diff_scan_id: Optional[str] = None,
617+
) -> None:
618+
self.id = new_scan_id
619+
self.new_scan_id = new_scan_id
620+
self.old_scan_id = old_scan_id
621+
self.diff_scan_id = diff_scan_id
602622

603623
def __str__(self):
604624
return json.dumps(self.__dict__)

‎socketsecurity/core/diff_result.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from dataclasses import dataclass
2+
from typing import Dict, Optional
3+
4+
from socketdev.fullscans import DiffArtifacts
5+
6+
from socketsecurity.core.classes import Package
7+
8+
9+
@dataclass
10+
class DiffScanResult:
11+
artifacts: DiffArtifacts
12+
diff_scan_id: Optional[str]
13+
14+
15+
@dataclass
16+
class PackageComparisonResult:
17+
added_packages: Dict[str, Package]
18+
removed_packages: Dict[str, Package]
19+
packages: Dict[str, Package]
20+
diff_scan_id: Optional[str]
21+
22+
23+
def create_diff_scan_result(
24+
artifacts: Dict[str, object], diff_scan_id: Optional[str]
25+
) -> DiffScanResult:
26+
return DiffScanResult(
27+
artifacts=DiffArtifacts.from_dict({
28+
key: artifacts.get(key) or []
29+
for key in ("added", "removed", "unchanged", "replaced", "updated")
30+
}),
31+
diff_scan_id=diff_scan_id,
32+
)

0 commit comments

Comments
 (0)