Skip to content

Commit fae0dc9

Browse files
committed
Streamline plugin API
* Decouples runtime configuration and testing from the ExtensionFactory interface into separate RuntimeConfigurable and Testable interfaces. * Replace ExtensionContext with a more generic ServiceRegistry, which will be easier to extend going forward. * Improve naming of various classes, e.g. ExtensionKVStore -> KeyValueStore. * Updates the extensions REST API to indicate whether a given extension supports runtime configuration or testing. * Aligns response of extension list endpoints with others in the API. Signed-off-by: nscuro <nscuro@protonmail.com>
1 parent 83efdb8 commit fae0dc9

99 files changed

Lines changed: 886 additions & 653 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/src/main/openapi/components/schemas/extensions/list-extension-points-response.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
# SPDX-License-Identifier: Apache-2.0
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717
type: object
18+
allOf:
19+
- $ref: "../paginated-response.yaml"
1820
properties:
19-
extension_points:
21+
items:
2022
type: array
2123
items:
2224
$ref: "./list-extension-points-response-item.yaml"
2325
required:
24-
- extension_points
26+
- items

api/src/main/openapi/components/schemas/extensions/list-extensions-response-item.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,13 @@ type: object
1818
properties:
1919
name:
2020
type: string
21+
configurable:
22+
type: boolean
23+
description: Whether the extension supports runtime configuration.
24+
testable:
25+
type: boolean
26+
description: Whether the extension can be tested.
2127
required:
22-
- name
28+
- name
29+
- configurable
30+
- testable

api/src/main/openapi/components/schemas/extensions/list-extensions-response.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
# SPDX-License-Identifier: Apache-2.0
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717
type: object
18+
allOf:
19+
- $ref: "../paginated-response.yaml"
1820
properties:
19-
extensions:
21+
items:
2022
type: array
2123
items:
2224
$ref: "./list-extensions-response-item.yaml"
2325
required:
24-
- extensions
26+
- items

apiserver/src/main/java/org/dependencytrack/plugin/PluginInitializer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public void contextInitialized(ServletContextEvent event) {
8383
secretManager::getSecretValue,
8484
JdbiFactory.createJdbi(),
8585
HttpClient.INSTANCE,
86-
HttpClient.INSTANCE.userAgent(),
8786
extensionPoints);
8887

8988
LOGGER.info("Discovering plugins");

apiserver/src/main/java/org/dependencytrack/resources/v2/ExtensionsResource.java

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@
3636
import org.dependencytrack.api.v2.model.ListExtensionsResponseItem;
3737
import org.dependencytrack.api.v2.model.TestExtensionRequest;
3838
import org.dependencytrack.api.v2.model.TestExtensionResponse;
39+
import org.dependencytrack.api.v2.model.TotalCount;
40+
import org.dependencytrack.api.v2.model.TotalCountType;
3941
import org.dependencytrack.api.v2.model.UpdateExtensionConfigRequest;
4042
import org.dependencytrack.auth.Permissions;
4143
import org.dependencytrack.common.MdcScope;
4244
import org.dependencytrack.plugin.api.ExtensionFactory;
4345
import org.dependencytrack.plugin.api.ExtensionPoint;
4446
import org.dependencytrack.plugin.api.ExtensionTestCheck;
4547
import org.dependencytrack.plugin.api.ExtensionTestResult;
48+
import org.dependencytrack.plugin.api.RuntimeConfigurable;
49+
import org.dependencytrack.plugin.api.Testable;
4650
import org.dependencytrack.plugin.api.config.MutableConfigRegistry;
4751
import org.dependencytrack.plugin.api.config.RuntimeConfig;
4852
import org.dependencytrack.plugin.api.config.RuntimeConfigSpec;
@@ -58,6 +62,7 @@
5862

5963
import java.io.IOException;
6064
import java.io.UncheckedIOException;
65+
import java.util.Comparator;
6166
import java.util.Map;
6267
import java.util.SequencedCollection;
6368

