Skip to content

Commit b41be0d

Browse files
committed
chore: simplify licenseCheck and remove unnecessary method
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
1 parent ca48015 commit b41be0d

3 files changed

Lines changed: 43 additions & 126 deletions

File tree

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as url from 'url';
99

1010
export { parseImageRef } from "./oci_image/utils.js";
1111
export { ImageRef } from "./oci_image/images.js";
12-
export { getProjectLicense, getProjectLicenseFromManifest as getProjectLicenseFromManifestOnly, findLicenseFilePath, identifyLicenseViaBackend, getLicensesByPurl, getLicenseDetails, licenseMapFromAnalysisReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility, extractPurlsFromSbom } from "./license/index.js";
12+
export { getProjectLicense, getProjectLicenseFromManifest as getProjectLicenseFromManifestOnly, findLicenseFilePath, identifyLicenseViaBackend, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
1313

1414
export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken }
1515

src/license/index.js

Lines changed: 41 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,13 @@
33
*/
44

55
import { getProjectLicense, findLicenseFilePath, identifyLicense } from './project_license.js';
6-
import { licenseMapFromAnalysisReport, getLicenseDetails } from './licenses_api.js';
6+
import { licensesFromReport, getLicenseDetails } from './licenses_api.js';
77
import { getCompatibility } from './compatibility.js';
88

99
export { getProjectLicense, getProjectLicenseFromManifest, findLicenseFilePath, identifyLicense as identifyLicenseViaBackend } from './project_license.js';
10-
export { licenseMapFromAnalysisReport, normalizeLicensesResponse, getLicensesByPurl, getLicenseDetails } from './licenses_api.js';
10+
export { licensesFromReport as licensesFromReport, normalizeLicensesResponse, getLicenseDetails } from './licenses_api.js';
1111
export { getCompatibility } from './compatibility.js';
1212

