Skip to content

Commit cef098c

Browse files
committed
feat: implement OCI image analysis
1 parent 336a206 commit cef098c

13 files changed

+167403
-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
@@ -50,7 +50,7 @@
5050
"fast-xml-parser": "^4.2.4",
5151
"help": "^3.0.2",
5252
"node-fetch": "^2.6.7",
53-
"packageurl-js": "^1.0.2",
53+
"packageurl-js": "^1.2.1",
5454
"yargs": "^17.7.2"
5555
},
5656
"devDependencies": {

src/analysis.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
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";
5+
import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
56
import http from 'node:http';
67
import https from 'node:https';
78

8-
export default { requestComponent, requestStack, validateToken }
9+
export default { requestComponent, requestStack, requestImages, validateToken }
910

1011
const rhdaTokenHeader = "rhda-token";
1112
const rhdaSourceHeader = "rhda-source"
@@ -137,6 +138,54 @@ async function requestComponent(provider, manifest, url, opts = {}) {
137138
return Promise.resolve(result)
138139
}
139140

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