@@ -93,7 +98,7 @@ public Response listExtensionPoints() {
9398
pluginManager.getExtensionPoints();
9499

95100
final var response = ListExtensionPointsResponse.builder()
96-
.extensionPoints(
101+
.items(
97102
extensionPoints.stream()
98103
.map(ExtensionPointMetadata::name)
99104
.sorted()
@@ -102,6 +107,11 @@ public Response listExtensionPoints() {
102107
.name(name)
103108
.build())
104109
.toList())
110+
.total(
111+
TotalCount.builder()
112+
.count((long) extensionPoints.size())
113+
.type(TotalCountType.EXACT)
114+
.build())
105115
.build();
106116

107117
return Response.ok(response).build();
@@ -121,15 +131,21 @@ public Response listExtensions(String extensionPointName) {
121131
pluginManager.getFactories(extensionPointClass);
122132

123133
final var response = ListExtensionsResponse.builder()
124-
.extensions(
134+
.items(
125135
extensionFactories.stream()
126-
.map(ExtensionFactory::extensionName)
127-
.sorted()
136+
.sorted(Comparator.comparing(ExtensionFactory::extensionName))
128137
.<ListExtensionsResponseItem>map(
129-
extensionName -> ListExtensionsResponseItem.builder()
130-
.name(extensionName)
138+
extensionFactory -> ListExtensionsResponseItem.builder()
139+
.name(extensionFactory.extensionName())
140+
.configurable(extensionFactory instanceof RuntimeConfigurable)
141+
.testable(extensionFactory instanceof Testable)
131142
.build())
132143
.toList())
144+
.total(
145+
TotalCount.builder()
146+
.count((long) extensionFactories.size())
147+
.type(TotalCountType.EXACT)
148+
.build())
133149
.build();
134150

135151
return Response.ok(response).build();
@@ -148,7 +164,8 @@ public Response getExtensionConfig(
148164
final ExtensionFactory<?> extensionFactory =
149165
getExtensionFactory(extensionPointClass, extensionName);
150166

151-
if (extensionFactory.runtimeConfigSpec() == null) {
167+
if (!(extensionFactory instanceof RuntimeConfigurable rc)
168+
|| rc.runtimeConfigSpec() == null) {
152169
throw new NotFoundException();
153170
}
154171

@@ -189,7 +206,10 @@ public Response updateExtensionConfig(
189206
final ExtensionFactory<?> extensionFactory =
190207
getExtensionFactory(extensionPointClass, extensionName);
191208

192-
final RuntimeConfigSpec runtimeConfigSpec = extensionFactory.runtimeConfigSpec();
209+
final RuntimeConfigSpec runtimeConfigSpec =
210+
extensionFactory instanceof RuntimeConfigurable rc
211+
? rc.runtimeConfigSpec()
212+
: null;
193213
if (runtimeConfigSpec == null) {
194214
throw new BadRequestException();
195215
}
@@ -235,7 +255,10 @@ public Response getExtensionConfigSchema(
235255
final ExtensionFactory<?> extensionFactory =
236256
getExtensionFactory(extensionPointClass, extensionName);
237257

238-
final RuntimeConfigSpec runtimeConfigSpec = extensionFactory.runtimeConfigSpec();
258+
final RuntimeConfigSpec runtimeConfigSpec =
259+
extensionFactory instanceof RuntimeConfigurable rc
260+
? rc.runtimeConfigSpec()
261+
: null;
239262
if (runtimeConfigSpec == null) {
240263
return Response.noContent().build();
241264
}
@@ -267,7 +290,10 @@ public Response testExtension(
267290
}
268291

269292
RuntimeConfig runtimeConfig = null;
270-
final RuntimeConfigSpec runtimeConfigSpec = extensionFactory.runtimeConfigSpec();
293+
final RuntimeConfigSpec runtimeConfigSpec =
294+
extensionFactory instanceof RuntimeConfigurable rc
295+
? rc.runtimeConfigSpec()
296+
: null;
271297
if (runtimeConfigSpec == null) {
272298
if (request.getConfig() != null) {
273299
throw new BadRequestException("The extension does not support configuration");
@@ -281,13 +307,12 @@ public Response testExtension(
281307
}
282308
}
283309

284-
final ExtensionTestResult testResult;
285-
try {
286-
testResult = extensionFactory.test(runtimeConfig);
287-
} catch (UnsupportedOperationException e) {
310+
if (!(extensionFactory instanceof Testable testable)) {
288311
throw new BadRequestException("The extension does not support testing");
289312
}
290313

314+
final ExtensionTestResult testResult = testable.test(runtimeConfig);
315+
291316
final var response = TestExtensionResponse.builder()
292317
.checks(testResult.checks().stream()
293318
.map(ExtensionsResource::convert)

apiserver/src/test/java/org/dependencytrack/dex/DexEngineInitializerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void shouldStartEngine() throws Exception {
105105
.when(servletContextMock).getAttribute(eq(FileStorage.class.getName()));
106106
doReturn(new PluginManager(config, cacheManager, secretManager::getSecretValue,
107107
JdbiFactory.createJdbi(),
108-
HttpClient.newHttpClient(), "Dependency-Track", Collections.emptyList()))
108+
HttpClient.newHttpClient(), Collections.emptyList()))
109109
.when(servletContextMock).getAttribute(eq(PluginManager.class.getName()));
110110
doReturn(secretManager)
111111
.when(servletContextMock).getAttribute(eq(SecretManager.class.getName()));

apiserver/src/test/java/org/dependencytrack/event/EventSubsystemInitializerTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ void shouldSubscribeAndUnsubscribeListeners() throws Exception {
6363
secretName -> null,
6464
JdbiFactory.createJdbi(),
6565
HttpClient.newHttpClient(),
66-
"Dependency-Track",
6766
Collections.emptyList());
6867
final var servletContextMock = mock(ServletContext.class);
6968

apiserver/src/test/java/org/dependencytrack/notification/PublishNotificationWorkflowTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ void beforeEach() {
8383
secretName -> null,
8484
JdbiFactory.createJdbi(),
8585
HttpClient.newHttpClient(),
86-
"Dependency-Track",
8786
List.of(NotificationPublisher.class));
8887
pluginManager.loadPlugins(List.of(
8988
new DefaultNotificationPublishersPlugin()));

apiserver/src/test/java/org/dependencytrack/pkgmetadata/MockPackageMetadataResolverPlugin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import org.dependencytrack.pkgmetadata.resolution.api.PackageMetadataResolver;
2424
import org.dependencytrack.pkgmetadata.resolution.api.PackageMetadataResolverFactory;
2525
import org.dependencytrack.pkgmetadata.resolution.api.PackageRepository;
26-
import org.dependencytrack.plugin.api.ExtensionContext;
2726
import org.dependencytrack.plugin.api.ExtensionFactory;
2827
import org.dependencytrack.plugin.api.ExtensionPoint;
2928
import org.dependencytrack.plugin.api.Plugin;
29+
import org.dependencytrack.plugin.api.ServiceRegistry;
3030
import org.jspecify.annotations.NonNull;
3131
import org.jspecify.annotations.Nullable;
3232

@@ -83,7 +83,7 @@ private static final class MockPackageMetadataResolverFactory implements Package
8383
}
8484

8585
@Override
86-
public void init(@NonNull ExtensionContext ctx) {
86+
public void init(@NonNull ServiceRegistry serviceRegistry) {
8787
}
8888

8989
@Override

apiserver/src/test/java/org/dependencytrack/pkgmetadata/ResolvePackageMetadataWorkflowTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ void beforeEach() {
8181
secretName -> null,
8282
JdbiFactory.createJdbi(),
8383
HttpClient.newHttpClient(),
84-
"Dependency-Track",
8584
List.of(PackageMetadataResolver.class));
8685
pluginManager.loadPlugins(List.of(mockPlugin));
8786

0 commit comments

Comments
 (0)