Skip to content

Commit db398b9

Browse files
committed
feat: migrate Vulnerability Management API from v1beta1 to v1
- Update all three endpoint paths to /secure/vulnerability/v1/... - Runtime: rename policyEvaluationsResult -> policyEvaluationResult, add resourceId field - Pipeline: rename policyEvaluationsResult -> policyEvaluationResult, rename mainAssetName -> pullString - Registry: rename mainAssetName -> pullString - Add tests validating v1 response field mapping for all three components - Add test asserting endpoint paths no longer reference v1beta1 Changes verified against live v1 API responses.
1 parent 1045e38 commit db398b9

File tree

8 files changed

+171
-18
lines changed

8 files changed

+171
-18
lines changed

src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ const mockEntityWithoutAnnotations = {
4141
},
4242
};
4343

44+
const mockPipelineScanV1 = {
45+
resultId: 'result-pipeline-123',
46+
imageId: 'sha256:deadbeef1234',
47+
pullString: 'ghcr.io/sysdiglabs/sample-app:latest',
48+
policyEvaluationResult: 'failed',
49+
createdAt: new Date('2024-01-22T08:51:46Z'),
50+
vulnTotalBySeverity: { critical: 1, high: 3, medium: 7, low: 2, negligible: 0 },
51+
};
52+
4453
const mockSysdigApi = {
4554
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }),
4655
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }),
@@ -92,4 +101,48 @@ describe('SysdigVMPipelineFetchComponent', () => {
92101
const message = await screen.findByText(/missing annotation/i);
93102
expect(message).toBeInTheDocument();
94103
});
104+
105+
it('renders rows using v1 policyEvaluationResult field (without s)', async () => {
106+
const apiWithData = {
107+
...mockSysdigApi,
108+
fetchVulnPipeline: jest.fn().mockResolvedValue({ data: [mockPipelineScanV1] }),
109+
};
110+
111+
await renderInTestApp(
112+
<TestApiProvider apis={[
113+
[sysdigApiRef, apiWithData],
114+
[configApiRef, mockConfig],
115+
]}>
116+
<EntityProvider entity={mockEntity}>
117+
<SysdigVMPipelineFetchComponent />
118+
</EntityProvider>
119+
</TestApiProvider>
120+
);
121+
122+
expect(await screen.findByText('ghcr.io/sysdiglabs/sample-app:latest')).toBeInTheDocument();
123+
expect(screen.getByText('failed')).toBeInTheDocument();
124+
});
125+
126+
it('filters out rows with null policyEvaluationResult', async () => {
127+
const scanWithNullPolicy = { ...mockPipelineScanV1, policyEvaluationResult: null as any };
128+
const apiWithData = {
129+
...mockSysdigApi,
130+
fetchVulnPipeline: jest.fn().mockResolvedValue({ data: [scanWithNullPolicy, mockPipelineScanV1] }),
131+
};
132+
133+
await renderInTestApp(
134+
<TestApiProvider apis={[
135+
[sysdigApiRef, apiWithData],
136+
[configApiRef, mockConfig],
137+
]}>
138+
<EntityProvider entity={mockEntity}>
139+
<SysdigVMPipelineFetchComponent />
140+
</EntityProvider>
141+
</TestApiProvider>
142+
);
143+
144+
// Only the scan with a non-null policyEvaluationResult should appear
145+
const rows = await screen.findAllByText('ghcr.io/sysdiglabs/sample-app:latest');
146+
expect(rows).toHaveLength(1);
147+
});
95148
});