13-
/**
14-
* Extract all component purls from a CycloneDX SBOM JSON string (excluding root if desired).
15-
* @param {string} sbomContent - CycloneDX JSON string
16-
* @param {boolean} [excludeRoot=true] - if true, exclude the root component's purl from the list
17-
* @returns {string[]}
18-
*/
19-
export function extractPurlsFromSbom(sbomContent, excludeRoot = true) {
20-
let obj;
21-
try {
22-
obj = typeof sbomContent === 'string' ? JSON.parse(sbomContent) : sbomContent;
23-
} catch {
24-
return [];
25-
}
26-
const components = obj?.components;
27-
if (!Array.isArray(components)) return [];
28-
29-
const rootRef = obj?.metadata?.component?.["bom-ref"] ?? obj?.metadata?.component?.purl;
30-
const purls = components
31-
.map(c => c.purl || c["bom-ref"] || c.bomRef)
32-
.filter(Boolean);
33-
34-
if (excludeRoot && rootRef) {
35-
const rootPurl = typeof rootRef === 'string' ? rootRef : rootRef?.purl;
36-
return purls.filter(p => p !== rootPurl);
37-
}
38-
return purls;
39-
}
40-
4113
/**
4214
* Run full license check: resolve project license (with backend identification and details),
4315
* get dependency licenses from analysis report, and compute incompatibilities.
@@ -50,80 +22,77 @@ export function extractPurlsFromSbom(sbomContent, excludeRoot = true) {
5022
* @returns {Promise<{ projectLicense: { manifest: Object|null, file: Object|null, mismatch: boolean }, incompatibleDependencies: Array<{ purl: string, licenses: string[], category?: string, reason: string }>, error?: string }>}
5123
*/
5224
export async function runLicenseCheck(sbomContent, manifestPath, backendUrl, opts = {}, analysisResult = null) {
53-
// Get project license from manifest (always available)
25+
// Resolve project license from manifest and LICENSE file
5426
const projectLicense = getProjectLicense(manifestPath, opts);
5527

56-
// Try to get more accurate LICENSE file identification from backend
57-
let projectLicenseFromFileBackend = null;
28+
// Try backend identification for LICENSE file (more accurate than local pattern matching)
5829
const licenseFilePath = findLicenseFilePath(manifestPath);
30+
let backendFileId = null;
5931
if (licenseFilePath && backendUrl) {
6032
try {
61-
projectLicenseFromFileBackend = await identifyLicense(licenseFilePath, backendUrl, opts);
33+
backendFileId = await identifyLicense(licenseFilePath, backendUrl, opts);
6234
} catch {
63-
// Fall back to local detection (already in projectLicense.fromFile)
35+
// Fall back to local detection
6436
}
6537
}
6638

67-
// Use backend identification if available, otherwise use local detection
68-
const finalFromFile = projectLicenseFromFileBackend || projectLicense.fromFile;
69-
const finalMismatch = Boolean(
70-
projectLicense.fromManifest && finalFromFile &&
71-
projectLicense.fromManifest.toLowerCase() !== finalFromFile.toLowerCase()
72-
);
39+
// Determine final license identifiers
40+
const manifestSpdx = projectLicense.fromManifest;
41+
const fileSpdx = backendFileId || projectLicense.fromFile;
42+
const mismatch = Boolean(manifestSpdx && fileSpdx && manifestSpdx.toLowerCase() !== fileSpdx.toLowerCase());
7343

74-
// Fetch detailed license info from backend for both manifest and file licenses
75-
let manifestLicenseInfo = null;
76-
let fileLicenseInfo = null;
44+
// Fetch detailed license info from backend (avoid duplicate calls if same license)
45+
const licenseDetailsCache = new Map();
7746

78-
if (projectLicense.fromManifest && backendUrl) {
79-
try {
80-
manifestLicenseInfo = await getLicenseDetails(projectLicense.fromManifest, backendUrl, opts);
81-
} catch {
82-
// Backend might not have this license; keep as null
83-
}
84-
}
47+
async function getDetails(spdxId) {
48+
if (!spdxId || !backendUrl) return null;
49+
if (licenseDetailsCache.has(spdxId)) return licenseDetailsCache.get(spdxId);
8550

86-
if (finalFromFile && backendUrl) {
8751
try {
88-
fileLicenseInfo = await getLicenseDetails(finalFromFile, backendUrl, opts);
52+
const details = await getLicenseDetails(spdxId, backendUrl, opts);
53+
licenseDetailsCache.set(spdxId, details);
54+
return details;
8955
} catch {
90-
// Backend might not have this license; keep as null
56+
return null;
9157
}
9258
}
9359

94-
const purls = extractPurlsFromSbom(sbomContent, true);
60+
const manifestLicenseInfo = await getDetails(manifestSpdx);
61+
const fileLicenseInfo = await getDetails(fileSpdx);
62+
63+
// Extract dependency purls from SBOM (exclude root component)
64+
const sbomObj = typeof sbomContent === 'string' ? JSON.parse(sbomContent) : sbomContent;
65+
const rootRef = sbomObj?.metadata?.component?.["bom-ref"] || sbomObj?.metadata?.component?.purl;
66+
const purls = (sbomObj?.components || [])
67+
.map(c => c.purl || c["bom-ref"])
68+
.filter(Boolean)
69+
.filter(purl => !rootRef || purl !== rootRef);
70+
9571
if (purls.length === 0) {
9672
return {
97-
projectLicense: {
98-
manifest: manifestLicenseInfo,
99-
file: fileLicenseInfo,
100-
mismatch: finalMismatch
101-
},
73+
projectLicense: { manifest: manifestLicenseInfo, file: fileLicenseInfo, mismatch },
10274
incompatibleDependencies: []
10375
};
10476
}
10577

106-
// Get dependency licenses from analysis report (backend already provides this)
107-
const licenseByPurl = licenseMapFromAnalysisReport(analysisResult, purls);
78+
// Get dependency licenses from analysis report
79+
const licenseByPurl = licensesFromReport(analysisResult, purls);
10880
if (licenseByPurl.size === 0 && analysisResult) {
109-
// No license data in analysis report - this might be expected for some backends
11081
return {
111-
projectLicense: {
112-
manifest: manifestLicenseInfo,
113-
file: fileLicenseInfo,
114-
mismatch: finalMismatch
115-
},
82+
projectLicense: { manifest: manifestLicenseInfo, file: fileLicenseInfo, mismatch },
11683
incompatibleDependencies: [],
11784
error: 'No license data available in analysis report'
11885
};
11986
}
12087

121-
// Use backend category from project license details (prefer manifest, fallback to file)
122-
const projectCategory = manifestLicenseInfo?.category || fileLicenseInfo?.category || null;
88+
// Check compatibility for each dependency
89+
const projectCategory = manifestLicenseInfo?.category || fileLicenseInfo?.category;
12390
const incompatibleDependencies = [];
12491

12592
for (const purl of purls) {
126-
const entry = licenseByPurl.get(purl) || { licenses: [], category: undefined };
93+
const entry = licenseByPurl.get(purl);
94+
if (!entry) continue;
95+
12796
const status = getCompatibility(projectCategory, entry.category);
12897
if (status === 'incompatible') {
12998
incompatibleDependencies.push({
@@ -136,11 +105,7 @@ export async function runLicenseCheck(sbomContent, manifestPath, backendUrl, opt
136105
}
137106

138107
return {
139-
projectLicense: {
140-
manifest: manifestLicenseInfo,
141-
file: fileLicenseInfo,
142-
mismatch: finalMismatch
143-
},
108+
projectLicense: { manifest: manifestLicenseInfo, file: fileLicenseInfo, mismatch },
144109
incompatibleDependencies
145110
};
146111
}

src/license/licenses_api.js

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ import { HttpsProxyAgent } from 'https-proxy-agent';
99

1010
import { getCustom , getTokenHeaders } from '../tools.js';
1111

12-
/** Default path for the licenses endpoint (API v5). Override via TRUSTIFY_DA_LICENSES_API_PATH or opts. */
13-
const DEFAULT_LICENSES_PATH = '/api/v5/licenses';
14-
1512
/**
1613
* Fetch license details by SPDX identifier from the backend GET /api/v5/licenses/{spdx}.
1714
* Returns detailed information about a specific license including category, name, and text.
@@ -91,52 +88,7 @@ export function normalizeLicensesResponse(data, purls = []) {
9188
* @param {string[]} [purls] - optional list of purls to restrict to
9289
* @returns {Map<string, { licenses: string[], category?: string }>}
9390
*/
94-
export function licenseMapFromAnalysisReport(analysisReport, purls = []) {
91+
export function licensesFromReport(analysisReport, purls = []) {
9592
if (!analysisReport?.licenses) {return new Map();}
9693
return normalizeLicensesResponse(analysisReport.licenses, purls);
9794
}
98-
99-
/**
100-
* Fetch licenses for the given purls from the backend POST /api/v5/licenses.
101-
* Request body: { purls: string[] }. Response: LicensesResponse (array of LicenseProviderResult).
102-
*
103-
* NOTE: This is typically not needed since dependency licenses are included in the analysis response.
104-
* Use licenseMapFromAnalysisReport() instead when you have an analysis result.
105-
*
106-
* @param {string[]} purls - array of purl strings (e.g. from SBOM components)
107-
* @param {string} backendUrl - base URL of the Trustify DA backend (no trailing slash)
108-
* @param {import('../index.js').Options} [opts={}] - options (proxy, token, etc.)
109-
* @returns {Promise<Map<string, { licenses: string[], category?: string }>>}
110-
*/
111-
export async function getLicensesByPurl(purls, backendUrl, opts = {}) {
112-
if (!purls || purls.length === 0) {
113-
return new Map();
114-
}
115-
116-
const pathSegment = getCustom('TRUSTIFY_DA_LICENSES_API_PATH', DEFAULT_LICENSES_PATH, opts);
117-
const url = `${backendUrl.replace(/\/$/, '')}${pathSegment}`;
118-
119-
const fetchOptions = {
120-
method: 'POST',
121-
headers: {
122-
'Content-Type': 'application/json',
123-
'Accept': 'application/json',
124-
...getTokenHeaders(opts)
125-
},
126-
body: JSON.stringify({ purls }),
127-
};
128-
129-
const proxyUrl = getCustom('TRUSTIFY_DA_PROXY_URL', null, opts);
130-
if (proxyUrl) {
131-
fetchOptions.agent = new HttpsProxyAgent(proxyUrl);
132-
}
133-
134-
const resp = await fetch(url, fetchOptions);
135-
if (!resp.ok) {
136-
const text = await resp.text();
137-
throw new Error(`Licenses API failed: ${resp.status} ${text}`);
138-
}
139-
140-
const data = await resp.json();
141-
return normalizeLicensesResponse(data, purls);
142-
}

0 commit comments

Comments
 (0)