Skip to content

Commit 87054ff

Browse files
authored
feat!: set golang mvs true as default (#255)
* feat!: set golang mvs true as default Signed-off-by: Ruben Romero Montes <[email protected]> * feat: update tests Signed-off-by: Ruben Romero Montes <[email protected]> * feat: update provider to trustify Signed-off-by: Ruben Romero Montes <[email protected]> * fix: ci build Signed-off-by: Ruben Romero Montes <[email protected]> * fix: update new perl-base sbom Signed-off-by: Ruben Romero Montes <[email protected]> * fix: align providers Signed-off-by: Ruben Romero Montes <[email protected]> --------- Signed-off-by: Ruben Romero Montes <[email protected]>
1 parent 8e44e06 commit 87054ff

File tree

17 files changed

+10307
-33058
lines changed

17 files changed

+10307
-33058
lines changed

.mocharc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"check-leaks": false,
1111
"color": true,
1212
"fail-zero": true,
13-
"recursive": true
13+
"recursive": true,
14+
"reporter-options": ["maxDiffSize=0"]
1415
}

README.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -444,26 +444,25 @@ Two possible values for this setting:
444444
2. MATCH_MANIFEST_VERSIONS="true" - means that before starting the analysis,
445445
the api will compare all the versions of packages in manifest against installed/resolved versions on client' environment, in case there is a difference, it will throw an error to the client/user with message containing the first encountered versions mismatch, including package name, and the versions difference, and will suggest to set setting `MATCH_MANIFEST_VERSIONS`="false" to ignore all differences
446446

447-
448447
#### Golang Support
449448