src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import { sysdigApiRef } from '../../api';
3535
type PipelineScan = {
3636
createdAt: Date,
3737
imageId: string,
38-
mainAssetName: string,
39-
policyEvaluationsResult: string,
38+
pullString: string,
39+
policyEvaluationResult: string,
4040
resultId: string,
4141
vulnTotalBySeverity: {
4242
critical: number,
@@ -58,8 +58,8 @@ type DenseTableProps = {
5858
{
5959
"createdAt": "2019-08-24T14:15:22Z",
6060
"imageId": "string",
61-
"mainAssetName": "string",
62-
"policyEvaluationsResult": "passed",
61+
"pullString": "string",
62+
"policyEvaluationResult": "passed",
6363
"resultId": "string",
6464
"vulnTotalBySeverity": {
6565
"critical": 0,
@@ -82,12 +82,12 @@ export const DenseTable = ({ pipelineScans, title }: DenseTableProps) => {
8282
// { title: 'URL', field: "url", width: "10%" },
8383
];
8484

85-
const data = pipelineScans.filter(scan => { return scan.policyEvaluationsResult !== null && scan.policyEvaluationsResult !== '' })
85+
const data = pipelineScans.filter(scan => { return scan.policyEvaluationResult !== null && scan.policyEvaluationResult !== '' })
8686
.flatMap(scan => {
8787
return {
88-
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationsResult),
88+
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationResult),
8989
imageId: <code>{scan.imageId}</code>,
90-
asset: scan.mainAssetName,
90+
asset: scan.pullString,
9191
vulns: getChips(scan.vulnTotalBySeverity),
9292
// convert image.lastEvaluatedAt to a date string
9393
// lastEvaluatedAt: getDate(image.lastEvaluatedAt * 1000),

src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ const mockEntityWithoutAnnotations = {
4242
},
4343
};
4444

45+
const mockRegistryScanV1 = {
46+
resultId: 'result-registry-456',
47+
imageId: 'sha256:abc123def456',
48+
pullString: 'harbor.example.com/library/nginx:1.25',
49+
vulnTotalBySeverity: { critical: 0, high: 2, medium: 5, low: 1, negligible: 3 },
50+
};
51+
4552
const mockSysdigApi = {
4653
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }),
4754
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }),
@@ -93,4 +100,24 @@ describe('SysdigVMRegistryFetchComponent', () => {
93100
const message = await screen.findByText(/missing annotation/i);
94101
expect(message).toBeInTheDocument();
95102
});
103+
104+
it('renders rows using v1 pullString field (not mainAssetName)', async () => {
105+
const apiWithData = {
106+
...mockSysdigApi,
107+
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [mockRegistryScanV1] }),
108+
};
109+
110+
await renderInTestApp(
111+
<TestApiProvider apis={[
112+
[sysdigApiRef, apiWithData],
113+
[configApiRef, mockConfig],
114+
]}>
115+
<EntityProvider entity={mockEntity}>
116+
<SysdigVMRegistryFetchComponent />
117+
</EntityProvider>
118+
</TestApiProvider>
119+
);
120+
121+
expect(await screen.findByText('harbor.example.com/library/nginx:1.25')).toBeInTheDocument();
122+
});
96123
});

src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
import { sysdigApiRef } from '../../api';
3535

3636
type RegistryScan = {
37-
mainAssetName: string,
37+
pullString: string,
3838
imageId: string,
3939
resultId: string,
4040
vulnTotalBySeverity: {
@@ -57,7 +57,7 @@ type DenseTableProps = {
5757
{
5858
"createdAt": "2019-08-24T14:15:22Z",
5959
"imageId": "string",
60-
"mainAssetName": "string",
60+
"pullString": "string",
6161
"resultId": "string",
6262
"vulnTotalBySeverity": {
6363
"critical": 0,
@@ -81,7 +81,7 @@ export const DenseTable = ({ registryScans, title }: DenseTableProps) => {
8181
.flatMap(scan => {
8282
return {
8383
imageId: <code>{scan.imageId}</code>,
84-
asset: scan.mainAssetName,
84+
asset: scan.pullString,
8585
severity: getChips(scan.vulnTotalBySeverity)
8686
};
8787
});

src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.test.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ const mockEntityWithoutAnnotations = {
4242
},
4343
};
4444

45+
const mockRuntimeScanV1 = {
46+
resultId: 'result-abc123',
47+
resourceId: 'sha256:a1b2c3d4e5f6',
48+
mainAssetName: 'nginx:latest',
49+
policyEvaluationResult: 'failed',
50+
isRiskSpotlightEnabled: true,
51+
sbomId: 'sbom-xyz',
52+
scope: {
53+
'asset.type': 'workload',
54+
'kubernetes.cluster.name': 'test-cluster',
55+
'kubernetes.namespace.name': 'test-namespace',
56+
'kubernetes.workload.name': 'nginx',
57+
'kubernetes.workload.type': 'deployment',
58+
'kubernetes.pod.container.name': 'nginx',
59+
},
60+
vulnTotalBySeverity: { critical: 2, high: 5, medium: 10, low: 3, negligible: 1 },
61+
runningVulnTotalBySeverity: { critical: 1, high: 2, medium: 4, low: 1, negligible: 0 },
62+
};
63+
4564
const mockSysdigApi = {
4665
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }),
4766
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }),
@@ -93,4 +112,48 @@ describe('SysdigVMRuntimeFetchComponent', () => {
93112
const message = await screen.findByText(/missing annotation/i);
94113
expect(message).toBeInTheDocument();
95114
});
115+
116+
it('renders rows using v1 policyEvaluationResult field (without s)', async () => {
117+
const apiWithData = {
118+
...mockSysdigApi,
119+
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [mockRuntimeScanV1] }),
120+
};
121+
122+
await renderInTestApp(
123+
<TestApiProvider apis={[
124+
[sysdigApiRef, apiWithData],
125+
[configApiRef, mockConfig],
126+
]}>
127+
<EntityProvider entity={mockEntity}>
128+
<SysdigVMRuntimeFetchComponent />
129+
</EntityProvider>
130+
</TestApiProvider>
131+
);
132+
133+
expect(await screen.findByText('nginx:latest')).toBeInTheDocument();
134+
expect(screen.getByText('failed')).toBeInTheDocument();
135+
});
136+
137+
it('filters out rows with null policyEvaluationResult', async () => {
138+
const scanWithNullPolicy = { ...mockRuntimeScanV1, policyEvaluationResult: null as any };
139+
const apiWithData = {
140+
...mockSysdigApi,
141+
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [scanWithNullPolicy, mockRuntimeScanV1] }),
142+
};
143+
144+
await renderInTestApp(
145+
<TestApiProvider apis={[
146+
[sysdigApiRef, apiWithData],
147+
[configApiRef, mockConfig],
148+
]}>
149+
<EntityProvider entity={mockEntity}>
150+
<SysdigVMRuntimeFetchComponent />
151+
</EntityProvider>
152+
</TestApiProvider>
153+
);
154+
155+
// Only the scan with a non-null policyEvaluationResult should appear (1 row = 1 asset name)
156+
const rows = await screen.findAllByText('nginx:latest');
157+
expect(rows).toHaveLength(1);
158+
});
96159
});

