Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,14 @@ This increasing the chances and the probability that the automatic installation
###### Usage
A New setting is introduced - `EXHORT_PYTHON_INSTALL_BEST_EFFORTS` (as both env variable/key in `options` object)
1. `EXHORT_PYTHON_INSTALL_BEST_EFFORTS`="false" - install requirements.txt while respecting declared versions for all packages.
2. `EXHORT_PYTHON_INSTALL_BEST_EFFORTS`="true" - install all packages from requirements.txt, not respecting the declared version, but trying to install a version tailored for the used python version, when using this setting,you must set setting `MATCH_MANIFEST_VERSIONS`="false"
2. `EXHORT_PYTHON_INSTALL_BEST_EFFORTS`="true" - install all packages from requirements.txt, not respecting the declared version, but trying to install a version tailored for the used python version. When using this setting, you must set setting `MATCH_MANIFEST_VERSIONS` to 'false'.

##### Using `pipdeptree`
By Default, The API algorithm will use native commands of PIP installer as data source to build the dependency tree.
It's also possible, to use lightweight Python PIP utility [pipdeptree](https://pypi.org/project/pipdeptree/) as data source instead, in order to activate this,
Need to set environment variable/option - `EXHORT_PIP_USE_DEP_TREE` to true.
By default, The API algorithm will use native commands of PIP installer as data source to build the dependency tree.
It's also possible to use the lightweight Python PIP utility [pipdeptree](https://pypi.org/project/pipdeptree/) as data source instead. In order to activate this, you need to set the environment variable/option `EXHORT_PIP_USE_DEP_TREE` to 'true'.

#### Toggle Red Hat Trusted Content recommendations
Both the HTML-based report and JSON response will by default contain recommendations for migrating to Red Hat-based Trusted Content repositories. This feature can be disabled by setting `EXHORT_RECOMMENDATIONS_ENABLED` to 'false' via environment variables or options.

<!-- Badge links -->
[0]: https://img.shields.io/github/v/release/trustification/exhort-javascript-api?color=green&label=latest
Expand All @@ -500,6 +502,6 @@ Need to set environment variable/option - `EXHORT_PIP_USE_DEP_TREE` to true.