450-
By default, all go.mod' packages' transitive modules will be taken to analysis with their original package version, that is,
451-
if go.mod has 2 modules, `a` and `b`, and each one of them has the same package c with same major version v1, but different minor versions:
452-
- namespace/c/[email protected]
453-
- namespace/c/[email protected]
454-
449+
By default, Golang dependency resolution follows the [Minimal Version Selection (MVS) Algorithm](https://go.dev/ref/mod#minimal-version-selection).
450+
This means that when analyzing a project, only the module versions that would actually be included in the final executable are considered.
455451

456-
Then both of these packages will be entered to the generated sbom and will be included in analysis returned to client.
457-
In golang, in an actual build of an application into an actual application executable binary, only one of the minor versions will be included in the executable, as only packages with same name but different major versions considered different packages ,
458-
hence can co-exist together in the application executable.
452+
For example, if your `go.mod` file declares two modules, `a` and `b`, and both depend on the same package `c` (same major version `v1`) but with different minor versions:
459453

460-
Go ecosystem knows how to select one minor version among all the minor versions of the same major version of a given package, using the [MVS Algorithm](https://go.dev/ref/mod#minimal-version-selection).
461-
462-
In order to enable this behavior, that only shows in analysis modules versions that are actually built into the application executable, please set
463-
system property/environment variable - `EXHORT_GO_MVS_LOGIC_ENABLED=true`(Default is false)
454+
- `namespace/c/[email protected]`
455+
- `namespace/c/[email protected]`
464456

457+
Only one of these versions — the minimal version selected by MVS — will be included in the generated SBOM and analysis results.
458+
This mirrors the behavior of a real Go build, where only one minor version of a given major version can be present in the executable (since Go treats packages with the same name and major version as identical).
465459

460+
The MVS-based resolution is **enabled by default**.
461+
If you want to disable this behavior and instead include **all transitive module versions** (as listed in `go.mod` dependencies), set the system property or environment variable:
466462

463+
```bash
464+
EXHORT_GO_MVS_LOGIC_ENABLED=false
465+
```
467466

468467
#### Python Support
469468

docker-image/Dockerfiles/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ ENV EXHORT_PIP_FREEZE=''
5656
# contains pip show data for all packages, base64 encoded
5757
ENV EXHORT_PIP_SHOW=''
5858
# indicate whether to use the Minimal version selection (MVS) algorithm to select a set of module versions to use when building Go packages.
59-
ENV EXHORT_GO_MVS_LOGIC_ENABLED='false'
59+
ENV EXHORT_GO_MVS_LOGIC_ENABLED='true'
6060

6161
# Copy java executable from the builder stage
6262
COPY --from=builder /usr/jdk-21.0.1/ /usr/jdk-21.0.1/

src/providers/golang_gomodules.js

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default { isSupported, validateLockFile, provideComponent, provideStack }
2424
*/
2525
const ecosystem = 'golang'
2626
const defaultMainModuleVersion = "v0.0.0";
27+
2728
/**
2829
* @param {string} manifestName - the subject manifest name-type
2930
* @returns {boolean} - return true if `pom.xml` is the manifest name-type
@@ -281,7 +282,7 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
281282

282283
const mainModule = toPurl(root, "@")
283284
sbom.addRoot(mainModule)
284-
const exhortGoMvsLogicEnabled = getCustom("EXHORT_GO_MVS_LOGIC_ENABLED", "false", opts)
285+
const exhortGoMvsLogicEnabled = getCustom("EXHORT_GO_MVS_LOGIC_ENABLED", "true", opts)
285286
if(includeTransitive && exhortGoMvsLogicEnabled === "true") {
286287
rows = getFinalPackagesVersionsForModule(rows, manifest, goBin)
287288
}
@@ -377,15 +378,25 @@ function getFinalPackagesVersionsForModule(rows,manifestPath,goBin) {
377378
let childName = getPackageName(child)
378379
let parentFinalVersion = finalVersionModules[parentName]
379380
let childFinalVersion = finalVersionModules[childName]
380-
// if this condition will be uncommented, there will be several differences between sbom and go list -m all listing...
381-
// let parentOriginalVersion = getVersionOfPackage(parent)
382-
// if( parentOriginalVersion !== undefined && parentOriginalVersion === parentFinalVersion) {
383-
if (parentName !== parent) {
384-
finalVersionModulesArray.push(`${parentName}@${parentFinalVersion} ${childName}@${childFinalVersion}`)
381+
382+
// Handle special cases for go and toolchain modules that aren't in go list -m all
383+
if (isSpecialGoModule(parentName) || isSpecialGoModule(childName)) {
384+
// For go and toolchain modules, use the original versions from the graph
385+
let parentVersion = getVersionOfPackage(parent)
386+
let childVersion = getVersionOfPackage(child)
387+
if (parentName !== parent) {
388+
finalVersionModulesArray.push(`${parentName}@${parentVersion} ${childName}@${childVersion}`)
389+
} else {
390+
finalVersionModulesArray.push(`${parentName} ${childName}@${childVersion}`)
391+
}
385392
} else {
386-
finalVersionModulesArray.push(`${parentName} ${childName}@${childFinalVersion}`)
393+
// For regular modules, use MVS logic
394+
if (parentName !== parent) {
395+
finalVersionModulesArray.push(`${parentName}@${parentFinalVersion} ${childName}@${childFinalVersion}`)
396+
} else {
397+
finalVersionModulesArray.push(`${parentName} ${childName}@${childFinalVersion}`)
398+
}
387399
}
388-
// }
389400
})
390401

391402
return finalVersionModulesArray
@@ -401,6 +412,27 @@ function getPackageName(fullPackage) {
401412
return fullPackage.split("@")[0]
402413
}
403414

415+
/**
416+
* Check if a module name is a special Go module (go or toolchain)
417+
* @param {string} moduleName - the module name to check
418+
* @return {boolean} true if it's a special Go module
419+
* @private
420+
*/
421+
function isSpecialGoModule(moduleName) {
422+
return moduleName === 'go' || moduleName === 'toolchain';
423+
}
424+
425+
/**
426+
*
427+
* @param {string} fullPackage - full package with its name and version
428+
* @return -{string} package version only
429+
* @private
430+
*/
431+
function getVersionOfPackage(fullPackage) {
432+
let parts = fullPackage.split("@")
433+
return parts.length > 1 ? parts[1] : undefined
434+
}
435+
404436
function getLineSeparatorGolang() {
405437
let reg = /\n|\r\n/
406438
return reg

test/it/end-to-end.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ function getParsedKeyFromHtml(html, key,keyLength) {
2222
}
2323

2424
function extractTotalsGeneralOrFromProvider(providedDataForStack, provider) {
25-
if(providedDataForStack.providers[provider].sources.length > 0) {
26-
return providedDataForStack.providers[provider].sources[provider].summary.total;
25+
if(providedDataForStack.providers[provider].sources && Object.keys(providedDataForStack.providers[provider].sources).length > 0) {
26+
// Get the first source (e.g., "osv") and return its summary total
27+
const firstSource = Object.keys(providedDataForStack.providers[provider].sources)[0];
28+
return providedDataForStack.providers[provider].sources[firstSource].summary.total;
2729
} else {
2830
return providedDataForStack.scanned.total;
2931
}
@@ -53,7 +55,7 @@ suite('Integration Tests', () => {
5355
let pomPath = `test/it/test_manifests/${packageManager}/${manifestName}`
5456
let providedDataForStack = await index.stackAnalysis(pomPath)
5557
console.log(JSON.stringify(providedDataForStack,null , 4))
56-
let providers = ["tpa"]
58+
let providers = ["rhtpa"]
5759
providers.forEach(provider => expect(extractTotalsGeneralOrFromProvider(providedDataForStack, provider)).greaterThan(0))
5860
// TODO: if sources doesn't exist, add "scanned" instead
5961
// python transitive count for stack analysis is awaiting fix in exhort backend
@@ -74,7 +76,7 @@ suite('Integration Tests', () => {
7476
}
7577
let reportParsedFromHtml
7678
let parsedSummaryFromHtml
77-
let parsedStatusFromHtmlOsvNvd
79+
let parsedStatusFromProvider
7880
let parsedScannedFromHtml
7981
try {
8082
reportParsedFromHtml = JSON.parse(html.substring(html.indexOf("\"report\" :") + 10, html.search(/([}](\s*)){5}/) + html.substring(html.search(/([}](\s*)){5}/)).indexOf(",")))
@@ -85,8 +87,8 @@ suite('Integration Tests', () => {
8587
reportParsedFromHtml = JSON.parse("{" + startOfJson.substring(0,startOfJson.indexOf("};") + 1))
8688
reportParsedFromHtml = reportParsedFromHtml.report
8789
} finally {
88-
parsedStatusFromHtmlOsvNvd = reportParsedFromHtml.providers["tpa"].status
89-
expect(parsedStatusFromHtmlOsvNvd.code).equals(200)
90+
parsedStatusFromProvider = reportParsedFromHtml.providers["rhtpa"].status
91+
expect(parsedStatusFromProvider.code).equals(200)
9092
parsedScannedFromHtml = reportParsedFromHtml.scanned
9193
expect( typeof html).equals("string")
9294
expect(html).include("html").include("svg")
@@ -102,7 +104,7 @@ suite('Integration Tests', () => {
102104

103105
expect(analysisReport.scanned.total).greaterThan(0)
104106
expect(analysisReport.scanned.transitive).equal(0)
105-
let providers = ["tpa"]
107+
let providers = ["rhtpa"]
106108
providers.forEach(provider => expect(extractTotalsGeneralOrFromProvider(analysisReport, provider)).greaterThan(0))
107109
providers.forEach(provider => expect(analysisReport.providers[provider].status.code).equals(200))
108110
}).timeout(20000);

0 commit comments

Comments
 (0)