src/components/SysdigVMRuntimeFetchComponent/SysdigVMRuntimeFetchComponent.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ import { sysdigApiRef } from '../../api';
4242
type RuntimeScan = {
4343
isRiskSpotlightEnabled: boolean,
4444
mainAssetName: string,
45-
policyEvaluationsResult: string,
45+
policyEvaluationResult: string,
46+
resourceId: string,
4647
resultId: string,
4748
runningVulnTotalBySeverity: {
4849
critical: number,
@@ -120,10 +121,10 @@ export const DenseTable = ({ runtimeScans, title }: DenseTableProps) => {
120121
// { title: 'URL', field: "url", width: "10%" },
121122
];
122123

123-
const data = runtimeScans.filter(scan => { return scan.policyEvaluationsResult !== null && scan.policyEvaluationsResult !== '' })
124+
const data = runtimeScans.filter(scan => { return scan.policyEvaluationResult !== null && scan.policyEvaluationResult !== '' })
124125
.flatMap(scan => {
125126
return {
126-
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationsResult),
127+
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationResult),
127128
asset: scan.mainAssetName,
128129
// scope: JSON.stringify(scan.scope),
129130
severity: getChips(scan.vulnTotalBySeverity),

src/infra/api/SysdigApiClient.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ConfigApi, FetchApi } from "@backstage/core-plugin-api";
44
import { rest } from "msw";
55
import { setupServer } from "msw/node";
66
import { SysdigApiClient } from "./SysdigApiClient";
7-
import { API_PROXY_BASE_PATH, API_VULN_RUNTIME } from "../../lib";
7+
import { API_PROXY_BASE_PATH, API_VULN_RUNTIME, API_VULN_REGISTRY, API_VULN_PIPELINE } from "../../lib";
88

99
describe("SysdigApiClient", () => {
1010
const configApi: ConfigApi = new ConfigReader({
@@ -46,4 +46,13 @@ describe("SysdigApiClient", () => {
4646
undefined,
4747
);
4848
});
49+
50+
it("should use v1 endpoint paths (not v1beta1)", () => {
51+
expect(API_VULN_RUNTIME).toMatch(/\/v1\//);
52+
expect(API_VULN_REGISTRY).toMatch(/\/v1\//);
53+
expect(API_VULN_PIPELINE).toMatch(/\/v1\//);
54+
expect(API_VULN_RUNTIME).not.toMatch(/v1beta1/);
55+
expect(API_VULN_REGISTRY).not.toMatch(/v1beta1/);
56+
expect(API_VULN_PIPELINE).not.toMatch(/v1beta1/);
57+
});
4958
});

src/lib/endpoints.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ export const API_PROXY_BASE_PATH = "/api/proxy/sysdig";
88
*/
99

1010
// API Endpoint for Vulnerability Management at Runtime
11-
export const API_VULN_RUNTIME = "/secure/vulnerability/v1beta1/runtime-results";
11+
export const API_VULN_RUNTIME = "/secure/vulnerability/v1/runtime-results";
1212

1313
// API Endpoint for Vulnerability Management at Registry
14-
export const API_VULN_REGISTRY = "/secure/vulnerability/v1beta1/registry-results";
14+
export const API_VULN_REGISTRY = "/secure/vulnerability/v1/registry-results";
1515

1616
// API Endpoint for Vulnerability Management at Pipeline
17-
export const API_VULN_PIPELINE = "/secure/vulnerability/v1beta1/pipeline-results";
17+
export const API_VULN_PIPELINE = "/secure/vulnerability/v1/pipeline-results";
1818

1919
// API Endpoint for Inventory (Posture)
2020
export const API_INVENTORY = "/api/cspm/v1/inventory/resources";
@@ -54,4 +54,4 @@ export function getBacklink(endpoint: string | undefined, backlink: string | und
5454
}
5555

5656
return backlink_base + backlink_section;
57-
}
57+
}

0 commit comments

Comments
 (0)