- For maven pom.xml, it has been noticed that using java 17 might cause stack analysis to hang forever.
This is caused by maven [`dependency` Plugin](https://maven.apache.org/plugins/maven-dependency-plugin/) bug when running with JDK/JRE' JVM version 17.
This is caused by maven [`dependency` plugin](https://maven.apache.org/plugins/maven-dependency-plugin/) bug when running with JDK/JRE' JVM version 17.

To overcome this, you can use any other java version (14,20,21, etc..). ( best way is to install JDK/JRE version different from 17 , and set the location of the installation in environment variable `JAVA_HOME` so maven will use it.)
71 changes: 42 additions & 29 deletions src/analysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ const rhdaOperationTypeHeader = "rhda-operation-type"

/**
* Adds proxy agent configuration to fetch options if a proxy URL is specified
* @param {Object} options - The base fetch options
* @param {Object} opts - The exhort options that may contain proxy configuration
* @returns {Object} The fetch options with proxy agent if applicable
* @param {RequestInit} options - The base fetch options
* @param {import("index.js").Options} opts - The exhort options that may contain proxy configuration
* @returns {RequestInit} The fetch options with proxy agent if applicable
*/
function addProxyAgent(options, opts) {
const proxyUrl = getCustom('EXHORT_PROXY_URL', null, opts);
Expand All @@ -33,15 +33,15 @@ function addProxyAgent(options, opts) {
* @param {string} manifest - path for the manifest
* @param {string} url - the backend url to send the request to
* @param {boolean} [html=false] - true will return 'text/html', false will return 'application/json'
* @param {{}} [opts={}] - optional various options to pass along the application
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
* @returns {Promise<string|import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>}
*/
async function requestStack(provider, manifest, url, html = false, opts = {}) {
opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64')
opts["manifest-type"] = path.parse(manifest).base
let provided = provider.provideStack(manifest, opts) // throws error if content providing failed
opts["source-manifest"]= ""
opts[rhdaOperationTypeHeader.toUpperCase().replaceAll("-","_")] = "stack-analysis"
opts["source-manifest"] = ""
opts[rhdaOperationTypeHeader.toUpperCase().replaceAll("-", "_")] = "stack-analysis"
let startTime = new Date()
let EndTime
if (process.env["EXHORT_DEBUG"] === "true") {
Expand All @@ -58,9 +58,14 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
body: provided.content
}, opts);

let resp = await fetch(`${url}/api/v4/analysis`, fetchOptions)
const finalUrl = new URL(`${url}/api/v4/analysis`);
if (opts['EXHORT_RECOMMENDATIONS_ENABLED'] === 'false') {
finalUrl.searchParams.append('recommend', 'false');
}

let resp = await fetch(finalUrl, fetchOptions)
let result
if(resp.status === 200) {
if (resp.status === 200) {
if (!html) {
result = await resp.json()
} else {
Expand Down Expand Up @@ -91,15 +96,15 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
* @param {import('./provider').Provider} provider - the provided data for constructing the request
* @param {string} manifest - path for the manifest
* @param {string} url - the backend url to send the request to
* @param {{}} [opts={}] - optional various options to pass along the application
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
* @returns {Promise<import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>}
*/
async function requestComponent(provider, manifest, url, opts = {}) {
opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64')

let provided = provider.provideComponent(manifest, opts) // throws error if content providing failed
opts["source-manifest"] = ""
opts[rhdaOperationTypeHeader.toUpperCase().replaceAll("-","_")] = "component-analysis"
opts[rhdaOperationTypeHeader.toUpperCase().replaceAll("-", "_")] = "component-analysis"
if (process.env["EXHORT_DEBUG"] === "true") {
console.log("Starting time of sending component analysis request to exhort server= " + new Date())
}
Expand All @@ -114,9 +119,14 @@ async function requestComponent(provider, manifest, url, opts = {}) {
body: provided.content
}, opts);

let resp = await fetch(`${url}/api/v4/analysis`, fetchOptions)
const finalUrl = new URL(`${url}/api/v4/analysis`);
if (opts['EXHORT_RECOMMENDATIONS_ENABLED'] === 'false') {
finalUrl.searchParams.append('recommend', 'false');
}

let resp = await fetch(finalUrl, fetchOptions)
let result
if(resp.status === 200) {
if (resp.status === 200) {
result = await resp.json()
if (process.env["EXHORT_DEBUG"] === "true") {
let exRequestId = resp.headers.get("ex-request-id");
Expand All @@ -140,7 +150,7 @@ async function requestComponent(provider, manifest, url, opts = {}) {
*
* @param {Array<string>} imageRefs
* @param {string} url
* @param {{}} [opts={}] - optional various options to pass along the application
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
* @returns {Promise<string|Object.<string, import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>>}
*/
async function requestImages(imageRefs, url, html = false, opts = {}) {
Expand All @@ -150,7 +160,12 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
imageSboms[parsedImageRef.getPackageURL().toString()] = generateImageSBOM(parsedImageRef)
}

const resp = await fetch(`${url}/api/v4/batch-analysis`, {
const finalUrl = new URL(`${url}/api/v4/batch-analysis`);
if (opts['EXHORT_RECOMMENDATIONS_ENABLED'] === 'false') {
finalUrl.searchParams.append('recommend', 'false');
}

const resp = await fetch(finalUrl, {
method: 'POST',
headers: {
'Accept': html ? 'text/html' : 'application/json',
Expand All @@ -160,7 +175,7 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
body: JSON.stringify(imageSboms),
})

if(resp.status === 200) {
if (resp.status === 200) {
let result;
if (!html) {
result = await resp.json()
Expand All @@ -185,7 +200,7 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
/**
*
* @param url the backend url to send the request to
* @param {{}} [opts={}] - optional various options to pass headers for t he validateToken Request
* @param {import("index.js").Options} [opts={}] - optional various options to pass headers for t he validateToken Request
* @return {Promise<number>} return the HTTP status Code of the response from the validate token request.
*/
async function validateToken(url, opts = {}) {
Expand All @@ -210,10 +225,10 @@ async function validateToken(url, opts = {}) {
*
* @param {string} headerName - the header name to populate in request
* @param headers
* @param {{}} [opts={}] - optional various options to pass along the application
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
* @private
*/
function setRhdaHeader(headerName,headers,opts) {
function setRhdaHeader(headerName, headers, opts) {
let rhdaHeaderValue = getCustom(headerName.toUpperCase().replaceAll("-", "_"), null, opts);
if (rhdaHeaderValue) {
headers[headerName] = rhdaHeaderValue
Expand All @@ -222,31 +237,29 @@ function setRhdaHeader(headerName,headers,opts) {

/**
* Utility function for fetching vendor tokens
* @param {{}} [opts={}] - optional various options to pass along the application
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
* @returns {{}}
*/
function getTokenHeaders(opts = {}) {
let supportedTokens = ['snyk','oss-index']
let supportedTokens = ['snyk', 'oss-index']
let headers = {}
supportedTokens.forEach(vendor => {
let token = getCustom(`EXHORT_${vendor.replace("-","_").toUpperCase()}_TOKEN`, null, opts);
let token = getCustom(`EXHORT_${vendor.replace("-", "_").toUpperCase()}_TOKEN`, null, opts);
if (token) {
headers[`ex-${vendor}-token`] = token
}
let user = getCustom(`EXHORT_${vendor.replace("-","_").toUpperCase()}_USER`, null, opts);
let user = getCustom(`EXHORT_${vendor.replace("-", "_").toUpperCase()}_USER`, null, opts);
if (user) {
headers[`ex-${vendor}-user`] = user
}
})
setRhdaHeader(rhdaTokenHeader,headers, opts);
setRhdaHeader(rhdaSourceHeader,headers, opts);
setRhdaHeader(rhdaOperationTypeHeader, headers,opts);
if (process.env["EXHORT_DEBUG"] === "true")
{
setRhdaHeader(rhdaTokenHeader, headers, opts);
setRhdaHeader(rhdaSourceHeader, headers, opts);
setRhdaHeader(rhdaOperationTypeHeader, headers, opts);
if (process.env["EXHORT_DEBUG"] === "true") {
console.log("Headers Values to be sent to exhort:" + EOL)
for (const headerKey in headers) {
if(!headerKey.match(RegexNotToBeLogged))
{
if (!headerKey.match(RegexNotToBeLogged)) {
console.log(`${headerKey}: ${headers[headerKey]}`)
}
}
Expand Down
50 changes: 42 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,40 @@ export const exhortDevDefaultUrl = 'https://exhort.stage.devshift.net';
/** @type {string} The default production URL for the Exhort backend. */
export const exhortDefaultUrl = "https://rhda.rhcloud.com";

/**
* @typedef {{
* EXHORT_DOCKER_PATH?: string | undefined,
* EXHORT_GO_MVS_LOGIC_ENABLED?: string | undefined,
* EXHORT_GO_PATH?: string | undefined,
* EXHORT_GRADLE_PATH?: string | undefined,
* EXHORT_IMAGE_PLATFORM?: string | undefined,
* EXHORT_MVN_PATH?: string | undefined,
* EXHORT_PIP_PATH?: string | undefined,
* EXHORT_PIP_USE_DEP_TREE?: string | undefined,
* EXHORT_PIP3_PATH?: string | undefined,
* EXHORT_PNPM_PATH?: string | undefined,
* EXHORT_PODMAN_PATH?: string | undefined,
* EXHORT_PREFER_GRADLEW?: string | undefined,
* EXHORT_PREFER_MVNW?: string | undefined,
* EXHORT_PROXY_URL?: string | undefined,
* EXHORT_PYTHON_INSTALL_BEST_EFFORTS?: string | undefined,
* EXHORT_PYTHON_PATH?: string | undefined,
* EXHORT_PYTHON_VIRTUAL_ENV?: string | undefined,
* EXHORT_PYTHON3_PATH?: string | undefined,
* EXHORT_RECOMMENDATIONS_ENABLED?: string | undefined,
* EXHORT_SKOPEO_CONFIG_PATH?: string | undefined,
* EXHORT_SKOPEO_PATH?: string | undefined,
* EXHORT_SYFT_CONFIG_PATH?: string | undefined,
* EXHORT_SYFT_PATH?: string | undefined,
* EXHORT_YARN_PATH?: string | undefined,
* MATCH_MANIFEST_VERSIONS?: string | undefined,
* RHDA_SOURCE?: string | undefined,
* RHDA_TOKEN?: string | undefined,
* [key: string]: string | undefined,
* }} Options
*/


/**
* Logs messages to the console if the EXHORT_DEBUG environment variable is set to "true".
* @param {string} alongsideText - The text to prepend to the log message.
Expand Down Expand Up @@ -105,7 +139,7 @@ let theUrl
* @overload
* @param {string} manifest
* @param {true} html
* @param {object} [opts={}]
* @param {Options} [opts={}]
* @returns {Promise<string>}
* @throws {Error}
*/
Expand All @@ -114,7 +148,7 @@ let theUrl
* @overload
* @param {string} manifest
* @param {false} html
* @param {object} [opts={}]
* @param {Options} [opts={}]
* @returns {Promise<import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>}
* @throws {Error}
*/
Expand All @@ -124,7 +158,7 @@ let theUrl
* @overload
* @param {string} manifest - path for the manifest
* @param {boolean} [html=false] - true will return a html string, false will return AnalysisReport object.
* @param {object} [opts={}] - optional various options to pass along the application
* @param {Options} [opts={}] - optional various options to pass along the application
* @returns {Promise<string|import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>}
* @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
* or backend request failed
Expand All @@ -139,7 +173,7 @@ async function stackAnalysis(manifest, html = false, opts = {}) {
/**
* Get component analysis report for a manifest content.
* @param {string} manifest - path to the manifest
* @param {object} [opts={}] - optional various options to pass along the application
* @param {Options} [opts={}] - optional various options to pass along the application
* @returns {Promise<import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>}
* @throws {Error} if no matching provider, failed to get create content, or backend request failed
*/
Expand All @@ -155,7 +189,7 @@ async function componentAnalysis(manifest, opts = {}) {
* @overload
* @param {Array<string>} imageRefs
* @param {true} html
* @param {object} [opts={}]
* @param {Options} [opts={}]
* @returns {Promise<string>}
* @throws {Error}
*/
Expand All @@ -164,7 +198,7 @@ async function componentAnalysis(manifest, opts = {}) {
* @overload
* @param {Array<string>} imageRefs
* @param {false} html
* @param {object} [opts={}]
* @param {Options} [opts={}]
* @returns {Promise<Object.<string, import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>>}
* @throws {Error}
*/
Expand All @@ -174,7 +208,7 @@ async function componentAnalysis(manifest, opts = {}) {
* @overload
* @param {Array<string>} imageRefs - OCI image references
* @param {boolean} [html=false] - true will return a html string, false will return AnalysisReport
* @param {{}} [opts={}] - optional various options to pass along the application
* @param {Options} [opts={}] - optional various options to pass along the application
* @returns {Promise<string|Object.<string, import('@trustification/exhort-api-spec/model/v4/AnalysisReport').AnalysisReport>>}
* @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
* or backend request failed
Expand All @@ -186,7 +220,7 @@ async function imageAnalysis(imageRefs, html = false, opts = {}) {

/**
* Validates the Exhort token.
* @param {object} [opts={}] - Optional parameters, potentially including token override.
* @param {Options} [opts={}] - Optional parameters, potentially including token override.
* @returns {Promise<object>} A promise that resolves with the validation result from the backend.
* @throws {Error} if the backend request failed.
*/
Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"module": "esnext",
"outDir": "dist",
"target": "esnext",
"resolveJsonModule": true
"resolveJsonModule": true,
"exactOptionalPropertyTypes": true,
"strictNullChecks": true
}
}
Loading