Skip to content

Commit 2de611c

Browse files
committed
Embedded tests and endpoint extension scaffold
1 parent 00c505e commit 2de611c

File tree

9 files changed

+295
-11
lines changed

9 files changed

+295
-11
lines changed

bom/application/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,16 @@
887887
<artifactId>quarkus-cyclonedx-generator</artifactId>
888888
<version>${project.version}</version>
889889
</dependency>
890+
<dependency>
891+
<groupId>io.quarkus</groupId>
892+
<artifactId>quarkus-cyclonedx-endpoint</artifactId>
893+
<version>${project.version}</version>
894+
</dependency>
895+
<dependency>
896+
<groupId>io.quarkus</groupId>
897+
<artifactId>quarkus-cyclonedx-endpoint-deployment</artifactId>
898+
<version>${project.version}</version>
899+
</dependency>
890900
<dependency>
891901
<groupId>io.quarkus</groupId>
892902
<artifactId>quarkus-datasource-common</artifactId>

extensions/cyclonedx/deployment/src/main/java/io/quarkus/cyclonedx/deployment/CdxSbomBuildStep.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void generate(ApplicationManifestsBuildItem applicationManifestsBuildItem
3636
AppModelProviderBuildItem appModelProviderBuildItem,
3737
CycloneDxConfig cdxSbomConfig,
3838
BuildProducer<SbomBuildItem> sbomProducer) {
39-
if (cdxSbomConfig.skip() || applicationManifestsBuildItem.getManifests().isEmpty()) {
39+
if (!cdxSbomConfig.enabled() || applicationManifestsBuildItem.getManifests().isEmpty()) {
4040
// until there is a proper way to request the desired build items as build outcome
4141
return;
4242
}
@@ -60,11 +60,13 @@ public void embedDependencySbom(BuildProducer<GeneratedResourceBuildItem> genera
6060
CycloneDxConfig cdxConfig,
6161
CurateOutcomeBuildItem curateOutcomeBuildItem,
6262
AppModelProviderBuildItem appModelProviderBuildItem) {
63-
if (!cdxConfig.embeddedDependencySbom().enabled() || cdxConfig.skip()) {
63+
if (!cdxConfig.enabled() ||
64+
// if embedded
65+
!cdxConfig.embedded().enabled() && !cdxConfig.endpoint().enabled()) {
6466
return;
6567
}
6668

67-
final CycloneDxConfig.EmbeddedDependencySbomConfig dependencySbomConfig = cdxConfig.embeddedDependencySbom();
69+
final CycloneDxConfig.EmbeddedSbomConfig dependencySbomConfig = cdxConfig.embedded();
6870
final String resourceName = dependencySbomConfig.resourceName();
6971
if (resourceName == null || resourceName.isEmpty()) {
7072
throw new IllegalArgumentException("resourceName is not configured for the embedded dependency SBOM");

extensions/cyclonedx/deployment/src/main/java/io/quarkus/cyclonedx/deployment/CycloneDxConfig.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
@ConfigRoot
1515
public interface CycloneDxConfig {
1616
/**
17-
* Whether to skip SBOM generation
17+
* Whether to CycloneDX SBOM generation is enabled.
18+
* If this option is false, the rest of the configuration will be ignored.
1819
*/
19-
@WithDefault("false")
20-
boolean skip();
20+
@WithDefault("true")
21+
boolean enabled();
2122

2223
/**
2324
* SBOM file format. Supported formats are {code json} and {code xml}.
@@ -45,18 +46,25 @@ public interface CycloneDxConfig {
4546
boolean includeLicenseText();
4647

4748
/**
48-
* Embedded dependency SBOM
49+
* Embedded dependency SBOM configuration
4950
*/
5051
@ConfigDocSection
51-
EmbeddedDependencySbomConfig embeddedDependencySbom();
52+
EmbeddedSbomConfig embedded();
53+
54+
/**
55+
* REST endpoint configuration for an embedded SBOM
56+
*
57+
* @return REST endpoint configuration for an embedded SBOM
58+
*/
59+
SbomEndpointConfig endpoint();
5260

5361
/**
5462
* Embedded dependency SBOM configuration
5563
*/
56-
interface EmbeddedDependencySbomConfig {
64+
interface EmbeddedSbomConfig {
5765

5866
/**
59-
* Whether dependency SBOM should be embedded in the final application.
67+
* Whether a dependency SBOM should be embedded in the final application.
6068
*
6169
* @return true, if dependency SBOM should be embedded in the final application, false - otherwise
6270
*/
@@ -68,7 +76,32 @@ interface EmbeddedDependencySbomConfig {
6876
*
6977
* @return resource name for the embedded dependency SBOM
7078
*/
71-
@WithDefault("META-INF/sbom/dependency-cdx.json")
79+
@WithDefault("META-INF/sbom/application.cdx.json")
7280
String resourceName();
7381
}
82+
83+
/**
84+
* SBOM endpoint configuration
85+
*/
86+
interface SbomEndpointConfig {
87+
88+
/**
89+
* Whether the embedded SBOM should be exposed through a REST endpoint.
90+
* <p>
91+
* Note: enabling the endpoint option will force embedded SBOM generation,
92+
* even if {@link EmbeddedSbomConfig#enabled()} is {@code false}.
93+
*
94+
* @return true, if the embedded SBOM should be exposed through a REST endpoint, otherwise - false
95+
*/
96+
@WithDefault("false")
97+
boolean enabled();
98+
99+
/**
100+
* REST endpoint path that will provide an SBOM
101+
*
102+
* @return REST endpoint path that will provide an SBOM
103+
*/
104+
@WithDefault("/.well-known/sbom")
105+
String path();
106+
}
74107
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>quarkus-cyclonedx-endpoint-parent</artifactId>
7+
<groupId>io.quarkus</groupId>
8+
<version>999-SNAPSHOT</version>
9+
</parent>
10+
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<artifactId>quarkus-cyclonedx-endpoint-deployment</artifactId>
14+
<name>Quarkus - CycloneDX - Endpoint - Deployment</name>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.quarkus</groupId>
19+
<artifactId>quarkus-core-deployment</artifactId>
20+
</dependency>
21+
<dependency>
22+
<groupId>io.quarkus</groupId>
23+
<artifactId>quarkus-cyclonedx-endpoint</artifactId>
24+
</dependency>
25+
</dependencies>
26+
27+
<build>
28+
<plugins>
29+
<plugin>
30+
<artifactId>maven-compiler-plugin</artifactId>
31+
<executions>
32+
<execution>
33+
<id>default-compile</id>
34+
<configuration>
35+
<annotationProcessorPaths>
36+
<path>
37+
<groupId>io.quarkus</groupId>
38+
<artifactId>quarkus-extension-processor</artifactId>
39+
<version>${project.version}</version>
40+
</path>
41+
</annotationProcessorPaths>
42+
</configuration>
43+
</execution>
44+
</executions>
45+
</plugin>
46+
</plugins>
47+
</build>
48+
49+
</project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>quarkus-cyclonedx-parent</artifactId>
7+
<groupId>io.quarkus</groupId>
8+
<version>999-SNAPSHOT</version>
9+
<relativePath>../pom.xml</relativePath>
10+
</parent>
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<artifactId>quarkus-cyclonedx-endpoint-parent</artifactId>
14+
<name>Quarkus - CycloneDX - Endpoint</name>
15+
<packaging>pom</packaging>
16+
<modules>
17+
<module>deployment</module>
18+
<module>runtime</module>
19+
</modules>
20+
21+
</project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.quarkus</groupId>
9+
<artifactId>quarkus-cyclonedx-endpoint-parent</artifactId>
10+
<version>999-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>quarkus-cyclonedx-endpoint</artifactId>
14+
<name>Quarkus - CycloneDX - Endpoint - Runtime</name>
15+
<description>CycloneDX REST Endpoint</description>
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.quarkus</groupId>
19+
<artifactId>quarkus-core</artifactId>
20+
</dependency>
21+
</dependencies>
22+
<build>
23+
<plugins>
24+
<plugin>
25+
<groupId>io.quarkus</groupId>
26+
<artifactId>quarkus-extension-maven-plugin</artifactId>
27+
</plugin>
28+
</plugins>
29+
</build>
30+
</project>

extensions/cyclonedx/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<module>generator</module>
1818
<module>deployment</module>
1919
<module>runtime</module>
20+
<module>endpoint</module>
2021
</modules>
2122

2223
</project>

integration-tests/maven/src/test/java/io/quarkus/maven/it/CycloneDxIT.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.assertj.core.api.Assertions.assertThat;
55

66
import java.io.File;
7+
import java.nio.file.Path;
78
import java.util.List;
89
import java.util.Map;
910
import java.util.Properties;
@@ -99,4 +100,109 @@ public void testMutableJar() throws Exception {
99100
assertComponent(components, "io.quarkus", "quarkus-cyclonedx", "runtime", "lib/main/");
100101
assertComponent(components, "io.quarkus", "quarkus-cyclonedx-deployment", "development", "lib/deployment/");
101102
}
103+
104+
@Test
105+
public void testEmbeddedSbomFastJar() throws Exception {
106+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-embedded-fast-jar");
107+
running = new RunningInvoker(testDir, false);
108+
109+
Properties p = new Properties();
110+
p.setProperty("quarkus.cyclonedx.embedded.enabled", "true");
111+
112+
final MavenProcessInvocationResult result = running.execute(
113+
List.of("package", "-DskipTests"),
114+
Map.of(), p);
115+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
116+
117+
// the embedded SBOM should be in generated-bytecode.jar
118+
final Path generatedJar = testDir.toPath()
119+
.resolve("target/quarkus-app/quarkus/generated-bytecode.jar");
120+
final String defaultResourceName = "META-INF/sbom/application.cdx.json";
121+
final Bom bom = parseEmbeddedSbom(generatedJar, defaultResourceName);
122+
123+
assertEmbeddedSbomComponents(bom);
124+
}
125+
126+
@Test
127+
public void testEmbeddedSbomUberJar() throws Exception {
128+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-embedded-uber-jar");
129+
running = new RunningInvoker(testDir, false);
130+
131+
Properties p = new Properties();
132+
p.setProperty("quarkus.package.jar.type", "uber-jar");
133+
p.setProperty("quarkus.cyclonedx.embedded.enabled", "true");
134+
135+
final MavenProcessInvocationResult result = running.execute(
136+
List.of("package", "-DskipTests"),
137+
Map.of(), p);
138+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
139+
140+
// for uber-jar, the resource is embedded directly in the runner jar
141+
final Path uberJar = testDir.toPath()
142+
.resolve("target/acme-app-1.0-SNAPSHOT-runner.jar");
143+
final String defaultResourceName = "META-INF/sbom/application.cdx.json";
144+
final Bom bom = parseEmbeddedSbom(uberJar, defaultResourceName);
145+
146+
assertEmbeddedSbomComponents(bom);
147+
}
148+
149+
@Test
150+
public void testEmbeddedSbomCustomResourceName() throws Exception {
151+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-embedded-custom-name");
152+
running = new RunningInvoker(testDir, false);
153+
154+
final String customResourceName = "META-INF/custom-sbom.json";
155+
Properties p = new Properties();
156+
p.setProperty("quarkus.cyclonedx.embedded.enabled", "true");
157+
p.setProperty("quarkus.cyclonedx.embedded.resource-name", customResourceName);
158+
159+
final MavenProcessInvocationResult result = running.execute(
160+
List.of("package", "-DskipTests"),
161+
Map.of(), p);
162+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
163+
164+
final Path generatedJar = testDir.toPath()
165+
.resolve("target/quarkus-app/quarkus/generated-bytecode.jar");
166+
167+
// the default resource name should not exist
168+
assertNoEmbeddedResource(generatedJar, "META-INF/sbom/application.cdx.json");
169+
170+
// the custom resource name should contain a valid SBOM
171+
final Bom bom = parseEmbeddedSbom(generatedJar, customResourceName);
172+
assertEmbeddedSbomComponents(bom);
173+
}
174+
175+
@Test
176+
public void testEmbeddedSbomDisabledByDefault() throws Exception {
177+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-embedded-disabled");
178+
running = new RunningInvoker(testDir, false);
179+
180+
final MavenProcessInvocationResult result = running.execute(
181+
List.of("package", "-DskipTests"),
182+
Map.of());
183+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
184+
185+
// without enabling embedded SBOM, the resource should not be present
186+
final Path generatedJar = testDir.toPath()
187+
.resolve("target/quarkus-app/quarkus/generated-bytecode.jar");
188+
assertNoEmbeddedResource(generatedJar, "META-INF/sbom/application.cdx.json");
189+
}
190+
191+
/**
192+
* Asserts that an embedded SBOM has the expected structure and contains
193+
* expected dependency components.
194+
*/
195+
private static void assertEmbeddedSbomComponents(Bom bom) {
196+
assertThat(bom).isNotNull();
197+
assertThat(bom.getMetadata()).isNotNull();
198+
assertThat(bom.getMetadata().getComponent()).isNotNull();
199+
200+
final List<Component> components = bom.getComponents();
201+
assertThat(components).isNotEmpty();
202+
// the embedded SBOM should contain application dependencies
203+
assertComponent(components, "io.quarkus", "quarkus-rest", "runtime", null);
204+
assertComponent(components, "io.quarkus", "quarkus-rest-deployment", "development", null);
205+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx", "runtime", null);
206+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx-deployment", "development", null);
207+
}
102208
}

0 commit comments

Comments
 (0)