Skip to content

Commit f36f832

Browse files
committed
feat: implement OCI image analysis
1 parent 5fd685c commit f36f832

13 files changed

+167404
-17
lines changed

.eslintrc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
],
1919
"rules": {
2020
"curly": "warn",
21-
"eqeqeq": "warn",
21+
"eqeqeq": ["warn", "always", {"null": "never"}],
2222
"no-throw-literal": "warn",
2323
"sort-imports": "warn"
2424
},
2525
"ignorePatterns": [
2626
"integration"
2727
]
28-
}
28+
}

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"help": "^3.0.2",
5252
"https-proxy-agent": "^7.0.6",
5353
"node-fetch": "^2.6.7",
54-
"packageurl-js": "^1.0.2",
54+
"packageurl-js": "^1.2.1",
5555
"yargs": "^17.7.2"
5656
},
5757
"devDependencies": {

src/analysis.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import fs from "node:fs";
22
import path from "node:path";
3-
import {EOL} from "os";
4-
import {RegexNotToBeLogged, getCustom} from "./tools.js";
3+
import { EOL } from "os";
4+
import { RegexNotToBeLogged, getCustom } from "./tools.js";
55
import { HttpsProxyAgent } from "https-proxy-agent";
6+
import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
67

7-
export default { requestComponent, requestStack, validateToken }
8+
export default { requestComponent, requestStack, requestImages, validateToken }
89

910
const rhdaTokenHeader = "rhda-token";
1011
const rhdaSourceHeader = "rhda-source"
@@ -133,6 +134,54 @@ async function requestComponent(provider, manifest, url, opts = {}) {
133134
return Promise.resolve(result)
134135
}
135136

137+
/**
138+
*
139+
* @param {Array<string>} imageRefs
140+
* @param {string} url
141+
* @param {{}} [opts={}] - optional various options to pass along the application
142+
* @returns {Promise<string|import('../generated/backend/AnalysisReport').AnalysisReport>}
143+
*/
144+
async function requestImages(imageRefs, url, html = false, opts = {}) {
145+
const imageSboms = {}
146+
for (const image of imageRefs) {
147+
const parsedImageRef = parseImageRef(image)
148+
imageSboms[parsedImageRef.getPackageURL().toString()] = generateImageSBOM(parsedImageRef)
149+
}
150+
151+
const resp = await fetch(`${url}/api/v4/batch-analysis`, {
152+
method: 'POST',
153+
headers: {
154+
'Accept': html ? 'text/html' : 'application/json',
155+
'Content-Type': 'application/vnd.cyclonedx+json',
156+
...getTokenHeaders(opts)
157+
},
158+
body: JSON.stringify(imageSboms),
159+
})
160+
161+
console.error(JSON.stringify(imageSboms, '', '\t'))
162+
163+
if(resp.status === 200) {
164+
let result;
165+
if (!html) {
166+
result = await resp.json()
167+
} else {
168+
result = await resp.text()
169+
}
170+
if (process.env["EXHORT_DEBUG"] === "true") {
171+
let exRequestId = resp.headers.get("ex-request-id");
172+
if (exRequestId) {
173+
console.log("Unique Identifier associated with this request - ex-request-id=" + exRequestId)
174+
}
175+
console.log("Response body received from exhort server : " + EOL + EOL)
176+
console.log(JSON.stringify(result, null, 4))
177+
console.log("Ending time of sending component analysis request to exhort server= " + new Date())
178+
}
179+
return result
180+
} else {
181+
throw new Error(`Got error response from exhort backend - http return code : ${resp.status}, ex-request-id: ${resp.headers.get("ex-request-id")} error message => ${await resp.text()}`)
182+
}
183+
}
184+
136185
/**
137186
*
138187
* @param url the backend url to send the request to

src/index.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getCustom } from "./tools.js";
77
import.meta.dirname
88
import * as url from 'url';
99

10-
export default { componentAnalysis, stackAnalysis, validateToken }
10+
export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken }
1111

1212
export const exhortDevDefaultUrl = 'https://exhort.stage.devshift.net';
1313

@@ -148,6 +148,39 @@ async function componentAnalysis(manifest, opts = {}) {
148148
return await analysis.requestComponent(provider, manifest, theUrl, opts) // throws error request sending failed
149149
}
150150

151+
/**
152+
* @overload
153+
* @param {Array<string>} imageRefs
154+
* @param {true} html
155+
* @param {object} [opts={}]
156+
* @returns {Promise<string>}
157+
* @throws {Error}
158+
*/
159+
160+
/**
161+
* @overload
162+
* @param {Array<string>} imageRefs
163+
* @param {false} html
164+
* @param {object} [opts={}]
165+
* @returns {Promise<AnalysisReport>}
166+
* @throws {Error}
167+
*/
168+
169+
/**
170+
* Get image analysis report for a set of OCI image references.
171+
* @overload
172+
* @param {Array<string>} imageRefs - OCI image references
173+
* @param {boolean} [html=false] - true will return a html string, false will return AnalysisReport
174+
* @param {{}} [opts={}] - optional various options to pass along the application
175+
* @returns {Promise<string|AnalysisReport>}
176+
* @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
177+
* or backend request failed
178+
*/
179+
async function imageAnalysis(imageRefs, html = false, opts = {}) {
180+
theUrl = selectExhortBackend(opts)
181+
return await analysis.requestImages(imageRefs, theUrl, opts)
182+
}
183+
151184
/**
152185
* Validates the Exhort token.
153186
* @param {object} [opts={}] - Optional parameters, potentially including token override.

0 commit comments

Comments
 (0)