From 5d46fdbfd209e7769022e06e312f944a2f4baeb7 Mon Sep 17 00:00:00 2001 From: Florian Schreiber Date: Mon, 3 Aug 2026 17:38:16 +0200 Subject: [PATCH 1/4] feat(UP-4700): Improve DependencyChecker logging --- plugins/dependency-checker/pom.xml | 6 + .../sauron/plugins/DependencyChecker.java | 32 +- .../elasticsearch/ElasticSearchClient.java | 10 +- .../sauron/plugins/DependencyCheckerTest.java | 365 +++++++++--------- .../src/test/resources/pom.xml | 6 +- 5 files changed, 223 insertions(+), 196 deletions(-) diff --git a/plugins/dependency-checker/pom.xml b/plugins/dependency-checker/pom.xml index 8b541f88..c983c593 100644 --- a/plugins/dependency-checker/pom.xml +++ b/plugins/dependency-checker/pom.xml @@ -95,6 +95,12 @@ 4.13.1 test + + org.mockito + mockito-core + 5.23.0 + test + org.apache.maven.shared diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java index 642129a1..91a75463 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java @@ -9,6 +9,7 @@ import com.freenow.sauron.model.DataSet; import com.freenow.sauron.plugins.elasticsearch.DependenciesModel; import com.freenow.sauron.plugins.elasticsearch.ElasticSearchClient; +import com.freenow.sauron.plugins.generator.DependencyGenerator; import com.freenow.sauron.plugins.generator.DependencyGeneratorFactory; import com.freenow.sauron.properties.PluginsConfigurationProperties; import lombok.extern.slf4j.Slf4j; @@ -38,21 +39,22 @@ public DataSet apply(PluginsConfigurationProperties properties, DataSet input) log.info("Detected project type: {} for repository: {}", projectType, repository); input.setAdditionalInformation("projectType", projectType.toString()); - DependencyGeneratorFactory.newInstance(projectType, properties) - .map(dependencyGenerator -> - { - Path bom = dependencyGenerator.generateCycloneDxBom(repository); - log.info("Generated BOM path: {}", bom); - return bom; - }) - .filter(Files::exists) - .ifPresent(bom -> - { - log.info("BOM exists, setting cycloneDxBomPath: {}", bom); - input.setAdditionalInformation("cycloneDxBomPath", bom.toString()); - DependenciesModel dependenciesModel = DependenciesModel.from(input, parseCycloneDx(bom)); - new ElasticSearchClient(properties).index(dependenciesModel); - }); + Optional dependencyGenerator = DependencyGeneratorFactory.newInstance(projectType, properties); + Optional bomFile = dependencyGenerator.map(generator -> generator.generateCycloneDxBom(repository)); + Path bom = bomFile.filter(Files::exists).orElse(null); + + if (bom != null) { + log.info("Generated BOM using generator {}, setting cycloneDxBomPath: {}", dependencyGenerator.get().getClass().getSimpleName(), bom); + input.setAdditionalInformation("cycloneDxBomPath", bom.toString()); + DependenciesModel dependenciesModel = DependenciesModel.from(input, parseCycloneDx(bom)); + new ElasticSearchClient(properties).index(dependenciesModel); + } else if (bomFile.isPresent()) { + log.warn("DependencyGenerator {} returned BOM path, but didn't actually write the file: {}", dependencyGenerator.get().getClass().getSimpleName(), bomFile); + } else if (dependencyGenerator.isPresent()) { + log.warn("DependencyGenerator {} did not return a BOM path", dependencyGenerator.get().getClass().getSimpleName()); + } else { + log.warn("Could not find DependencyGenerator for repository {} with detected type {}", repository, projectType); + } }); return input; diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/elasticsearch/ElasticSearchClient.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/elasticsearch/ElasticSearchClient.java index 78ea67c4..41592042 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/elasticsearch/ElasticSearchClient.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/elasticsearch/ElasticSearchClient.java @@ -50,13 +50,17 @@ public void index(DependenciesModel dependenciesModel) RestStatus status = response.status(); if (!status.equals(RestStatus.OK) && !status.equals(RestStatus.CREATED)) { - log.error(String.format("Error [%s] storing document: %s", status, dependenciesModel.toJson())); + log.error("Error [status: {}] storing document: {}", status, dependenciesModel.toJson()); + } else { + log.info("Updated dependencies indexed successfully: {}", dependenciesModel.toJson()); } } catch (IOException e) { - log.error(e.getMessage(), e); + log.error("Failure while updating dependencies index: {}", e.getMessage(), e); } + } else { + log.warn("ElasticSearchClient wasn't configured. Dependencies index isn't getting updated!"); } } @@ -68,4 +72,4 @@ private IndexRequest getDocIndexRequest(DependenciesModel model) throws JsonProc request.source(model.toJson(), XContentType.JSON); return request; } -} \ No newline at end of file +} diff --git a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java index affad709..8219d4d5 100644 --- a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java +++ b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java @@ -1,16 +1,20 @@ package com.freenow.sauron.plugins; import com.freenow.sauron.model.DataSet; +import com.freenow.sauron.plugins.elasticsearch.DependenciesModel; +import com.freenow.sauron.plugins.elasticsearch.ElasticSearchClient; import com.freenow.sauron.properties.PluginsConfigurationProperties; import org.apache.commons.io.FileUtils; -import org.cyclonedx.exception.ParseException; -import org.cyclonedx.model.Bom; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.cyclonedx.model.Component; -import org.cyclonedx.parsers.XmlParser; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedConstruction; import java.io.File; import java.io.IOException; @@ -42,6 +46,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.verify; public class DependencyCheckerTest { @@ -87,6 +93,20 @@ public void testDependencyCheckerMavenProject() throws IOException, URISyntaxExc DataSet dataSet = createDataSet("pom.xml", "pom.xml"); dataSet = plugin.apply(pluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", MAVEN.toString()); + + assertDependencies( + Map.of( + "org.apache.tomcat.embed:tomcat-embed-websocket", "11.0.22", + "org.apache.tomcat.embed:tomcat-embed-el", "11.0.22", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7", "2.3.21", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "1.3.50", + "javax.annotation:javax_annotation-api", "1.3.2", + "org.apache.tomcat.embed:tomcat-embed-core", "11.0.22", + "org.jetbrains.kotlin:kotlin-stdlib", "2.3.21", + "org.jetbrains:annotations", "13.0", + "org.springframework.boot:spring-boot-starter-tomcat", "2.1.2.RELEASE" + ) + ); } @@ -97,16 +117,14 @@ public void testDependencyCheckerGradleGroovyProjectWithPlugins() throws IOExcep dataSet = plugin.apply(pluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", GRADLE_GROOVY.toString()); - Path bomXmlPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomXmlPath, Files.exists(bomXmlPath)); - Bom bom = parseBomXmlFromFile(bomXmlPath); - assertTrue( - "kotlin-stdlib-jdk8@1.3.61 should be present in bom.xml", - hasDependency(bom, "org.jetbrains.kotlin", "kotlin-stdlib-jdk8", "1.3.61") - ); - assertTrue( - "BOM should contain all direct dependencies from build.gradle and their transitive dependencies identified by Gradle", - bom.getComponents().stream().anyMatch(c -> c.getType() == Component.Type.LIBRARY) + assertDependencies( + Map.of( + "org.jetbrains.kotlin:kotlin-stdlib", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-common", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "1.3.61", + "org.jetbrains:annotations", "13.0" + ) ); } @@ -118,16 +136,14 @@ public void testDependencyCheckerGradleGroovyProjectWithoutPlugins() throws IOEx dataSet = plugin.apply(pluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", GRADLE_GROOVY.toString()); - Path bomXmlPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomXmlPath, Files.exists(bomXmlPath)); - Bom bom = parseBomXmlFromFile(bomXmlPath); - assertTrue( - "kotlin-stdlib-jdk8@1.3.61 should be present in bom.xml", - hasDependency(bom, "org.jetbrains.kotlin", "kotlin-stdlib-jdk8", "1.3.61") - ); - assertTrue( - "BOM should contain all direct dependencies from build-noplugin.gradle and their transitive dependencies identified by Gradle", - bom.getComponents().stream().anyMatch(c -> c.getType() == Component.Type.LIBRARY) + assertDependencies( + Map.of( + "org.jetbrains.kotlin:kotlin-stdlib", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-common", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "1.3.61", + "org.jetbrains:annotations", "13.0" + ) ); } @@ -139,22 +155,20 @@ public void testDependencyCheckerGradleKotlinDsl() throws IOException, URISyntax dataSet = plugin.apply(pluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", GRADLE_KOTLIN_DSL.toString()); - Path bomXmlPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomXmlPath, Files.exists(bomXmlPath)); - Bom bom = parseBomXmlFromFile(bomXmlPath); - assertTrue( - "kotlin-stdlib-jdk8@1.3.61 should be present in bom.xml", - hasDependency(bomXmlPath, "org.jetbrains.kotlin", "kotlin-stdlib-jdk8", "1.3.61") - ); - assertTrue( - "BOM should contain all direct dependencies from build.gradle.kts and their transitive dependencies identified by Gradle", - bom.getComponents().stream().anyMatch(c -> c.getType() == Component.Type.LIBRARY) + assertDependencies( + Map.of( + "org.jetbrains.kotlin:kotlin-stdlib", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-common", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7", "1.3.61", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "1.3.61", + "org.jetbrains:annotations", "13.0" + ) ); } @Test - public void testDependencyCheckerNodeJsNpm() throws IOException, URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException + public void testDependencyCheckerNodeJsNpm() throws IOException, URISyntaxException { DataSet dataSet = createDataSet(Map.of( "package.json", "package.json", @@ -164,15 +178,16 @@ public void testDependencyCheckerNodeJsNpm() throws IOException, URISyntaxExcept dataSet = plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", NODEJS_NPM.toString()); - Path bomJsonPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomJsonPath, Files.exists(bomJsonPath)); - assertTrue("react@18.0.0 should be present in bom.json", hasJsonDependency(bomJsonPath, "react", "18.0.0")); - assertEquals("BOM should contain 1 library component (react)", 1, invokeParseCycloneDxJson(plugin, bomJsonPath).size()); + assertDependencies( + Map.of( + "org.npmjs:react", "18.0.0" + ) + ); } @Test - public void testDependencyCheckerNodeJsYarn() throws IOException, URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException + public void testDependencyCheckerNodeJsYarn() throws IOException, URISyntaxException { DataSet dataSet = createDataSet(Map.of( "package.json", "package.json", @@ -181,17 +196,13 @@ public void testDependencyCheckerNodeJsYarn() throws IOException, URISyntaxExcep dataSet = plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", NODEJS_YARN.toString()); - Path bomJsonPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomJsonPath, Files.exists(bomJsonPath)); - Map dependencies = Map.of( - "react", "18.0.0", - "loose-envify", "1.4.0", - "js-tokens", "4.0.0" + assertDependencies( + Map.of( + "org.npmjs:react", "18.0.0", + "org.npmjs:loose-envify", "1.4.0", + "org.npmjs:js-tokens", "4.0.0" + ) ); - for (Map.Entry dependency : dependencies.entrySet()) { - assertTrue(dependency.getKey() + "@" + dependency.getValue() + " should be present in bom.json", hasJsonDependency(bomJsonPath, dependency.getKey(), dependency.getValue())); - } - assertEquals("BOM should contain 1 library component (react)", dependencies.size(), invokeParseCycloneDxJson(plugin, bomJsonPath).size()); } @@ -203,6 +214,7 @@ public void testDependencyCheckerNodeJsMissingPackageLockJson() throws IOExcepti )); dataSet = plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); checkKeyNotPresent(dataSet, "cycloneDxBomPath"); + assertNoDependenciesReport(); } @@ -213,32 +225,13 @@ public void testDependencyCheckerPythonRequirementsProject() throws IOException, dataSet = plugin.apply(createPythonPluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", PYTHON_REQUIREMENTS.toString()); - Path bomXmlPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomXmlPath, Files.exists(bomXmlPath)); - Bom bom = parseBomXmlFromFile(bomXmlPath); - assertTrue( - "packaging==21.3 should be present in bom.xml", - hasDependency(bom, null, "packaging", "21.3") - ); - assertTrue( - "boto3==1.17.105 should be present in bom.xml", - hasDependency(bom, null, "boto3", "1.17.105") - ); - assertTrue( - "requests should be present in bom.xml", - hasDependency(bom, null, "requests", null) - ); - assertTrue( - "eventlet should be present in bom.xml", - hasDependency(bom, null, "eventlet", null) - ); - assertTrue( - "eventlet should be present in bom.xml", - hasDependency(bom, null, "eventlet", null) - ); - assertEquals( - "Should have same number of dependencies", - 4, bom.getComponents().stream().filter(c -> c.getType() == Component.Type.LIBRARY).count() + assertDependencies( + Map.of( + "org.python:packaging", "21.3", + "org.python:boto3", "1.17.105", + "org.python:requests", "null", + "org.python:eventlet", "null" + ) ); } @@ -250,20 +243,18 @@ public void testDependencyCheckerPythonPoetryProject() throws IOException, URISy dataSet = plugin.apply(createPythonPluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", PYTHON_POETRY.toString()); - Path bomXmlPath = Paths.get((String) dataSet.getObjectAdditionalInformation("cycloneDxBomPath").orElseThrow()); - assertTrue("BOM file should exist at " + bomXmlPath, Files.exists(bomXmlPath)); - Bom bom = parseBomXmlFromFile(bomXmlPath); - assertTrue( - "packaging=21.3 should be present in bom.xml", - hasDependency(bom, null, "packaging", "21.3") - ); - assertTrue( - "boto3=1.17.105 should be present in bom.xml", - hasDependency(bom, null, "boto3", "1.17.105") - ); - assertTrue( - "BOM should contain all direct dependencies from pyproject.toml and their transitive dependencies identified by Poetry", - bom.getComponents().stream().filter(c -> c.getType() == Component.Type.LIBRARY).count() >= 4 + assertDependencies( + Map.of( + "org.python:packaging", "21.3", + "org.python:boto3", "1.17.105", + "org.python:s3transfer", "0.4.2", + "org.python:urllib3", "1.26.20", + "org.python:botocore", "1.20.112", + "org.python:jmespath", "0.10.0", + "org.python:six", "1.17.0", + "org.python:python-dateutil", "2.9.0.post0", + "org.python:pyparsing", "3.1.4" + ) ); } @@ -274,6 +265,7 @@ public void testDependencyCheckerSbtProject() throws IOException, URISyntaxExcep DataSet dataSet = createDataSet("build.sbt", "build.sbt"); dataSet = plugin.apply(pluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", SBT.toString()); + assertNoDependenciesReport(); } @@ -283,6 +275,7 @@ public void testDependencyCheckerClojureProject() throws IOException, URISyntaxE DataSet dataSet = createDataSet("project.clj", "project.clj"); dataSet = plugin.apply(pluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", CLOJURE.toString()); + assertNoDependenciesReport(); } @@ -294,17 +287,12 @@ public void testDependencyCheckerGoProject() throws IOException, URISyntaxExcept ); dataSet = plugin.apply(createGoPluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", GO.toString()); - Path bomXmlPath = tempFolder.getRoot().toPath().resolve("go-sbom/bom.xml"); - checkKeyPresent(dataSet, "cycloneDxBomPath", bomXmlPath.toString()); - Bom bom = parseBomXmlFromFile(bomXmlPath); - assertTrue( - "yaml.v2@v2.4.0 should be present in bom.xml", - hasDependency(bom, null, "gopkg.in/yaml.v2", "v2.4.0") - ); - assertEquals( - "Should have same number of dependencies", - 1, bom.getComponents().stream().filter(c -> c.getType() == Component.Type.LIBRARY).count() + assertDependencies( + Map.of( + "org.golang:/wrk/go_mod", "null", + "org.golang:gopkg_in/yaml_v2", "v2.4.0" + ) ); } @@ -317,52 +305,40 @@ public void testDependencyCheckerGoSubFolderProject() throws IOException, URISyn ); dataSet = plugin.apply(createGoPluginConfigurationProperties(), dataSet); checkKeyPresent(dataSet, "projectType", GO.toString()); - Path bomXmlPath = tempFolder.getRoot().toPath().resolve("go-sbom-sub/dummys/bom.xml"); - checkKeyPresent(dataSet, "cycloneDxBomPath", bomXmlPath.toString()); - - Bom bom = parseBomXmlFromFile(bomXmlPath); - Map expectedDeps = Map.ofEntries( - Map.entry("github.com/MicahParks/keyfunc", "v1.9.0"), - Map.entry("github.com/golang-jwt/jwt/v4", "v4.4.2"), - Map.entry("github.com/lestrrat-go/jwx/v2", "v2.1.4"), - Map.entry("github.com/prometheus/client_golang", "v1.21.1"), - Map.entry("github.com/stretchr/testify", "v1.10.0"), - Map.entry("gitlab.free-now.com/free-now/sre-backend/fnlog", "v0.6.0"), - Map.entry("github.com/beorn7/perks", "v1.0.1"), - Map.entry("github.com/cespare/xxhash/v2", "v2.3.0"), - Map.entry("github.com/davecgh/go-spew", "v1.1.1"), - Map.entry("github.com/decred/dcrd/dcrec/secp256k1/v4", "v4.4.0"), - Map.entry("github.com/goccy/go-json", "v0.10.3"), - Map.entry("github.com/klauspost/compress", "v1.17.11"), - Map.entry("github.com/kr/text", "v0.2.0"), - Map.entry("github.com/lestrrat-go/blackmagic", "v1.0.2"), - Map.entry("github.com/lestrrat-go/httpcc", "v1.0.1"), - Map.entry("github.com/lestrrat-go/httprc", "v1.0.6"), - Map.entry("github.com/lestrrat-go/iter", "v1.0.2"), - Map.entry("github.com/lestrrat-go/option", "v1.0.1"), - Map.entry("github.com/munnerz/goautoneg", "v0.0.0-20191010083416-a7dc8b61c822"), - Map.entry("github.com/pmezard/go-difflib", "v1.0.0"), - Map.entry("github.com/prometheus/client_model", "v0.6.1"), - Map.entry("github.com/prometheus/common", "v0.62.0"), - Map.entry("github.com/prometheus/procfs", "v0.15.1"), - Map.entry("github.com/segmentio/asm", "v1.2.0"), - Map.entry("golang.org/x/crypto", "v0.32.0"), - Map.entry("golang.org/x/sys", "v0.29.0"), - Map.entry("google.golang.org/protobuf", "v1.36.1"), - Map.entry("gopkg.in/yaml.v3", "v3.0.1") - ); - expectedDeps.forEach((name, version) -> - assertTrue( - String.format("%s@%s should be present in bom.xml", name, version), - hasDependency(bom, null, name, version) + assertDependencies( + Map.ofEntries( + Map.entry("org.golang:/wrk/go_mod", "null"), + Map.entry("org.golang:github_com/MicahParks/keyfunc", "v1.9.0"), + Map.entry("org.golang:github_com/golang-jwt/jwt/v4", "v4.4.2"), + Map.entry("org.golang:github_com/lestrrat-go/jwx/v2", "v2.1.4"), + Map.entry("org.golang:github_com/prometheus/client_golang", "v1.21.1"), + Map.entry("org.golang:github_com/stretchr/testify", "v1.10.0"), + Map.entry("org.golang:gitlab_free-now_com/free-now/sre-backend/fnlog", "v0.6.0"), + Map.entry("org.golang:github_com/beorn7/perks", "v1.0.1"), + Map.entry("org.golang:github_com/cespare/xxhash/v2", "v2.3.0"), + Map.entry("org.golang:github_com/davecgh/go-spew", "v1.1.1"), + Map.entry("org.golang:github_com/decred/dcrd/dcrec/secp256k1/v4", "v4.4.0"), + Map.entry("org.golang:github_com/goccy/go-json", "v0.10.3"), + Map.entry("org.golang:github_com/klauspost/compress", "v1.17.11"), + Map.entry("org.golang:github_com/kr/text", "v0.2.0"), + Map.entry("org.golang:github_com/lestrrat-go/blackmagic", "v1.0.2"), + Map.entry("org.golang:github_com/lestrrat-go/httpcc", "v1.0.1"), + Map.entry("org.golang:github_com/lestrrat-go/httprc", "v1.0.6"), + Map.entry("org.golang:github_com/lestrrat-go/iter", "v1.0.2"), + Map.entry("org.golang:github_com/lestrrat-go/option", "v1.0.1"), + Map.entry("org.golang:github_com/munnerz/goautoneg", "v0.0.0-20191010083416-a7dc8b61c822"), + Map.entry("org.golang:github_com/pmezard/go-difflib", "v1.0.0"), + Map.entry("org.golang:github_com/prometheus/client_model", "v0.6.1"), + Map.entry("org.golang:github_com/prometheus/common", "v0.62.0"), + Map.entry("org.golang:github_com/prometheus/procfs", "v0.15.1"), + Map.entry("org.golang:github_com/segmentio/asm", "v1.2.0"), + Map.entry("org.golang:golang_org/x/crypto", "v0.32.0"), + Map.entry("org.golang:golang_org/x/sys", "v0.29.0"), + Map.entry("org.golang:google_golang_org/protobuf", "v1.36.1"), + Map.entry("org.golang:gopkg_in/yaml_v3", "v3.0.1") ) ); - assertEquals( - "Should have same number of dependencies", - expectedDeps.size(), - bom.getComponents().stream().filter(c -> c.getType() == Component.Type.LIBRARY).count() - ); } @@ -599,61 +575,100 @@ private PluginsConfigurationProperties pluginConfigurationProperties() } - private boolean hasJsonDependency(Path bomJsonPath, String name, String version) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException + private boolean isDockerAvailable() { - List components = invokeParseCycloneDxJson(plugin, bomJsonPath); - return components.stream() - .anyMatch(c -> name.equals(c.getName()) && version.equals(c.getVersion())); + try + { + Process process = Runtime.getRuntime().exec("docker ps"); + return process.waitFor() == 0; + } + catch (Exception e) + { + return false; + } } - private boolean hasDependency(Path bomXmlPath, String group, String name, String version) - { - return hasDependency(parseBomXmlFromFile(bomXmlPath), group, name, version); - } + private MockedConstruction clientMockedConstruction; - private boolean hasDependency(Bom bom, String group, String name, String version) + @Before + public void mockElasticSearchClient() { - if (bom == null || bom.getComponents() == null) - { - return false; - } - return bom.getComponents().stream().anyMatch(c -> matches(c, group, name, version)); + clientMockedConstruction = mockConstruction(ElasticSearchClient.class); } - private boolean matches(Component component, String group, String name, String version) + @After + public void unmockElasticSearchClient() { - return (group == null || Objects.equals(group, component.getGroup())) && - Objects.equals(name, component.getName()) && - Objects.equals(version, component.getVersion()); + clientMockedConstruction.close(); } - private Bom parseBomXmlFromFile(Path bomXmlPath) + private void assertDependencies( + Map expectedDependencies + ) { - try + assertEquals( + "Unexpected count of reports send to ElasticSearch", + 1, + clientMockedConstruction.constructed().size() + ); + ElasticSearchClient elasticSearchClient = clientMockedConstruction.constructed().get(0); + ArgumentCaptor captor = ArgumentCaptor.forClass(DependenciesModel.class); + verify(elasticSearchClient).index(captor.capture()); + DependenciesModel dependenciesModel = captor.getValue(); + + Map dependencies = dependenciesModel.getDependencies(); + Map missingDependencies = new HashMap<>(); + Map> mismatchedDependencies = new HashMap<>(); + Map unexpectedDependencies = new HashMap<>(); + + for (Map.Entry dependency : expectedDependencies.entrySet()) { - return new XmlParser().parse(bomXmlPath.toFile()); + if (!dependencies.containsKey(dependency.getKey())) + { + missingDependencies.put(dependency.getKey(), dependency.getValue()); + } + else if (!dependency.getValue().equals(dependencies.get(dependency.getKey()))) + { + mismatchedDependencies.put(dependency.getKey(), new ImmutablePair<>(dependency.getValue(), dependencies.get(dependency.getKey()))); + } } - catch (ParseException e) + for (Map.Entry dependency : dependencies.entrySet()) { - throw new IllegalStateException("Failed to parse BOM file: " + bomXmlPath, e); + String name = dependency.getKey().replaceAll("-(normalized|license)$", ""); + if (!name.equals("licenses") && !expectedDependencies.containsKey(name)) + { + unexpectedDependencies.put(name, dependency.getValue()); + } } + + assertEquals( + "Some dependencies weren't expected to be found", + Map.of(), + unexpectedDependencies + ); + assertEquals( + "Some expected dependencies weren't found", + Map.of(), + missingDependencies + ); + assertEquals( + "Some dependencies did not have the correct versions", + Map.of(), + mismatchedDependencies + ); } - private boolean isDockerAvailable() + private void assertNoDependenciesReport() { - try - { - Process process = Runtime.getRuntime().exec("docker ps"); - return process.waitFor() == 0; - } - catch (Exception e) - { - return false; - } + assertEquals( + "ElasticSearch was called unexpectedly", + 0, + clientMockedConstruction.constructed().size() + ); } } diff --git a/plugins/dependency-checker/src/test/resources/pom.xml b/plugins/dependency-checker/src/test/resources/pom.xml index 75c62ecb..d504eafb 100644 --- a/plugins/dependency-checker/src/test/resources/pom.xml +++ b/plugins/dependency-checker/src/test/resources/pom.xml @@ -3,9 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - com.test - parent - 1.1.0 + org.springframework.boot + spring-boot-starter-parent + 4.1.0 service com.test From c597af303a66fc3879b7bc12d9a42022cd79b6ad Mon Sep 17 00:00:00 2001 From: Florian Schreiber Date: Mon, 3 Aug 2026 18:17:23 +0200 Subject: [PATCH 2/4] chore(UP-4700): Clean up GitHub Actions --- .github/workflows/build.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0686d11..bf77a0af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,16 +22,5 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11.4" - - run: python --version - - run: pipx --version - - run: pipx ensurepath - - run: pipx install poetry==1.8.2 - - run: pipx inject poetry poetry-plugin-export==1.7.1 - - run: pipx install cyclonedx-bom==4.1.5 - - name: Build with Maven and run the tests - run: mvn --batch-mode --update-snapshots verify -Dgpg.skip=true \ No newline at end of file + run: mvn --batch-mode --update-snapshots verify -Dgpg.skip=true From d9e32d123aa7b6b516eadc6d5c8db669d9ed96d1 Mon Sep 17 00:00:00 2001 From: Florian Schreiber Date: Mon, 3 Aug 2026 18:24:44 +0200 Subject: [PATCH 3/4] fix(UP-4700): Forward maven.home property during tests --- plugins/dependency-checker/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/dependency-checker/pom.xml b/plugins/dependency-checker/pom.xml index c983c593..798b36ed 100644 --- a/plugins/dependency-checker/pom.xml +++ b/plugins/dependency-checker/pom.xml @@ -335,6 +335,17 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + + ${maven.home} + + + From df9b681bccb027066a6729370822e54b017fc991 Mon Sep 17 00:00:00 2001 From: Florian Schreiber Date: Mon, 3 Aug 2026 18:25:04 +0200 Subject: [PATCH 4/4] feat(UP-4700): Use non-interactive mode for maven BOM generation --- .../sauron/plugins/generator/maven/MavenDependencyGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/maven/MavenDependencyGenerator.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/maven/MavenDependencyGenerator.java index 63bf4fac..fc2a45ec 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/maven/MavenDependencyGenerator.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/maven/MavenDependencyGenerator.java @@ -42,6 +42,7 @@ public Path generateCycloneDxBom(Path repositoryPath) InvocationRequest request = new DefaultInvocationRequest(); request.setTimeoutInSeconds(Math.toIntExact(Duration.ofMinutes(commandTimeoutMinutes).toSeconds())); request.setPomFile(pom); + request.setBatchMode(true); request.setQuiet(!log.isDebugEnabled()); request.setGoals(Collections.singletonList("cyclonedx:makeBom"));