diff --git a/components/collector/.vulture_ignore_list.py b/components/collector/.vulture_ignore_list.py index 5c7d1628ea..c13832516e 100644 --- a/components/collector/.vulture_ignore_list.py +++ b/components/collector/.vulture_ignore_list.py @@ -68,7 +68,9 @@ GitLabUnusedJobs # unused class (src/source_collectors/gitlab/unused_jobs.py:11) scan_status # unused variable (src/source_collectors/harbor/security_warnings.py:58) HarborSecurityWarnings # unused class (src/source_collectors/harbor/security_warnings.py:62) -HarborJSONSecurityWarnings # unused class (src/source_collectors/harbor_json/security_warnings.py:36) +generated_at # unused variable (src/source_collectors/harbor_json/base.py:23) +HarborJSONSecurityWarnings # unused class (src/source_collectors/harbor_json/security_warnings.py:12) +HarborJSONSourceUpToDateness # unused class (src/source_collectors/harbor_json/source_up_to_dateness.py:13) JacocoSourceUpToDateness # unused class (src/source_collectors/jacoco/source_up_to_dateness.py:11) JacocoUncoveredBranches # unused class (src/source_collectors/jacoco/uncovered_branches.py:6) JacocoUncoveredLines # unused class (src/source_collectors/jacoco/uncovered_lines.py:6) @@ -173,4 +175,3 @@ pageInfo # unused variable (tests/source_collectors/github/test_merge_requests.py:45) totalCount # unused variable (tests/source_collectors/github/test_merge_requests.py:46) pullRequests # unused variable (tests/source_collectors/github/test_merge_requests.py:53) -RobotFrameworkSourceVersion # unused class (tests/source_collectors/robot_framework/test_source_version.py:6) diff --git a/components/collector/src/source_collectors/__init__.py b/components/collector/src/source_collectors/__init__.py index a766f47858..43bc216b68 100644 --- a/components/collector/src/source_collectors/__init__.py +++ b/components/collector/src/source_collectors/__init__.py @@ -60,6 +60,7 @@ from .gitlab.unused_jobs import GitLabUnusedJobs from .harbor.security_warnings import HarborSecurityWarnings from .harbor_json.security_warnings import HarborJSONSecurityWarnings +from .harbor_json.source_up_to_dateness import HarborJSONSourceUpToDateness from .jacoco.source_up_to_dateness import JacocoSourceUpToDateness from .jacoco.uncovered_branches import JacocoUncoveredBranches from .jacoco.uncovered_lines import JacocoUncoveredLines diff --git a/components/collector/src/source_collectors/harbor_json/base.py b/components/collector/src/source_collectors/harbor_json/base.py new file mode 100644 index 0000000000..13600f8b9a --- /dev/null +++ b/components/collector/src/source_collectors/harbor_json/base.py @@ -0,0 +1,30 @@ +"""Base classes for Harbor JSON collectors.""" + +from typing import Final, TypedDict + +REPORT_MIME_TYPE: Final = "application/vnd.security.vulnerability.report; version=1.1" + + +class Vulnerability(TypedDict): + """A Harbor JSON vulnerability.""" + + id: str + package: str + version: str + fix_version: str + severity: str + description: str + links: list[str] + + +class HarborJSONVulnerabilityReport(TypedDict): + """A Harbor JSON vulnerability report.""" + + generated_at: str + vulnerabilities: list[Vulnerability] + + +HarborJSON = TypedDict( + "HarborJSON", + {"application/vnd.security.vulnerability.report; version=1.1": HarborJSONVulnerabilityReport}, +) diff --git a/components/collector/src/source_collectors/harbor_json/security_warnings.py b/components/collector/src/source_collectors/harbor_json/security_warnings.py index 2050701ea0..9e03c49ec7 100644 --- a/components/collector/src/source_collectors/harbor_json/security_warnings.py +++ b/components/collector/src/source_collectors/harbor_json/security_warnings.py @@ -1,36 +1,12 @@ """Harbor JSON security warnings collector.""" -from typing import Final, TypedDict, cast +from typing import cast from base_collectors import JSONFileSourceCollector, SecurityWarningsSourceCollector from collector_utilities.type import JSON from model import Entities, Entity -REPORT_MIME_TYPE: Final = "application/vnd.security.vulnerability.report; version=1.1" - - -class Vulnerability(TypedDict): - """A Harbor JSON vulnerability.""" - - id: str - package: str - version: str - fix_version: str - severity: str - description: str - links: list[str] - - -class HarborJSONVulnerabilityReport(TypedDict): - """A Harbor JSON vulnerability report.""" - - vulnerabilities: list[Vulnerability] - - -HarborJSON = TypedDict( - "HarborJSON", - {"application/vnd.security.vulnerability.report; version=1.1": HarborJSONVulnerabilityReport}, -) +from .base import REPORT_MIME_TYPE, HarborJSON class HarborJSONSecurityWarnings(SecurityWarningsSourceCollector, JSONFileSourceCollector): diff --git a/components/collector/src/source_collectors/harbor_json/source_up_to_dateness.py b/components/collector/src/source_collectors/harbor_json/source_up_to_dateness.py new file mode 100644 index 0000000000..734bafcffc --- /dev/null +++ b/components/collector/src/source_collectors/harbor_json/source_up_to_dateness.py @@ -0,0 +1,18 @@ +"""Harbor JSON source up-to-dateness collector.""" + +from datetime import datetime +from typing import Final + +from base_collectors import JSONFileSourceCollector, TimePassedCollector +from collector_utilities.date_time import parse_datetime +from collector_utilities.type import Response + +from .base import REPORT_MIME_TYPE + + +class HarborJSONSourceUpToDateness(JSONFileSourceCollector, TimePassedCollector): + """Harbor JSON collector for source up-to-dateness.""" + + async def _parse_source_response_date_time(self, response: Response) -> datetime: + """Override to parse the date of the most recent analysis.""" + return parse_datetime((await response.json())[REPORT_MIME_TYPE]["generated_at"]) diff --git a/components/collector/tests/source_collectors/harbor_json/base.py b/components/collector/tests/source_collectors/harbor_json/base.py new file mode 100644 index 0000000000..07a1988c6f --- /dev/null +++ b/components/collector/tests/source_collectors/harbor_json/base.py @@ -0,0 +1,38 @@ +"""Base classes for Harbor JSON collector unit tests.""" + +from typing import ClassVar + +from source_collectors.harbor_json.base import HarborJSON + +from tests.source_collectors.source_collector_test_case import SourceCollectorTestCase + + +class HarborJSONCollectorTestCase(SourceCollectorTestCase): + """Base class for Harbor JSON collector unit tests.""" + + SOURCE_TYPE = "harbor_json" + VULNERABILITIES_JSON: ClassVar[HarborJSON] = { + "application/vnd.security.vulnerability.report; version=1.1": { + "generated_at": "2023-08-26T16:32:21.923910328Z", + "vulnerabilities": [ + { + "id": "CVE-2011-3374", + "package": "apt", + "version": "2.2.4", + "fix_version": "2.2.5", + "severity": "Low", + "description": "It was found that apt-key in apt, all versions, do not correctly validate ...", + "links": ["https://avd.aquasec.com/nvd/cve-2011-3374"], + }, + { + "id": "CVE-2020-22218", + "package": "libssh2-1", + "version": "1.9.0-2", + "fix_version": "", + "severity": "High", + "description": "An issue was discovered in function _libssh2_packet_add in libssh2 1.10.0 ...", + "links": ["https://avd.aquasec.com/nvd/cve-2020-22218"], + }, + ], + }, + } diff --git a/components/collector/tests/source_collectors/harbor_json/test_security_warnings.py b/components/collector/tests/source_collectors/harbor_json/test_security_warnings.py index e797655f91..b9442c2c68 100644 --- a/components/collector/tests/source_collectors/harbor_json/test_security_warnings.py +++ b/components/collector/tests/source_collectors/harbor_json/test_security_warnings.py @@ -2,40 +2,13 @@ from typing import ClassVar -from source_collectors.harbor_json.security_warnings import HarborJSON +from .base import HarborJSONCollectorTestCase -from tests.source_collectors.source_collector_test_case import SourceCollectorTestCase - -class HarborJSONSecurityWarningsTest(SourceCollectorTestCase): +class HarborJSONSecurityWarningsTest(HarborJSONCollectorTestCase): """Unit tests for the security warning metric.""" - SOURCE_TYPE = "harbor_json" METRIC_TYPE = "security_warnings" - VULNERABILITIES_JSON: ClassVar[HarborJSON] = { - "application/vnd.security.vulnerability.report; version=1.1": { - "vulnerabilities": [ - { - "id": "CVE-2011-3374", - "package": "apt", - "version": "2.2.4", - "fix_version": "2.2.5", - "severity": "Low", - "description": "It was found that apt-key in apt, all versions, do not correctly validate ...", - "links": ["https://avd.aquasec.com/nvd/cve-2011-3374"], - }, - { - "id": "CVE-2020-22218", - "package": "libssh2-1", - "version": "1.9.0-2", - "fix_version": "", - "severity": "High", - "description": "An issue was discovered in function _libssh2_packet_add in libssh2 1.10.0 ...", - "links": ["https://avd.aquasec.com/nvd/cve-2020-22218"], - }, - ], - }, - } EXPECTED_ENTITIES: ClassVar[list[dict[str, str]]] = [ { "key": "CVE-2011-3374@apt@2_2_4", diff --git a/components/collector/tests/source_collectors/harbor_json/test_source_up_to_dateness.py b/components/collector/tests/source_collectors/harbor_json/test_source_up_to_dateness.py new file mode 100644 index 0000000000..526b8ee037 --- /dev/null +++ b/components/collector/tests/source_collectors/harbor_json/test_source_up_to_dateness.py @@ -0,0 +1,17 @@ +"""Unit tests for the Harbor JSON source up-to-dateness collector.""" + +from collector_utilities.date_time import days_ago, parse_datetime + +from .base import HarborJSONCollectorTestCase + + +class HarborJSONSourceUpToDatenessTest(HarborJSONCollectorTestCase): + """Unit tests for the source up-to-dateness metric.""" + + METRIC_TYPE = "source_up_to_dateness" + + async def test_souce_up_to_dateness(self): + """Test the source up-to-dateness.""" + response = await self.collect(get_request_json_return_value=self.VULNERABILITIES_JSON) + expected_value = str(days_ago(parse_datetime("2023-08-26T16:32:21.923910328Z"))) + self.assert_measurement(response, value=expected_value) diff --git a/components/shared_code/src/shared_data_model/metrics.py b/components/shared_code/src/shared_data_model/metrics.py index 7ad61d4cae..da868ad6ec 100644 --- a/components/shared_code/src/shared_data_model/metrics.py +++ b/components/shared_code/src/shared_data_model/metrics.py @@ -462,6 +462,7 @@ "dependency_track", "gatling", "gitlab", + "harbor_json", "jacoco", "jacoco_jenkins_plugin", "jenkins", diff --git a/components/shared_code/src/shared_data_model/sources/harbor.py b/components/shared_code/src/shared_data_model/sources/harbor.py index 38aea3d52d..2035ea781f 100644 --- a/components/shared_code/src/shared_data_model/sources/harbor.py +++ b/components/shared_code/src/shared_data_model/sources/harbor.py @@ -81,6 +81,8 @@ }, ) +ALL_HARBOR_JSON_METRICS = ["security_warnings", "source_up_to_dateness"] + HARBOR_JSON = Source( name="Harbor JSON", description=( @@ -91,7 +93,11 @@ parameters={ "severities": Severities(values=["unknown", "low", "medium", "high", "critical"]), "fix_availability": FixAvailability(), - **access_parameters(ALL_HARBOR_METRICS, source_type="Harbor vulnerability report", source_type_format="JSON"), + **access_parameters( + ALL_HARBOR_JSON_METRICS, + source_type="Harbor vulnerability report", + source_type_format="JSON", + ), }, entities={ "security_warnings": Entity( diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 483848ac24..9a5130266d 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -21,6 +21,7 @@ If your currently installed *Quality-time* version is not the latest version, pl ### Added - When measuring missing metrics, make the subject type and the metric type of the missing metrics link to the reference documentation. Closes [#10528](https://github.com/ICTU/quality-time/issues/10528). +- Allow for measuring the source up-to-dateness of Harbor JSON reports. Closes [#10609](https://github.com/ICTU/quality-time/issues/10609). ## v5.21.0 - 2024-12-12