Skip to content
This repository was archived by the owner on Jul 8, 2025. It is now read-only.

Commit b769241

Browse files
committed
fix: handle API returning purl instead of original image string
1 parent d5ab263 commit b769241

File tree

5 files changed

+132
-190
lines changed

5 files changed

+132
-190
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/dependencyAnalysis/analysis.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,10 @@ interface ISource {
2020
dependencies: any[];
2121
}
2222

23-
/**
24-
* Represents data specification related to a dependency.
25-
*/
26-
interface IDependencyData {
27-
sourceId: string;
28-
issuesCount: number;
29-
recommendationRef: string;
30-
remediationRef: string
31-
highestVulnerabilitySeverity: string;
32-
}
33-
3423
/**
3524
* Implementation of IDependencyData interface.
3625
*/
37-
class DependencyData implements IDependencyData {
26+
class DependencyData {
3827
constructor(
3928
public sourceId: string,
4029
public issuesCount: number,

src/imageAnalysis/analysis.ts

Lines changed: 109 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -10,144 +10,92 @@ import { connection } from '../server';
1010
import { globalConfig } from '../config';
1111
import { isDefined, decodeUriPath } from '../utils';
1212
import { IImage } from '../imageAnalysis/collector';
13+
import { AnalysisReport } from '@trustification/exhort-api-spec/model/v4/AnalysisReport';
14+
import { DependencyReport } from '@trustification/exhort-api-spec/model/v4/DependencyReport';
15+
import { SourceSummary } from '@trustification/exhort-api-spec/model/v4/SourceSummary';
16+
import { ProviderReport } from '@trustification/exhort-api-spec/model/v4/ProviderReport';
17+
import { Source } from '@trustification/exhort-api-spec/model/v4/Source';
1318

1419
/**
1520
* Represents the Red Hat Dependency Analytics (RHDA) analysis report, with images mapped by string keys.
1621
*/
1722
interface IExhortAnalysisReport {
18-
[key: string]: IImageReport;
19-
}
20-
21-
/**
22-
* Represents the RHDA analysis report for a single image.
23-
*/
24-
interface IImageReport {
25-
providers: Map<string, IProvider>;
26-
}
27-
28-
/**
29-
* Represents a provider of dependencies for an image.
30-
*/
31-
interface IProvider {
32-
status: IStatus;
33-
sources: Map<string, ISource>;
34-
}
35-
36-
/**
37-
* Represents the status of a provider.
38-
*/
39-
interface IStatus {
40-
ok: boolean;
41-
}
42-
43-
/**
44-
* Represents a source of dependencies.
45-
*/
46-
interface ISource {
47-
summary: ISummary;
48-
dependencies: ISourceDependency[];
49-
}
50-
51-
/**
52-
* Represents the summary of vulnerabilities for a source.
53-
*/
54-
interface ISummary {
55-
total: number,
56-
critical: number,
57-
high: number,
58-
medium: number,
59-
low: number,
60-
}
61-
62-
/**
63-
* Represents a dependency reported by a source.
64-
*/
65-
interface ISourceDependency {
66-
recommendation: string | null;
23+
[key: string]: AnalysisReport;
6724
}
6825

6926
/**
7027
* Represents data collected related to an image.
7128
*/
7229
interface IArtifact {
7330
id: string;
74-
summary: ISummary;
75-
dependencies: ISourceDependency[];
31+
summary: SourceSummary;
32+
dependencies: DependencyReport[];
7633
}
7734

78-
/**
79-
* Represents data specification related to an image.
80-
*/
81-
interface IImageData {
82-
sourceId: string;
83-
issuesCount: number;
84-
recommendationRef: string;
85-
highestVulnerabilitySeverity: string;
86-
}
87-
88-
/**
89-
* Implementation of IImageData interface.
90-
*/
91-
class ImageData implements IImageData {
35+
class ImageData {
9236
constructor(
93-
public sourceId: string,
94-
public issuesCount: number,
95-
public recommendationRef: string,
96-
public highestVulnerabilitySeverity: string
37+
public sourceId: string,
38+
public issuesCount: number,
39+
public recommendationRef: string,
40+
public highestVulnerabilitySeverity: string
9741
) {}
9842
}
9943

10044
/**
101-
* Represents the parsed response of Red Hat Dependency Analytics (RHDA) analysis report, with images mapped by string keys.
102-
*/
45+
* Represents the parsed response of Red Hat Dependency Analytics (RHDA) analysis report, with images mapped by string keys.
46+
*/
10347
interface IAnalysisResponse {
10448
images: Map<string, ImageData[]>;
10549
}
10650

10751
/**
108-
* Implementation of IAnalysisResponse interface.
109-
*/
52+
* Implementation of IAnalysisResponse interface.
53+
*/
11054
class AnalysisResponse implements IAnalysisResponse {
11155
images: Map<string, ImageData[]> = new Map<string, ImageData[]>();
11256

11357
constructor(resData: IExhortAnalysisReport, diagnosticFilePath: string) {
114-
const failedProviders: string[] = [];
115-
116-
Object.entries(resData).map(([imageRef, imageData]) => {
117-
const artifacts: IArtifact[] = [];
118-
119-
if (isDefined(imageData, 'providers')) {
120-
Object.entries(imageData.providers).map(([providerName, providerData]: [string, IProvider]) => {
121-
if (isDefined(providerData, 'status', 'ok') && providerData.status.ok) {
122-
if (isDefined(providerData, 'sources')) {
123-
Object.entries(providerData.sources).map(([sourceName, sourceData]: [string, ISource]) => {
124-
if (isDefined(sourceData, 'summary')) {
125-
artifacts.push({id: `${providerName}(${sourceName})`, summary: sourceData.summary, dependencies: sourceData.dependencies});
126-
}
127-
});
128-
}
129-
} else {
130-
failedProviders.push(providerName);
58+
const failedProviders: string[] = [];
59+
60+
Object.entries(resData).map(([imageRef, imageData]) => {
61+
const artifacts: IArtifact[] = [];
62+
63+
if (isDefined(imageData, 'providers')) {
64+
Object.entries(imageData.providers).map(([providerName, providerData]: [string, ProviderReport]) => {
65+
if (providerData?.status?.ok) {
66+
if (isDefined(providerData, 'sources')) {
67+
Object.entries(providerData.sources).map(([sourceName, sourceData]: [string, Source]) => {
68+
if (isDefined(sourceData, 'summary')) {
69+
artifacts.push({
70+
id: `${providerName}(${sourceName})`,
71+
summary: sourceData.summary,
72+
dependencies: sourceData.dependencies,
73+
});
13174
}
132-
});
133-
134-
artifacts.forEach(artifact => {
135-
const sd = new ImageData(artifact.id, this.getTotalIssues(artifact.summary), this.getRecommendation(artifact.dependencies), this.getHighestSeverity(artifact.summary));
136-
137-
this.images[imageRef] = this.images[imageRef] || [];
138-
this.images[imageRef].push(sd);
139-
});
140-
}
75+
});
76+
}
77+
} else {
78+
failedProviders.push(providerName);
79+
}
80+
});
81+
82+
artifacts.forEach(artifact => {
83+
const sd = new ImageData(artifact.id, this.getTotalIssues(artifact.summary), this.getRecommendation(artifact.dependencies), this.getHighestSeverity(artifact.summary));
84+
85+
this.images[imageRef] = this.images[imageRef] || [];
86+
this.images[imageRef].push(sd);
87+
});
88+
}
14189

142-
if (failedProviders.length !== 0) {
143-
const uniqueFailedProviders = Array.from(new Set(failedProviders));
144-
const errMsg = `The image component analysis couldn't fetch data from the following providers: [${uniqueFailedProviders.join(', ')}]`;
145-
connection.console.warn(`Component Analysis Error: ${errMsg}`);
146-
connection.sendNotification('caError', {
147-
errorMessage: errMsg,
148-
uri: decodeUriPath(diagnosticFilePath),
149-
});
150-
}
90+
if (failedProviders.length !== 0) {
91+
const uniqueFailedProviders = Array.from(new Set(failedProviders));
92+
const errMsg = `The image component analysis couldn't fetch data from the following providers: [${uniqueFailedProviders.join(', ')}]`;
93+
connection.console.warn(`Component Analysis Error: ${errMsg}`);
94+
connection.sendNotification('caError', {
95+
errorMessage: errMsg,
96+
uri: decodeUriPath(diagnosticFilePath),
97+
});
98+
}
15199
});
152100
}
153101

@@ -157,8 +105,8 @@ class AnalysisResponse implements IAnalysisResponse {
157105
* @returns The total number of issues.
158106
* @private
159107
*/
160-
private getTotalIssues(summary: any): number {
161-
return isDefined(summary, 'total') ? summary.total : 0;
108+
private getTotalIssues(summary: any): number {
109+
return isDefined(summary, 'total') ? summary.total : 0;
162110
}
163111

164112
/**
@@ -168,19 +116,19 @@ class AnalysisResponse implements IAnalysisResponse {
168116
* @private
169117
*/
170118
private getHighestSeverity(summary: any): string {
171-
let highestSeverity = 'NONE';
172-
173-
if ( isDefined(summary, 'critical') && summary.critical > 0) {
174-
highestSeverity = 'CRITICAL';
175-
} else if ( isDefined(summary, 'high') && summary.high > 0) {
176-
highestSeverity = 'HIGH';
177-
} else if ( isDefined(summary, 'medium') && summary.medium > 0) {
178-
highestSeverity = 'MEDIUM';
179-
} else if ( isDefined(summary, 'low') && summary.low > 0) {
180-
highestSeverity = 'LOW';
181-
}
119+
let highestSeverity = 'NONE';
120+
121+
if (isDefined(summary, 'critical') && summary.critical > 0) {
122+
highestSeverity = 'CRITICAL';
123+
} else if (isDefined(summary, 'high') && summary.high > 0) {
124+
highestSeverity = 'HIGH';
125+
} else if (isDefined(summary, 'medium') && summary.medium > 0) {
126+
highestSeverity = 'MEDIUM';
127+
} else if (isDefined(summary, 'low') && summary.low > 0) {
128+
highestSeverity = 'LOW';
129+
}
182130

183-
return highestSeverity;
131+
return highestSeverity;
184132
}
185133

186134
/**
@@ -189,10 +137,10 @@ class AnalysisResponse implements IAnalysisResponse {
189137
* @returns The recommendation reference or an empty string.
190138
* @private
191139
*/
192-
private getRecommendation(dependencies: ISourceDependency[]): string {
193-
let recommendation = '';
194-
if (dependencies && dependencies.length > 0){
195-
recommendation = isDefined(dependencies[0], 'recommendation') ? dependencies[0].recommendation.split(':')[1].split('@')[0] : '';
140+
private getRecommendation(dependencies: DependencyReport[]): string {
141+
let recommendation = '';
142+
if (dependencies && dependencies.length > 0) {
143+
recommendation = isDefined(dependencies[0], 'recommendation') ? dependencies[0].recommendation.split(':')[1].split('@')[0] : '';
196144
}
197145
return recommendation;
198146
}
@@ -202,15 +150,15 @@ class AnalysisResponse implements IAnalysisResponse {
202150
* Represents the options for running image analysis.
203151
*/
204152
interface IOptions {
205-
RHDA_TOKEN: string;
206-
RHDA_SOURCE: string;
207-
EXHORT_SYFT_PATH: string;
208-
EXHORT_SYFT_CONFIG_PATH: string;
209-
EXHORT_SKOPEO_PATH: string;
210-
EXHORT_SKOPEO_CONFIG_PATH: string;
211-
EXHORT_DOCKER_PATH: string;
212-
EXHORT_PODMAN_PATH: string;
213-
EXHORT_IMAGE_PLATFORM: string;
153+
RHDA_TOKEN: string;
154+
RHDA_SOURCE: string;
155+
EXHORT_SYFT_PATH: string;
156+
EXHORT_SYFT_CONFIG_PATH: string;
157+
EXHORT_SKOPEO_PATH: string;
158+
EXHORT_SKOPEO_CONFIG_PATH: string;
159+
EXHORT_DOCKER_PATH: string;
160+
EXHORT_PODMAN_PATH: string;
161+
EXHORT_IMAGE_PLATFORM: string;
214162
}
215163

216164
/**
@@ -220,38 +168,38 @@ interface IOptions {
220168
* @returns A Promise resolving to the analysis response.
221169
*/
222170
async function imageAnalysisService(images: IImage[], options: IOptions): Promise<any> {
223-
return await exhort.imageAnalysis(images.map(img => {
224-
if (img.platform) {
225-
return `${img.name.value}^^${img.platform}`;
226-
}
227-
return img.name.value;
228-
}), true, options);
229-
}
230-
171+
return await exhort.imageAnalysis(images.map(img => {
172+
if (img.platform) {
173+
return `${img.name.value}^^${img.platform}`;
174+
}
175+
return img.name.value;
176+
}), false, options);
177+
}
178+
231179
/**
232180
* Performs RHDA image analysis on provided images.
233181
* @param diagnosticFilePath - The path to the image file to analyze.
234182
* @param images - The images to analyze.
235183
* @returns A Promise resolving to an AnalysisResponse object.
236184
*/
237-
async function executeImageAnalysis(diagnosticFilePath: string, images: IImage[]): Promise<AnalysisResponse> {
238-
239-
// Define configuration options for the component analysis request
240-
const options: IOptions = {
241-
'RHDA_TOKEN': globalConfig.telemetryId,
242-
'RHDA_SOURCE': globalConfig.utmSource,
243-
'EXHORT_SYFT_PATH': globalConfig.exhortSyftPath,
244-
'EXHORT_SYFT_CONFIG_PATH': globalConfig.exhortSyftConfigPath,
245-
'EXHORT_SKOPEO_PATH': globalConfig.exhortSkopeoPath,
246-
'EXHORT_SKOPEO_CONFIG_PATH': globalConfig.exhortSkopeoConfigPath,
247-
'EXHORT_DOCKER_PATH': globalConfig.exhortDockerPath,
248-
'EXHORT_PODMAN_PATH': globalConfig.exhortPodmanPath,
249-
'EXHORT_IMAGE_PLATFORM': globalConfig.exhortImagePlatform,
250-
};
251-
252-
const imageAnalysisJson = await imageAnalysisService(images, options);
253-
254-
return new AnalysisResponse(imageAnalysisJson, diagnosticFilePath);
185+
async function executeImageAnalysis(diagnosticFilePath: string, images: IImage[]): Promise<AnalysisResponse> {
186+
187+
// Define configuration options for the component analysis request
188+
const options: IOptions = {
189+
'RHDA_TOKEN': globalConfig.telemetryId,
190+
'RHDA_SOURCE': globalConfig.utmSource,
191+
'EXHORT_SYFT_PATH': globalConfig.exhortSyftPath,
192+
'EXHORT_SYFT_CONFIG_PATH': globalConfig.exhortSyftConfigPath,
193+
'EXHORT_SKOPEO_PATH': globalConfig.exhortSkopeoPath,
194+
'EXHORT_SKOPEO_CONFIG_PATH': globalConfig.exhortSkopeoConfigPath,
195+
'EXHORT_DOCKER_PATH': globalConfig.exhortDockerPath,
196+
'EXHORT_PODMAN_PATH': globalConfig.exhortPodmanPath,
197+
'EXHORT_IMAGE_PLATFORM': globalConfig.exhortImagePlatform,
198+
};
199+
200+
const imageAnalysisJson = await imageAnalysisService(images, options);
201+
202+
return new AnalysisResponse(imageAnalysisJson, diagnosticFilePath);
255203
}
256204

257205
export { executeImageAnalysis, ImageData };

0 commit comments

Comments
 (0)