From e96b893179badd71451eab2ef0e069409e16ac8e Mon Sep 17 00:00:00 2001 From: Vladimir Belousov Date: Wed, 7 Jan 2026 14:13:35 +0200 Subject: [PATCH] feat: make SBOM metadata labels keys configurable Signed-off-by: Vladimir Belousov --- pom.xml | 4 ++ .../morpheus/config/AppConfig.java | 34 ++++++++++++ .../morpheus/service/ReportService.java | 51 +++++++----------- .../ReportServiceMetadataKeysTest.java | 52 +++++++++++++++++++ 4 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/redhat/ecosystemappeng/morpheus/config/AppConfig.java create mode 100644 src/test/java/com/redhat/ecosystemappeng/morpheus/service/ReportServiceMetadataKeysTest.java diff --git a/pom.xml b/pom.xml index c5bcc833..648ec979 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,10 @@ io.quarkus quarkus-vertx + + io.quarkus + quarkus-hibernate-validator + io.quarkus quarkus-junit5 diff --git a/src/main/java/com/redhat/ecosystemappeng/morpheus/config/AppConfig.java b/src/main/java/com/redhat/ecosystemappeng/morpheus/config/AppConfig.java new file mode 100644 index 00000000..9f87dbfb --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/morpheus/config/AppConfig.java @@ -0,0 +1,34 @@ +package com.redhat.ecosystemappeng.morpheus.config; + +import java.util.List; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +import io.quarkus.runtime.annotations.StaticInitSafe; + +@StaticInitSafe +@ConfigMapping(prefix = "exploit-iq") +public interface AppConfig { + Image image(); + + interface Image { + Source source(); + + interface Source { + @WithName("location-keys") + @WithDefault("image.source-location,org.opencontainers.image.source,syft:image:labels:io.openshift.build.source-location") + @Size(min = 1, max = 10) + List<@NotBlank @Pattern(regexp = "^[a-zA-Z0-9.:_/-]+$", message = "Invalid key format") String> locationKeys(); + + @WithName("commit-id-keys") + @WithDefault("image.source.commit-id,org.opencontainers.image.revision,syft:image:labels:io.openshift.build.commit.id") + @Size(min = 1, max = 10) + List<@NotBlank @Pattern(regexp = "^[a-zA-Z0-9.:_/-]+$", message = "Invalid key format") String> commitIdKeys(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/ecosystemappeng/morpheus/service/ReportService.java b/src/main/java/com/redhat/ecosystemappeng/morpheus/service/ReportService.java index dac1b363..fd62d94a 100644 --- a/src/main/java/com/redhat/ecosystemappeng/morpheus/service/ReportService.java +++ b/src/main/java/com/redhat/ecosystemappeng/morpheus/service/ReportService.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.redhat.ecosystemappeng.morpheus.client.GitHubService; +import com.redhat.ecosystemappeng.morpheus.config.AppConfig; import com.redhat.ecosystemappeng.morpheus.model.PaginatedResult; import com.redhat.ecosystemappeng.morpheus.model.Pagination; import com.redhat.ecosystemappeng.morpheus.model.Report; @@ -60,11 +61,6 @@ public class ReportService { private static final Logger LOGGER = Logger.getLogger(ReportService.class); - - private static final String COMMIT_ID_PROPERTY = "syft:image:labels:io.openshift.build.commit.id"; - private static final String COMMIT_ID_PROPERTY_GENERAL = "image.source.commit-id"; - private static final String SOURCE_LOCATION_PROPERTY = "syft:image:labels:io.openshift.build.source-location"; - private static final String SOURCE_LOCATION_PROPERTY_GENERAL = "image.source-location"; private static final String PACKAGE_TYPE_PROPERTY = "syft:package:type"; private static final Pattern PURL_PKG_TYPE = Pattern.compile("pkg\\:(\\w+)\\/.*"); public static final String HOSTED_GITHUB_COM = "github.com"; @@ -73,6 +69,9 @@ public class ReportService { @RestClient GitHubService gitHubService; + @Inject + AppConfig appConfig; + @Inject ReportRepositoryService repository; @@ -364,35 +363,25 @@ private Set buildLanguagesExtensions(String ecosystem) { } } - private static String getSourceLocationFromMetadataLabels(HashMap properties) { - String sourceLocationValue = properties.get(SOURCE_LOCATION_PROPERTY_GENERAL); - - if(Objects.isNull(sourceLocationValue)) - { - sourceLocationValue = properties.get(SOURCE_LOCATION_PROPERTY); - } - - if(Objects.isNull(sourceLocationValue)) - { - throw new IllegalArgumentException("SBOM is missing required field: " + SOURCE_LOCATION_PROPERTY_GENERAL + " or " + SOURCE_LOCATION_PROPERTY); - } - - return sourceLocationValue; + private String getSourceLocationFromMetadataLabels(Map properties) { + return appConfig.image().source().locationKeys().stream() + .map(String::trim) + .map(properties::get) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + "SBOM is missing required field. Checked keys: " + appConfig.image().source().locationKeys())); } - private static String getCommitIdFromMetadataLabels(HashMap properties) { - String commitIdIdValue = properties.get(COMMIT_ID_PROPERTY_GENERAL); - - if(Objects.isNull(commitIdIdValue)) { - commitIdIdValue = properties.get(COMMIT_ID_PROPERTY); - } - - if(Objects.isNull(commitIdIdValue)) - { - throw new IllegalArgumentException("SBOM is missing required field: " + COMMIT_ID_PROPERTY_GENERAL + " or " + COMMIT_ID_PROPERTY); - } - return commitIdIdValue; + private String getCommitIdFromMetadataLabels(HashMap properties) { + return appConfig.image().source().commitIdKeys().stream() + .map(String::trim) + .map(properties::get) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + "SBOM is missing required field. Checked keys: " + appConfig.image().source().commitIdKeys())); } private JsonNode buildSbomInfo(ReportRequest request) { diff --git a/src/test/java/com/redhat/ecosystemappeng/morpheus/service/ReportServiceMetadataKeysTest.java b/src/test/java/com/redhat/ecosystemappeng/morpheus/service/ReportServiceMetadataKeysTest.java new file mode 100644 index 00000000..1c549bb3 --- /dev/null +++ b/src/test/java/com/redhat/ecosystemappeng/morpheus/service/ReportServiceMetadataKeysTest.java @@ -0,0 +1,52 @@ +package com.redhat.ecosystemappeng.morpheus.service; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ReportServiceMetadataKeysTest { + + private static final String EXPECTED_URL = "https://github.com/openshift/origin"; + private static final List LOCATION_KEYS = List.of( + "image.source-location", + "org.opencontainers.image.source", + "syft:image:labels:io.openshift.build.source-location"); + + static Stream validKeyProvider() { + return Stream.of( + Arguments.of("image.source-location", "first key"), + Arguments.of("org.opencontainers.image.source", "second key (fallback)"), + Arguments.of("syft:image:labels:io.openshift.build.source-location", "third key (fallback)")); + } + + @ParameterizedTest(name = "should find value using {1}") + @MethodSource("validKeyProvider") + void shouldFindValueByKey(String key, String description) { + Map props = Map.of(key, EXPECTED_URL); + String result = findValue(props, LOCATION_KEYS); + assertEquals(EXPECTED_URL, result); + } + + @Test + void shouldThrowWhenNoKeyFound() { + Map props = Map.of("wrong-key", "value"); + var ex = assertThrows(IllegalArgumentException.class, + () -> findValue(props, LOCATION_KEYS)); + assertTrue(ex.getMessage().contains("image.source-location")); + } + + private String findValue(Map properties, List keys) { + return keys.stream() + .map(properties::get) + .filter(v -> v != null) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Missing key. Checked: " + keys)); + } +} \ No newline at end of file