diff --git a/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.test.tsx b/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.test.tsx index dde4119..eff5547 100644 --- a/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.test.tsx +++ b/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.test.tsx @@ -41,6 +41,15 @@ const mockEntityWithoutAnnotations = { }, }; +const mockPipelineScanV1 = { + resultId: 'result-pipeline-123', + imageId: 'sha256:deadbeef1234', + pullString: 'ghcr.io/sysdiglabs/sample-app:latest', + policyEvaluationResult: 'failed', + createdAt: new Date('2024-01-22T08:51:46Z'), + vulnTotalBySeverity: { critical: 1, high: 3, medium: 7, low: 2, negligible: 0 }, +}; + const mockSysdigApi = { fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }), fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }), @@ -92,4 +101,48 @@ describe('SysdigVMPipelineFetchComponent', () => { const message = await screen.findByText(/missing annotation/i); expect(message).toBeInTheDocument(); }); + + it('renders rows using v1 policyEvaluationResult field (without s)', async () => { + const apiWithData = { + ...mockSysdigApi, + fetchVulnPipeline: jest.fn().mockResolvedValue({ data: [mockPipelineScanV1] }), + }; + + await renderInTestApp( + + + + + + ); + + expect(await screen.findByText('ghcr.io/sysdiglabs/sample-app:latest')).toBeInTheDocument(); + expect(screen.getByText('failed')).toBeInTheDocument(); + }); + + it('filters out rows with null policyEvaluationResult', async () => { + const scanWithNullPolicy = { ...mockPipelineScanV1, policyEvaluationResult: null as any }; + const apiWithData = { + ...mockSysdigApi, + fetchVulnPipeline: jest.fn().mockResolvedValue({ data: [scanWithNullPolicy, mockPipelineScanV1] }), + }; + + await renderInTestApp( + + + + + + ); + + // Only the scan with a non-null policyEvaluationResult should appear + const rows = await screen.findAllByText('ghcr.io/sysdiglabs/sample-app:latest'); + expect(rows).toHaveLength(1); + }); }); diff --git a/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.tsx b/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.tsx index 966e7b4..d2e0b2e 100644 --- a/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.tsx +++ b/src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.tsx @@ -35,8 +35,8 @@ import { sysdigApiRef } from '../../api'; type PipelineScan = { createdAt: Date, imageId: string, - mainAssetName: string, - policyEvaluationsResult: string, + pullString: string, + policyEvaluationResult: string, resultId: string, vulnTotalBySeverity: { critical: number, @@ -58,8 +58,8 @@ type DenseTableProps = { { "createdAt": "2019-08-24T14:15:22Z", "imageId": "string", - "mainAssetName": "string", - "policyEvaluationsResult": "passed", + "pullString": "string", + "policyEvaluationResult": "passed", "resultId": "string", "vulnTotalBySeverity": { "critical": 0, @@ -82,12 +82,12 @@ export const DenseTable = ({ pipelineScans, title }: DenseTableProps) => { // { title: 'URL', field: "url", width: "10%" }, ]; - const data = pipelineScans.filter(scan => { return scan.policyEvaluationsResult !== null && scan.policyEvaluationsResult !== '' }) + const data = pipelineScans.filter(scan => { return scan.policyEvaluationResult !== null && scan.policyEvaluationResult !== '' }) .flatMap(scan => { return { - policyEvalStatus: getStatusColorSpan(scan.policyEvaluationsResult), + policyEvalStatus: getStatusColorSpan(scan.policyEvaluationResult), imageId: {scan.imageId}, - asset: scan.mainAssetName, + asset: scan.pullString, vulns: getChips(scan.vulnTotalBySeverity), // convert image.lastEvaluatedAt to a date string // lastEvaluatedAt: getDate(image.lastEvaluatedAt * 1000), diff --git a/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.test.tsx b/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.test.tsx index 52dbf08..11b0d3f 100644 --- a/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.test.tsx +++ b/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.test.tsx @@ -42,6 +42,13 @@ const mockEntityWithoutAnnotations = { }, }; +const mockRegistryScanV1 = { + resultId: 'result-registry-456', + imageId: 'sha256:abc123def456', + pullString: 'harbor.example.com/library/nginx:1.25', + vulnTotalBySeverity: { critical: 0, high: 2, medium: 5, low: 1, negligible: 3 }, +}; + const mockSysdigApi = { fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }), fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }), @@ -93,4 +100,24 @@ describe('SysdigVMRegistryFetchComponent', () => { const message = await screen.findByText(/missing annotation/i); expect(message).toBeInTheDocument(); }); + + it('renders rows using v1 pullString field (not mainAssetName)', async () => { + const apiWithData = { + ...mockSysdigApi, + fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [mockRegistryScanV1] }), + }; + + await renderInTestApp( + + + + + + ); + + expect(await screen.findByText('harbor.example.com/library/nginx:1.25')).toBeInTheDocument(); + }); }); diff --git a/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.tsx b/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.tsx index ce0fbb6..d975b8c 100644 --- a/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.tsx +++ b/src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.tsx @@ -34,7 +34,7 @@ import { import { sysdigApiRef } from '../../api'; type RegistryScan = { - mainAssetName: string, + pullString: string, imageId: string, resultId: string, vulnTotalBySeverity: { @@ -57,7 +57,7 @@ type DenseTableProps = { { "createdAt": "2019-08-24T14:15:22Z", "imageId": "string", - "mainAssetName": "string", + "pullString": "string", "resultId": "string", "vulnTotalBySeverity": { "critical": 0, @@ -81,7 +81,7 @@ export const DenseTable = ({ registryScans, title }: DenseTableProps) => { .flatMap(scan => { return { imageId: {scan.imageId}, - asset: scan.mainAssetName, + asset: scan.pullString, severity: getChips(scan.vulnTotalBySeverity) }; }); diff --git a/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.test.tsx b/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.test.tsx index d91e0c1..03d9e51 100644 --- a/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.test.tsx +++ b/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.test.tsx @@ -42,6 +42,25 @@ const mockEntityWithoutAnnotations = { }, }; +const mockRuntimeScanV1 = { + resultId: 'result-abc123', + resourceId: 'sha256:a1b2c3d4e5f6', + mainAssetName: 'nginx:latest', + policyEvaluationResult: 'failed', + isRiskSpotlightEnabled: true, + sbomId: 'sbom-xyz', + scope: { + 'asset.type': 'workload', + 'kubernetes.cluster.name': 'test-cluster', + 'kubernetes.namespace.name': 'test-namespace', + 'kubernetes.workload.name': 'nginx', + 'kubernetes.workload.type': 'deployment', + 'kubernetes.pod.container.name': 'nginx', + }, + vulnTotalBySeverity: { critical: 2, high: 5, medium: 10, low: 3, negligible: 1 }, + runningVulnTotalBySeverity: { critical: 1, high: 2, medium: 4, low: 1, negligible: 0 }, +}; + const mockSysdigApi = { fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }), fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }), @@ -93,4 +112,48 @@ describe('SysdigVMRuntimeFetchComponent', () => { const message = await screen.findByText(/missing annotation/i); expect(message).toBeInTheDocument(); }); + + it('renders rows using v1 policyEvaluationResult field (without s)', async () => { + const apiWithData = { + ...mockSysdigApi, + fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [mockRuntimeScanV1] }), + }; + + await renderInTestApp( + + + + + + ); + + expect(await screen.findByText('nginx:latest')).toBeInTheDocument(); + expect(screen.getByText('failed')).toBeInTheDocument(); + }); + + it('filters out rows with null policyEvaluationResult', async () => { + const scanWithNullPolicy = { ...mockRuntimeScanV1, policyEvaluationResult: null as any }; + const apiWithData = { + ...mockSysdigApi, + fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [scanWithNullPolicy, mockRuntimeScanV1] }), + }; + + await renderInTestApp( + + + + + + ); + + // Only the scan with a non-null policyEvaluationResult should appear (1 row = 1 asset name) + const rows = await screen.findAllByText('nginx:latest'); + expect(rows).toHaveLength(1); + }); }); diff --git a/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.tsx b/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.tsx index 686b45f..6eafd69 100644 --- a/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.tsx +++ b/src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.tsx @@ -42,7 +42,8 @@ import { sysdigApiRef } from '../../api'; type RuntimeScan = { isRiskSpotlightEnabled: boolean, mainAssetName: string, - policyEvaluationsResult: string, + policyEvaluationResult: string, + resourceId: string, resultId: string, runningVulnTotalBySeverity: { critical: number, @@ -120,10 +121,10 @@ export const DenseTable = ({ runtimeScans, title }: DenseTableProps) => { // { title: 'URL', field: "url", width: "10%" }, ]; - const data = runtimeScans.filter(scan => { return scan.policyEvaluationsResult !== null && scan.policyEvaluationsResult !== '' }) + const data = runtimeScans.filter(scan => { return scan.policyEvaluationResult !== null && scan.policyEvaluationResult !== '' }) .flatMap(scan => { return { - policyEvalStatus: getStatusColorSpan(scan.policyEvaluationsResult), + policyEvalStatus: getStatusColorSpan(scan.policyEvaluationResult), asset: scan.mainAssetName, // scope: JSON.stringify(scan.scope), severity: getChips(scan.vulnTotalBySeverity), diff --git a/src/infra/api/SysdigApiClient.test.ts b/src/infra/api/SysdigApiClient.test.ts index bef53d9..a1c1243 100644 --- a/src/infra/api/SysdigApiClient.test.ts +++ b/src/infra/api/SysdigApiClient.test.ts @@ -4,7 +4,7 @@ import { ConfigApi, FetchApi } from "@backstage/core-plugin-api"; import { rest } from "msw"; import { setupServer } from "msw/node"; import { SysdigApiClient } from "./SysdigApiClient"; -import { API_PROXY_BASE_PATH, API_VULN_RUNTIME } from "../../lib"; +import { API_PROXY_BASE_PATH, API_VULN_RUNTIME, API_VULN_REGISTRY, API_VULN_PIPELINE } from "../../lib"; describe("SysdigApiClient", () => { const configApi: ConfigApi = new ConfigReader({ @@ -46,4 +46,13 @@ describe("SysdigApiClient", () => { undefined, ); }); + + it("should use v1 endpoint paths (not v1beta1)", () => { + expect(API_VULN_RUNTIME).toMatch(/\/v1\//); + expect(API_VULN_REGISTRY).toMatch(/\/v1\//); + expect(API_VULN_PIPELINE).toMatch(/\/v1\//); + expect(API_VULN_RUNTIME).not.toMatch(/v1beta1/); + expect(API_VULN_REGISTRY).not.toMatch(/v1beta1/); + expect(API_VULN_PIPELINE).not.toMatch(/v1beta1/); + }); }); diff --git a/src/lib/endpoints.ts b/src/lib/endpoints.ts index b9308dc..08f0107 100644 --- a/src/lib/endpoints.ts +++ b/src/lib/endpoints.ts @@ -8,13 +8,13 @@ export const API_PROXY_BASE_PATH = "/api/proxy/sysdig"; */ // API Endpoint for Vulnerability Management at Runtime -export const API_VULN_RUNTIME = "/secure/vulnerability/v1beta1/runtime-results"; +export const API_VULN_RUNTIME = "/secure/vulnerability/v1/runtime-results"; // API Endpoint for Vulnerability Management at Registry -export const API_VULN_REGISTRY = "/secure/vulnerability/v1beta1/registry-results"; +export const API_VULN_REGISTRY = "/secure/vulnerability/v1/registry-results"; // API Endpoint for Vulnerability Management at Pipeline -export const API_VULN_PIPELINE = "/secure/vulnerability/v1beta1/pipeline-results"; +export const API_VULN_PIPELINE = "/secure/vulnerability/v1/pipeline-results"; // API Endpoint for Inventory (Posture) export const API_INVENTORY = "/api/cspm/v1/inventory/resources"; @@ -54,4 +54,4 @@ export function getBacklink(endpoint: string | undefined, backlink: string | und } return backlink_base + backlink_section; -} \ No newline at end of file +}