From 7e517c9b39ade829afc61ebd4b4c7bacdf27551e Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Mon, 10 Jan 2022 21:54:40 +0000 Subject: [PATCH] Dev console commands and UI for listing dev services Exclude junit from keycloak test server dependency --- bom/application/pom.xml | 10 + .../DevServicesConfigResultBuildItem.java | 3 + .../builditem/DevServicesResultBuildItem.java | 97 ++++++++ .../steps/DevServicesConfigBuildStep.java | 5 + devtools/bom-descriptor-json/pom.xml | 13 + docs/pom.xml | 13 + .../apicurio-registry-avro/deployment/pom.xml | 2 +- .../DevServicesApicurioRegistryProcessor.java | 78 +++--- .../apicurio-registry-avro/runtime/pom.xml | 4 + .../spi/DevServicesDatasourceProvider.java | 8 +- .../DevServicesDatasourceResultBuildItem.java | 55 ----- extensions/datasource/deployment/pom.xml | 4 + .../DevServicesDatasourceProcessor.java | 54 ++--- extensions/datasource/runtime/pom.xml | 4 + .../devservices/common/ContainerAddress.java | 8 +- .../devservices/common/ContainerLocator.java | 1 + .../deployment/DB2DevServicesProcessor.java | 4 +- extensions/devservices/deployment/pom.xml | 49 ++++ .../deployment/ContainerLogForwarder.java | 79 ++++++ .../deployment/DevServicesCommand.java | 51 ++++ .../deployment/DevServicesListCommand.java | 26 ++ .../deployment/DevServicesLogsCommand.java | 67 +++++ .../deployment/DevServicesProcessor.java | 228 ++++++++++++++++++ .../resources/dev-templates/dev-services.html | 65 +++++ .../resources/dev-templates/embedded.html | 4 + .../deployment/DerbyDevServicesProcessor.java | 2 +- .../h2/deployment/H2DevServicesProcessor.java | 2 +- .../MariaDBDevServicesProcessor.java | 4 +- .../deployment/MSSQLDevServicesProcessor.java | 4 +- .../deployment/MySQLDevServicesProcessor.java | 4 +- .../OracleDevServicesProcessor.java | 4 +- extensions/devservices/pom.xml | 2 + .../PostgresqlDevServicesProcessor.java | 4 +- extensions/devservices/runtime/pom.xml | 43 ++++ .../runtime/devmode/ContainerInfo.java | 154 ++++++++++++ .../devmode/DevServiceDescription.java | 53 ++++ .../runtime/devmode/DevServices.java | 25 ++ .../runtime/devmode/DevServicesRecorder.java | 14 ++ .../resources/META-INF/quarkus-extension.yaml | 9 + .../infinispan-client/deployment/pom.xml | 2 +- .../InfinispanDevServiceProcessor.java | 96 ++++---- extensions/infinispan-client/runtime/pom.xml | 4 + extensions/kafka-client/deployment/pom.xml | 2 +- .../DevServicesKafkaBrokerBuildItem.java | 17 -- .../deployment/DevServicesKafkaProcessor.java | 102 +++----- extensions/kafka-client/runtime/pom.xml | 4 + extensions/mongodb-client/deployment/pom.xml | 2 +- .../deployment/DevServicesMongoProcessor.java | 76 ++---- extensions/mongodb-client/runtime/pom.xml | 4 + .../oidc-client-filter/deployment/pom.xml | 6 + extensions/oidc-client/deployment/pom.xml | 4 + extensions/oidc-client/runtime/pom.xml | 4 + extensions/oidc/deployment/pom.xml | 6 +- .../KeycloakDevServicesProcessor.java | 152 +++++------- extensions/oidc/runtime/pom.xml | 4 + .../deployment/pom.xml | 6 + extensions/redis-client/deployment/pom.xml | 2 +- .../deployment/DevServicesRedisProcessor.java | 63 +++-- extensions/redis-client/runtime/pom.xml | 4 + .../deployment/pom.xml | 2 +- .../deployment/AmqpDevServicesProcessor.java | 105 ++++---- .../DevServicesAmqpBrokerBuildItem.java | 18 -- .../runtime/pom.xml | 4 + .../deployment/pom.xml | 2 +- .../DevServicesRabbitMQBrokerBuildItem.java | 18 -- .../RabbitMQDevServicesProcessor.java | 110 ++++----- .../runtime/pom.xml | 4 + .../DevConsoleDevServicesSmokeTest.java | 33 +++ 68 files changed, 1478 insertions(+), 634 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesResultBuildItem.java delete mode 100644 extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceResultBuildItem.java create mode 100644 extensions/devservices/deployment/pom.xml create mode 100644 extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/ContainerLogForwarder.java create mode 100644 extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesCommand.java create mode 100644 extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesListCommand.java create mode 100644 extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesLogsCommand.java create mode 100644 extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java create mode 100644 extensions/devservices/deployment/src/main/resources/dev-templates/dev-services.html create mode 100644 extensions/devservices/deployment/src/main/resources/dev-templates/embedded.html create mode 100644 extensions/devservices/runtime/pom.xml create mode 100644 extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/ContainerInfo.java create mode 100644 extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServiceDescription.java create mode 100644 extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServices.java create mode 100644 extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServicesRecorder.java create mode 100644 extensions/devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml delete mode 100644 extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaBrokerBuildItem.java delete mode 100644 extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/DevServicesAmqpBrokerBuildItem.java delete mode 100644 extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/DevServicesRabbitMQBrokerBuildItem.java create mode 100644 integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleDevServicesSmokeTest.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 276341e7d55fd0..2ed83fed3a13c9 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -641,6 +641,16 @@ quarkus-devservices-common ${project.version} + + io.quarkus + quarkus-devservices-deployment + ${project.version} + + + io.quarkus + quarkus-devservices + ${project.version} + io.quarkus quarkus-elasticsearch-rest-client-common diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesConfigResultBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesConfigResultBuildItem.java index 0d98a21d0c42e4..4e338b83ff43ec 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesConfigResultBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesConfigResultBuildItem.java @@ -8,7 +8,10 @@ * Used to start and configure dev services, any processor starting dev services should produce these items. * * Quarkus will make sure the relevant settings are present in both JVM and native modes. + * + * @deprecated use {@link DevServicesResultBuildItem} */ +@Deprecated public final class DevServicesConfigResultBuildItem extends MultiBuildItem { final String key; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesResultBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesResultBuildItem.java new file mode 100644 index 00000000000000..5ed1385142e113 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesResultBuildItem.java @@ -0,0 +1,97 @@ +package io.quarkus.deployment.builditem; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * BuildItem for running dev services + * Combines injected configs to the application with container id (if it exists). + * + * Processors are expected to return this build item not only when the dev service first starts, + * but also if a running dev service already exists. + * + * {@link RunningDevService} helps to manage the lifecycle of the running dev service + */ +public final class DevServicesResultBuildItem extends MultiBuildItem { + + private final String name; + private final String containerId; + private final Map config; + + public DevServicesResultBuildItem(String name, String containerId, Map config) { + this.name = name; + this.containerId = containerId; + this.config = config; + } + + public String getName() { + return name; + } + + public String getContainerId() { + return containerId; + } + + public Map getConfig() { + return config; + } + + public static class RunningDevService implements Closeable { + + private final String name; + private final String containerId; + private final Map config; + private final Closeable closeable; + + private static Map mapOf(String key, String value) { + Map map = new HashMap<>(); + map.put(key, value); + return map; + } + + public RunningDevService(String name, String containerId, Closeable closeable, String key, String value) { + this(name, containerId, closeable, mapOf(key, value)); + } + + public RunningDevService(String name, String containerId, Closeable closeable, Map config) { + this.name = name; + this.containerId = containerId; + this.closeable = closeable; + this.config = Collections.unmodifiableMap(config); + } + + public String getName() { + return name; + } + + public String getContainerId() { + return containerId; + } + + public Map getConfig() { + return config; + } + + public Closeable getCloseable() { + return closeable; + } + + public boolean isOwner() { + return closeable != null; + } + + @Override + public void close() throws IOException { + this.closeable.close(); + } + + public DevServicesResultBuildItem toBuildItem() { + return new DevServicesResultBuildItem(name, containerId, config); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/DevServicesConfigBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/DevServicesConfigBuildStep.java index 5c01b2b7bbf1b7..c035e78d5e3249 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/DevServicesConfigBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/DevServicesConfigBuildStep.java @@ -16,6 +16,7 @@ import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesNativeConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; @@ -32,9 +33,13 @@ List deprecated(List runtimeConfig, List devServicesConfigResultBuildItems, + List devServicesResultBuildItems, CuratedApplicationShutdownBuildItem shutdownBuildItem) { Map newProperties = new HashMap<>(devServicesConfigResultBuildItems.stream().collect( Collectors.toMap(DevServicesConfigResultBuildItem::getKey, DevServicesConfigResultBuildItem::getValue))); + for (DevServicesResultBuildItem resultBuildItem : devServicesResultBuildItems) { + newProperties.putAll(resultBuildItem.getConfig()); + } Config config = ConfigProvider.getConfig(); //check if there are existing already started dev services //if there were no changes to the processors they don't produce config diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index 475c041b7053b5..286caf141ab496 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -396,6 +396,19 @@ + + io.quarkus + quarkus-devservices + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-elasticsearch-rest-client diff --git a/docs/pom.xml b/docs/pom.xml index 24ca4c204ba5cb..9070e2d830a849 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -356,6 +356,19 @@ + + io.quarkus + quarkus-devservices-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-elasticsearch-rest-client-deployment diff --git a/extensions/apicurio-registry-avro/deployment/pom.xml b/extensions/apicurio-registry-avro/deployment/pom.xml index 388a8ad5b82fb8..4df4d4cbfd5328 100644 --- a/extensions/apicurio-registry-avro/deployment/pom.xml +++ b/extensions/apicurio-registry-avro/deployment/pom.xml @@ -49,7 +49,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment diff --git a/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java b/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java index ae450b9c249938..6bcf7a83f93e77 100644 --- a/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java +++ b/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java @@ -11,12 +11,13 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; @@ -50,56 +51,50 @@ public class DevServicesApicurioRegistryProcessor { private static final ContainerLocator apicurioRegistryContainerLocator = new ContainerLocator(DEV_SERVICE_LABEL, APICURIO_REGISTRY_PORT); - static volatile AutoCloseable closeable; + static volatile RunningDevService devService; static volatile ApicurioRegistryDevServiceCfg cfg; static volatile boolean first = true; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - public void startApicurioRegistryDevService(LaunchModeBuildItem launchMode, + public DevServicesResultBuildItem startApicurioRegistryDevService(LaunchModeBuildItem launchMode, ApicurioRegistryDevServicesBuildTimeConfig apicurioRegistryDevServices, List devServicesSharedNetworkBuildItem, - BuildProducer devServicesConfiguration, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) { ApicurioRegistryDevServiceCfg configuration = getConfiguration(apicurioRegistryDevServices); - if (closeable != null) { + if (devService != null) { boolean restartRequired = !configuration.equals(cfg); if (!restartRequired) { - return; + return devService.toBuildItem(); } shutdownApicurioRegistry(); cfg = null; } - ApicurioRegistry apicurioRegistry; StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "Apicurio Registry Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); try { - apicurioRegistry = startApicurioRegistry(configuration, launchMode, + devService = startApicurioRegistry(configuration, launchMode, !devServicesSharedNetworkBuildItem.isEmpty(), devServicesConfig.timeout); - if (apicurioRegistry == null) { - compressor.close(); - return; - } compressor.close(); } catch (Throwable t) { compressor.closeAndDumpCaptured(); throw new RuntimeException(t); } - cfg = configuration; - closeable = apicurioRegistry.getCloseable(); + if (devService == null) { + return null; + } - devServicesConfiguration.produce(new DevServicesConfigResultBuildItem( - REGISTRY_URL_CONFIG, apicurioRegistry.getUrl() + "/apis/registry/v2")); + cfg = configuration; - if (apicurioRegistry.isOwner()) { - log.infof("Dev Services for Apicurio Registry started. The registry is available at %s", apicurioRegistry.getUrl()); + if (devService.isOwner()) { + log.infof("Dev Services for Apicurio Registry started. The registry is available at %s", getRegistryUrlConfig()); } // Configure the watch dog @@ -108,31 +103,36 @@ public void startApicurioRegistryDevService(LaunchModeBuildItem launchMode, Runnable closeTask = new Runnable() { @Override public void run() { - if (closeable != null) { + if (devService != null) { shutdownApicurioRegistry(); } first = true; - closeable = null; + devService = null; cfg = null; } }; closeBuildItem.addCloseTask(closeTask, true); } + return devService.toBuildItem(); + } + + private String getRegistryUrlConfig() { + return devService.getConfig().get(REGISTRY_URL_CONFIG) + "/apis/registry/v2"; } private void shutdownApicurioRegistry() { - if (closeable != null) { + if (devService != null) { try { - closeable.close(); + devService.close(); } catch (Throwable e) { log.error("Failed to stop Apicurio Registry", e); } finally { - closeable = null; + devService = null; } } } - private ApicurioRegistry startApicurioRegistry(ApicurioRegistryDevServiceCfg config, LaunchModeBuildItem launchMode, + private RunningDevService startApicurioRegistry(ApicurioRegistryDevServiceCfg config, LaunchModeBuildItem launchMode, boolean useSharedNetwork, Optional timeout) { if (!config.devServicesEnabled) { // explicitly disabled @@ -157,7 +157,8 @@ private ApicurioRegistry startApicurioRegistry(ApicurioRegistryDevServiceCfg con // Starting the broker return apicurioRegistryContainerLocator.locateContainer(config.serviceName, config.shared, launchMode.getLaunchMode()) - .map(containerAddress -> new ApicurioRegistry(containerAddress.getUrl(), null)) + .map(containerAddress -> new RunningDevService(Feature.APICURIO_REGISTRY_AVRO.getName(), + containerAddress.getId(), null, REGISTRY_URL_CONFIG, containerAddress.getUrl())) .orElseGet(() -> { ApicurioRegistryContainer container = new ApicurioRegistryContainer( DockerImageName.parse(config.imageName), config.fixedExposedPort, @@ -166,7 +167,8 @@ private ApicurioRegistry startApicurioRegistry(ApicurioRegistryDevServiceCfg con timeout.ifPresent(container::withStartupTimeout); container.start(); - return new ApicurioRegistry(container.getUrl(), container); + return new RunningDevService(Feature.APICURIO_REGISTRY_AVRO.getName(), container.getContainerId(), + container::close, REGISTRY_URL_CONFIG, container.getUrl()); }); } @@ -193,28 +195,6 @@ private ApicurioRegistryDevServiceCfg getConfiguration(ApicurioRegistryDevServic return new ApicurioRegistryDevServiceCfg(cfg); } - private static class ApicurioRegistry { - private final String url; - private final AutoCloseable closeable; - - public ApicurioRegistry(String url, AutoCloseable closeable) { - this.url = url; - this.closeable = closeable; - } - - public String getUrl() { - return url; - } - - public AutoCloseable getCloseable() { - return closeable; - } - - public boolean isOwner() { - return closeable != null; - } - } - private static final class ApicurioRegistryDevServiceCfg { private final boolean devServicesEnabled; private final String imageName; diff --git a/extensions/apicurio-registry-avro/runtime/pom.xml b/extensions/apicurio-registry-avro/runtime/pom.xml index 8544a4c7a3069b..44c69d6f669076 100644 --- a/extensions/apicurio-registry-avro/runtime/pom.xml +++ b/extensions/apicurio-registry-avro/runtime/pom.xml @@ -51,6 +51,10 @@ org.apache.commons commons-lang3 + + io.quarkus + quarkus-devservices + diff --git a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceProvider.java b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceProvider.java index fe5666027f196a..25bd51c5571ed5 100644 --- a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceProvider.java +++ b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceProvider.java @@ -23,18 +23,24 @@ default boolean isDockerRequired() { class RunningDevServicesDatasource { + private final String id; private final String url; private final String username; private final String password; private final Closeable closeTask; - public RunningDevServicesDatasource(String url, String username, String password, Closeable closeTask) { + public RunningDevServicesDatasource(String id, String url, String username, String password, Closeable closeTask) { + this.id = id; this.url = url; this.username = username; this.password = password; this.closeTask = closeTask; } + public String getId() { + return id; + } + public String getUrl() { return url; } diff --git a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceResultBuildItem.java b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceResultBuildItem.java deleted file mode 100644 index 2cbd92f923a5b9..00000000000000 --- a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevServicesDatasourceResultBuildItem.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.quarkus.datasource.deployment.spi; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.datasource.common.runtime.DataSourceUtil; - -public final class DevServicesDatasourceResultBuildItem extends SimpleBuildItem { - - final DbResult defaultDatasource; - final Map namedDatasources; - - public DevServicesDatasourceResultBuildItem(DbResult defaultDatasource, Map namedDatasources) { - this.defaultDatasource = defaultDatasource; - this.namedDatasources = Collections.unmodifiableMap(namedDatasources); - } - - public DbResult getDefaultDatasource() { - return defaultDatasource; - } - - public Map getNamedDatasources() { - return namedDatasources; - } - - public static DbResult resolve(Optional devDbResultBuildItem, String dataSourceName) { - if (devDbResultBuildItem.isPresent()) { - if (dataSourceName.equals(DataSourceUtil.DEFAULT_DATASOURCE_NAME)) { - return devDbResultBuildItem.get().defaultDatasource; - } - return devDbResultBuildItem.get().namedDatasources.get(dataSourceName); - } - return null; - } - - public static class DbResult { - final String dbType; - final Map configProperties; - - public DbResult(String dbType, Map configProperties) { - this.dbType = dbType; - this.configProperties = Collections.unmodifiableMap(configProperties); - } - - public String getDbType() { - return dbType; - } - - public Map getConfigProperties() { - return configProperties; - } - } -} diff --git a/extensions/datasource/deployment/pom.xml b/extensions/datasource/deployment/pom.xml index 3cae04a4341ce1..676f7b24bed928 100644 --- a/extensions/datasource/deployment/pom.xml +++ b/extensions/datasource/deployment/pom.xml @@ -33,6 +33,10 @@ io.quarkus quarkus-kubernetes-service-binding-spi + + io.quarkus + quarkus-devservices-deployment + diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java index 84e47aadc76220..b9b49a151cc4a3 100644 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java @@ -17,15 +17,14 @@ import io.quarkus.datasource.deployment.spi.DevServicesDatasourceConfigurationHandlerBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProvider; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProviderBuildItem; -import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; import io.quarkus.deployment.console.StartupLogCompressor; @@ -38,7 +37,7 @@ public class DevServicesDatasourceProcessor { private static final Logger log = Logger.getLogger(DevServicesDatasourceProcessor.class); - static volatile List databases; + static volatile List databases; static volatile Map cachedProperties; @@ -47,13 +46,12 @@ public class DevServicesDatasourceProcessor { private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem curateOutcomeBuildItem, + List launchDatabases(CurateOutcomeBuildItem curateOutcomeBuildItem, List installedDrivers, List devDBProviders, DataSourcesBuildTimeConfig dataSourceBuildTimeConfig, LaunchModeBuildItem launchMode, List configurationHandlerBuildItems, - BuildProducer devServicesResultBuildItemBuildProducer, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, @@ -82,7 +80,7 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura } } if (!restartRequired) { - return null; + return databases.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } for (Closeable i : databases) { try { @@ -94,8 +92,6 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura databases = null; cachedProperties = null; } - DevServicesDatasourceResultBuildItem.DbResult defaultResult; - Map namedResults = new HashMap<>(); //now we need to figure out if we need to launch some databases //note that because we run in dev and test mode only we know the runtime //config at build time, as they both execute in the same JVM @@ -104,7 +100,7 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura //support for named datasources will come later Map propertiesMap = new HashMap<>(); - List closeableList = new ArrayList<>(); + List runningDevServices = new ArrayList<>(); Map> configHandlersByDbType = configurationHandlerBuildItems .stream() .collect(Collectors.toMap(DevServicesDatasourceConfigurationHandlerBuildItem::getDbKind, @@ -118,34 +114,24 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura Map devDBProviderMap = devDBProviders.stream() .collect(Collectors.toMap(DevServicesDatasourceProviderBuildItem::getDatabase, DevServicesDatasourceProviderBuildItem::getDevServicesProvider)); - defaultResult = startDevDb(null, curateOutcomeBuildItem, installedDrivers, + RunningDevService defaultDevService = startDevDb(null, curateOutcomeBuildItem, installedDrivers, !dataSourceBuildTimeConfig.namedDataSources.isEmpty(), devDBProviderMap, dataSourceBuildTimeConfig.defaultDataSource, - configHandlersByDbType, propertiesMap, closeableList, launchMode.getLaunchMode(), consoleInstalledBuildItem, + configHandlersByDbType, propertiesMap, launchMode.getLaunchMode(), consoleInstalledBuildItem, loggingSetupBuildItem, globalDevServicesConfig); - List dbConfig = new ArrayList<>(); - if (defaultResult != null) { - for (Map.Entry i : defaultResult.getConfigProperties().entrySet()) { - dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue())); - } + if (defaultDevService != null) { + runningDevServices.add(defaultDevService); } for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) { - DevServicesDatasourceResultBuildItem.DbResult result = startDevDb(entry.getKey(), curateOutcomeBuildItem, + RunningDevService namedDevService = startDevDb(entry.getKey(), curateOutcomeBuildItem, installedDrivers, true, - devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, closeableList, + devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, launchMode.getLaunchMode(), consoleInstalledBuildItem, loggingSetupBuildItem, globalDevServicesConfig); - if (result != null) { - namedResults.put(entry.getKey(), result); - for (Map.Entry i : result.getConfigProperties().entrySet()) { - dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue())); - } + if (namedDevService != null) { + runningDevServices.add(namedDevService); } } - for (DevServicesConfigResultBuildItem i : dbConfig) { - devServicesResultBuildItemBuildProducer - .produce(i); - } if (first) { first = false; @@ -168,9 +154,9 @@ public void run() { }; closeBuildItem.addCloseTask(closeTask, true); } - databases = closeableList; + databases = runningDevServices; cachedProperties = propertiesMap; - return new DevServicesDatasourceResultBuildItem(defaultResult, namedResults); + return databases.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } private String trim(String optional) { @@ -180,13 +166,13 @@ private String trim(String optional) { return optional.trim(); } - private DevServicesDatasourceResultBuildItem.DbResult startDevDb(String dbName, + private RunningDevService startDevDb(String dbName, CurateOutcomeBuildItem curateOutcomeBuildItem, List installedDrivers, boolean hasNamedDatasources, Map devDBProviders, DataSourceBuildTimeConfig dataSourceBuildTimeConfig, Map> configurationHandlerBuildItems, - Map propertiesMap, List closeableList, + Map propertiesMap, LaunchMode launchMode, Optional consoleInstalledBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig globalDevServicesConfig) { boolean explicitlyDisabled = !(dataSourceBuildTimeConfig.devservices.enabled @@ -269,7 +255,6 @@ private DevServicesDatasourceResultBuildItem.DbResult startDevDb(String dbName, dataSourceBuildTimeConfig.devservices.containerProperties, dataSourceBuildTimeConfig.devservices.properties, dataSourceBuildTimeConfig.devservices.port, launchMode, globalDevServicesConfig.timeout); - closeableList.add(datasource.getCloseTask()); propertiesMap.put(prefix + "db-kind", dataSourceBuildTimeConfig.dbKind.orElse(null)); String devServicesPrefix = prefix + "devservices."; @@ -308,7 +293,8 @@ private DevServicesDatasourceResultBuildItem.DbResult startDevDb(String dbName, devDebProperties.put(name, ConfigProvider.getConfig().getValue(name, String.class)); } } - return new DevServicesDatasourceResultBuildItem.DbResult(defaultDbKind.get(), devDebProperties); + return new RunningDevService(dbName != null ? dbName : defaultDbKind.get(), datasource.getId(), + datasource.getCloseTask(), devDebProperties); } catch (Throwable t) { compressor.closeAndDumpCaptured(); throw new RuntimeException(t); diff --git a/extensions/datasource/runtime/pom.xml b/extensions/datasource/runtime/pom.xml index eae8d29226ad36..350e97997f1782 100644 --- a/extensions/datasource/runtime/pom.xml +++ b/extensions/datasource/runtime/pom.xml @@ -39,6 +39,10 @@ io.quarkus quarkus-credentials + + io.quarkus + quarkus-devservices + diff --git a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerAddress.java b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerAddress.java index 3d2f90d307bd5e..2773132dfc9fc2 100644 --- a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerAddress.java +++ b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerAddress.java @@ -1,14 +1,20 @@ package io.quarkus.devservices.common; public class ContainerAddress { + private final String id; private final String host; private final int port; - public ContainerAddress(String host, int port) { + public ContainerAddress(String id, String host, int port) { + this.id = id; this.host = host; this.port = port; } + public String getId() { + return id; + } + public String getHost() { return host; } diff --git a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java index 70de1108b2b5d5..5ac10a7408e58c 100644 --- a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java +++ b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java @@ -47,6 +47,7 @@ public Optional locateContainer(String serviceName, boolean sh .flatMap(containerPort -> Optional.ofNullable(containerPort.getPublicPort()) .map(port -> { final ContainerAddress containerAddress = new ContainerAddress( + container.getId(), DockerClientFactory.instance().dockerHostIpAddress(), containerPort.getPublicPort()); log.infof("Dev Services container found: %s (%s). Connecting to: %s.", diff --git a/extensions/devservices/db2/src/main/java/io/quarkus/devservices/db2/deployment/DB2DevServicesProcessor.java b/extensions/devservices/db2/src/main/java/io/quarkus/devservices/db2/deployment/DB2DevServicesProcessor.java index 99a45bf1c1e8ef..904366fe5df0ad 100644 --- a/extensions/devservices/db2/src/main/java/io/quarkus/devservices/db2/deployment/DB2DevServicesProcessor.java +++ b/extensions/devservices/db2/src/main/java/io/quarkus/devservices/db2/deployment/DB2DevServicesProcessor.java @@ -49,7 +49,9 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt LOG.info("Dev Services for IBM Db2 started."); - return new RunningDevServicesDatasource(container.getEffectiveJdbcUrl(), container.getUsername(), + return new RunningDevServicesDatasource(container.getContainerId(), + container.getEffectiveJdbcUrl(), + container.getUsername(), container.getPassword(), new Closeable() { @Override diff --git a/extensions/devservices/deployment/pom.xml b/extensions/devservices/deployment/pom.xml new file mode 100644 index 00000000000000..cbec8cbafde7e4 --- /dev/null +++ b/extensions/devservices/deployment/pom.xml @@ -0,0 +1,49 @@ + + + + quarkus-devservices-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devservices-deployment + Quarkus - DevServices - Deployment + + + + io.quarkus + quarkus-devservices + + + io.quarkus + quarkus-devservices-common + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-core-deployment + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/ContainerLogForwarder.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/ContainerLogForwarder.java new file mode 100644 index 00000000000000..372b52798edcf1 --- /dev/null +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/ContainerLogForwarder.java @@ -0,0 +1,79 @@ +package io.quarkus.devservices.deployment; + +import static org.testcontainers.containers.output.OutputFrame.OutputType.STDERR; +import static org.testcontainers.containers.output.OutputFrame.OutputType.STDOUT; + +import java.io.Closeable; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.jboss.logging.Logger; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.output.FrameConsumerResultCallback; +import org.testcontainers.containers.output.OutputFrame; + +import io.quarkus.devservices.runtime.devmode.DevServiceDescription; + +public class ContainerLogForwarder implements Closeable { + + private final DevServiceDescription devService; + private final AtomicLong timestamp = new AtomicLong(0L); + private final Logger logger; + private final String shortId; + private FrameConsumerResultCallback resultCallback; + private final AtomicBoolean running = new AtomicBoolean(false); + + public ContainerLogForwarder(DevServiceDescription devService) { + this.devService = devService; + this.logger = Logger.getLogger(devService.getName()); + this.shortId = devService.getContainerInfo().getShortId(); + } + + public DevServiceDescription getDevService() { + return devService; + } + + public boolean isRunning() { + return running.get(); + } + + public void start() { + if (running.compareAndSet(false, true)) { + this.resultCallback = new FrameConsumerResultCallback(); + this.resultCallback.addConsumer(STDOUT, frame -> { + if (running.get()) + logger.infof("[%s] %s", shortId, updateTimestamp(frame)); + }); + this.resultCallback.addConsumer(STDERR, frame -> { + if (running.get()) + logger.errorf("[%s] %s", shortId, updateTimestamp(frame)); + }); + DockerClientFactory.lazyClient().logContainerCmd(devService.getContainerInfo().getId()) + .withFollowStream(true) + .withStdErr(true) + .withStdOut(true) + .withSince(timestamp.intValue()) + .exec(resultCallback); + } + } + + @Override + public void close() { + if (running.compareAndSet(true, false)) { + try { + resultCallback.close(); + } catch (Throwable throwable) { + logger.errorf("Failed to close log forwarder %s", devService.getName()); + } finally { + resultCallback = null; + } + } + } + + private String updateTimestamp(OutputFrame frame) { + timestamp.set(Instant.now().getEpochSecond()); + return frame.getUtf8String().trim(); + } + +} diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesCommand.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesCommand.java new file mode 100644 index 00000000000000..01f592d6ef27dc --- /dev/null +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesCommand.java @@ -0,0 +1,51 @@ +package io.quarkus.devservices.deployment; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.aesh.command.Command; +import org.aesh.command.CommandResult; +import org.aesh.command.GroupCommand; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.invocation.CommandInvocation; + +import io.quarkus.deployment.console.SetCompleter; +import io.quarkus.devservices.runtime.devmode.DevServiceDescription; + +@GroupCommandDefinition(name = "devservices", description = "Dev Service Commands") +public class DevServicesCommand implements GroupCommand { + static List serviceDescriptions; + + public DevServicesCommand(List serviceDescriptions) { + DevServicesCommand.serviceDescriptions = serviceDescriptions; + } + + @Override + public List getCommands() { + return List.of(new DevServicesListCommand(), new DevServicesLogsCommand()); + } + + @Override + public CommandResult execute(CommandInvocation commandInvocation) { + commandInvocation.println(commandInvocation.getHelpInfo()); + return CommandResult.SUCCESS; + } + + static Optional findDevService(String devServiceName) { + return serviceDescriptions.stream() + .filter(d -> d.getName().equals(devServiceName)) + .findFirst(); + } + + public static class DevServiceCompleter extends SetCompleter { + + @Override + protected Set allOptions(String soFar) { + return serviceDescriptions.stream() + .map(DevServiceDescription::getName) + .collect(Collectors.toSet()); + } + } +} diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesListCommand.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesListCommand.java new file mode 100644 index 00000000000000..b2e36090f18b37 --- /dev/null +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesListCommand.java @@ -0,0 +1,26 @@ +package io.quarkus.devservices.deployment; + +import static io.quarkus.devservices.deployment.DevServicesProcessor.printDevService; + +import org.aesh.command.Command; +import org.aesh.command.CommandDefinition; +import org.aesh.command.CommandResult; +import org.aesh.command.invocation.CommandInvocation; + +import io.quarkus.devservices.runtime.devmode.DevServiceDescription; + +@CommandDefinition(name = "list", description = "List of dev services") +public class DevServicesListCommand implements Command { + + @Override + public CommandResult execute(CommandInvocation commandInvocation) { + commandInvocation.println(""); + StringBuilder builder = new StringBuilder(); + for (DevServiceDescription serviceDescription : DevServicesCommand.serviceDescriptions) { + printDevService(builder, serviceDescription, false); + builder.append("\n"); + } + commandInvocation.print(builder.toString()); + return CommandResult.SUCCESS; + } +} diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesLogsCommand.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesLogsCommand.java new file mode 100644 index 00000000000000..a615764f57fa6b --- /dev/null +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesLogsCommand.java @@ -0,0 +1,67 @@ +package io.quarkus.devservices.deployment; + +import static io.quarkus.devservices.deployment.DevServicesCommand.findDevService; + +import java.io.IOException; +import java.util.Optional; + +import org.aesh.command.Command; +import org.aesh.command.CommandDefinition; +import org.aesh.command.CommandResult; +import org.aesh.command.invocation.CommandInvocation; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Option; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.output.FrameConsumerResultCallback; +import org.testcontainers.containers.output.OutputFrame; + +import com.github.dockerjava.api.command.LogContainerCmd; + +import io.quarkus.devservices.deployment.DevServicesCommand.DevServiceCompleter; +import io.quarkus.devservices.runtime.devmode.DevServiceDescription; + +@CommandDefinition(name = "logs", description = "Print container logs", generateHelp = true) +public class DevServicesLogsCommand implements Command { + + @Argument(required = true, description = "Dev Service name", completer = DevServiceCompleter.class) + private String devService; + + @Option(name = "follow", shortName = 'f', description = "Follow container logs", hasValue = false, defaultValue = "false") + private boolean follow; + + @Option(name = "tail", shortName = 't', description = "Tail container logs", defaultValue = "-1") + private int tail; + + @Override + public CommandResult execute(CommandInvocation commandInvocation) { + Optional devService = findDevService(this.devService); + if (devService.isPresent()) { + DevServiceDescription desc = devService.get(); + try (FrameConsumerResultCallback resultCallback = new FrameConsumerResultCallback()) { + resultCallback.addConsumer(OutputFrame.OutputType.STDERR, + frame -> commandInvocation.print(frame.getUtf8String())); + resultCallback.addConsumer(OutputFrame.OutputType.STDOUT, + frame -> commandInvocation.print(frame.getUtf8String())); + LogContainerCmd logCmd = DockerClientFactory.lazyClient() + .logContainerCmd(desc.getContainerInfo().getId()) + .withFollowStream(follow) + .withTail(tail) + .withStdErr(true) + .withStdOut(true); + logCmd.exec(resultCallback); + + if (follow) { + commandInvocation.inputLine(); + } else { + resultCallback.awaitCompletion(); + } + } catch (InterruptedException | IOException e) { + // noop + } + return CommandResult.SUCCESS; + } else { + commandInvocation.println("Could not find dev service with name " + this.devService); + return CommandResult.FAILURE; + } + } +} diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java new file mode 100644 index 00000000000000..f641e91945474a --- /dev/null +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java @@ -0,0 +1,228 @@ +package io.quarkus.devservices.deployment; + +import static io.quarkus.deployment.dev.testing.MessageFormat.BOLD; +import static io.quarkus.deployment.dev.testing.MessageFormat.GREEN; +import static io.quarkus.deployment.dev.testing.MessageFormat.NO_BOLD; +import static io.quarkus.deployment.dev.testing.MessageFormat.NO_UNDERLINE; +import static io.quarkus.deployment.dev.testing.MessageFormat.RED; +import static io.quarkus.deployment.dev.testing.MessageFormat.RESET; +import static io.quarkus.deployment.dev.testing.MessageFormat.UNDERLINE; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.testcontainers.DockerClientFactory; + +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.ContainerNetwork; +import com.github.dockerjava.api.model.ContainerNetworkSettings; + +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.IsDockerWorking; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ConsoleCommandBuildItem; +import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleCommand; +import io.quarkus.deployment.console.ConsoleStateManager; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.dev.spi.DevModeType; +import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; +import io.quarkus.devservices.runtime.devmode.ContainerInfo; +import io.quarkus.devservices.runtime.devmode.DevServiceDescription; +import io.quarkus.devservices.runtime.devmode.DevServicesRecorder; + +public class DevServicesProcessor { + + private static final String EXEC_FORMAT = "docker exec -it %s /bin/bash"; + + private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); + + static volatile ConsoleStateManager.ConsoleContext context; + static volatile boolean logForwardEnabled = false; + static Map containerLogForwarders = new HashMap<>(); + + @BuildStep(onlyIf = { IsDevelopment.class }) + @Record(value = ExecutionTime.STATIC_INIT) + public void config(BuildProducer devConsoleRuntimeTemplateProducer, + BuildProducer commandBuildItemBuildProducer, + CurateOutcomeBuildItem curateOutcomeBuildItem, + LaunchModeBuildItem launchModeBuildItem, + DevServicesRecorder recorder, + Optional devServicesLauncherConfig, + List devServicesResults) { + List serviceDescriptions = buildServiceDescriptions(devServicesResults, + devServicesLauncherConfig); + + for (DevServiceDescription devService : serviceDescriptions) { + if (devService.hasContainerInfo()) { + containerLogForwarders.compute(devService.getContainerInfo().getId(), + (id, forwarder) -> Objects.requireNonNullElseGet(forwarder, + () -> new ContainerLogForwarder(devService))); + } + } + + devConsoleRuntimeTemplateProducer.produce(new DevConsoleRuntimeTemplateInfoBuildItem("devServices", + recorder.devServices(serviceDescriptions), this.getClass(), curateOutcomeBuildItem)); + + // Build commands if we are in local dev mode + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return; + } + + commandBuildItemBuildProducer.produce( + new ConsoleCommandBuildItem(new DevServicesCommand(serviceDescriptions))); + + if (context == null) { + context = ConsoleStateManager.INSTANCE.createContext("Dev Services"); + } + context.reset( + new ConsoleCommand('c', "Show dev services containers", null, () -> { + List descriptions = buildServiceDescriptions(devServicesResults, + devServicesLauncherConfig); + StringBuilder builder = new StringBuilder(); + builder.append("\n\n") + .append(RED + "==" + RESET + " " + UNDERLINE + "Dev Services" + NO_UNDERLINE) + .append("\n\n"); + for (DevServiceDescription devService : descriptions) { + printDevService(builder, devService, true); + builder.append("\n"); + } + System.out.println(builder); + }), + new ConsoleCommand('g', "Follow dev services logs to the console", + new ConsoleCommand.HelpState(() -> logForwardEnabled ? GREEN : RED, + () -> logForwardEnabled ? "enabled" : "disabled"), + this::toggleLogForwarders)); + } + + private List buildServiceDescriptions(List devServicesResults, + Optional devServicesLauncherConfig) { + // Fetch container infos + Set containerIds = devServicesResults.stream() + .map(DevServicesResultBuildItem::getContainerId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map containerInfos = fetchContainerInfos(containerIds); + // Build descriptions + Set configKeysFromDevServices = new HashSet<>(); + List descriptions = new ArrayList<>(); + for (DevServicesResultBuildItem buildItem : devServicesResults) { + configKeysFromDevServices.addAll(buildItem.getConfig().keySet()); + descriptions.add(toDevServiceDescription(buildItem, containerInfos.get(buildItem.getContainerId()))); + } + // Sort descriptions by name + descriptions.sort(Comparator.comparing(DevServiceDescription::getName)); + // Add description from other dev service configs as last + if (devServicesLauncherConfig.isPresent()) { + Map config = new HashMap<>(devServicesLauncherConfig.get().getConfig()); + for (String key : configKeysFromDevServices) { + config.remove(key); + } + if (!config.isEmpty()) { + descriptions.add(new DevServiceDescription("Other Dev Services", null, config)); + } + } + return descriptions; + } + + private Map fetchContainerInfos(Set containerIds) { + if (!isDockerWorking.getAsBoolean() || containerIds.isEmpty()) { + return Collections.emptyMap(); + } + return DockerClientFactory.lazyClient().listContainersCmd() + .withIdFilter(containerIds) + .withShowAll(true) + .exec() + .stream() + .collect(Collectors.toMap(Container::getId, Function.identity())); + } + + private DevServiceDescription toDevServiceDescription(DevServicesResultBuildItem buildItem, Container container) { + if (container == null) { + return new DevServiceDescription(buildItem.getName(), null, buildItem.getConfig()); + } else { + return new DevServiceDescription(buildItem.getName(), toContainerInfo(container), buildItem.getConfig()); + } + } + + private ContainerInfo toContainerInfo(Container container) { + return new ContainerInfo(container.getId(), container.getNames(), container.getImage(), + container.getStatus(), getNetworks(container), container.getLabels(), getExposedPorts(container)); + } + + private static String[] getNetworks(Container container) { + ContainerNetworkSettings networkSettings = container.getNetworkSettings(); + if (networkSettings == null) { + return null; + } + Map networks = networkSettings.getNetworks(); + if (networks == null) { + return null; + } + return networks.entrySet().stream() + .map(e -> { + List aliases = e.getValue().getAliases(); + if (aliases == null) { + return e.getKey(); + } + return e.getKey() + " (" + String.join(",", aliases) + ")"; + }) + .toArray(String[]::new); + } + + private ContainerInfo.ContainerPort[] getExposedPorts(Container container) { + return Arrays.stream(container.getPorts()) + .map(c -> new ContainerInfo.ContainerPort(c.getIp(), c.getPrivatePort(), c.getPublicPort(), c.getType())) + .toArray(ContainerInfo.ContainerPort[]::new); + } + + private synchronized void toggleLogForwarders() { + if (logForwardEnabled) { + for (ContainerLogForwarder logForwarder : containerLogForwarders.values()) { + if (logForwarder.isRunning()) { + logForwarder.close(); + } + } + logForwardEnabled = false; + } else { + for (ContainerLogForwarder logForwarder : containerLogForwarders.values()) { + logForwarder.start(); + } + logForwardEnabled = true; + } + } + + public static void printDevService(StringBuilder builder, DevServiceDescription devService, boolean withStatus) { + if (devService.hasContainerInfo()) { + builder.append(BOLD).append(devService.getName()).append(NO_BOLD); + if (withStatus) { + builder.append(" - ").append(devService.getContainerInfo().getStatus()); + } + builder.append("\n"); + builder.append(String.format(" %-18s", "Container: ")) + .append(devService.getContainerInfo().getId(), 0, 12) + .append(devService.getContainerInfo().formatNames()) + .append(" ") + .append(devService.getContainerInfo().getImageName()) + .append("\n"); + builder.append(String.format(" %-18s", "Network: ")) + .append(devService.getContainerInfo().formatNetworks()) + .append(" - ") + .append(devService.getContainerInfo().formatPorts()) + .append("\n"); + builder.append(String.format(" %-18s", "Exec command: ")) + .append(String.format(EXEC_FORMAT, devService.getContainerInfo().getShortId())) + .append("\n"); + } + builder.append(String.format(" %-18s", "Injected Config: ")) + .append(devService.formatConfigs()) + .append("\n"); + } + +} diff --git a/extensions/devservices/deployment/src/main/resources/dev-templates/dev-services.html b/extensions/devservices/deployment/src/main/resources/dev-templates/dev-services.html new file mode 100644 index 00000000000000..e5c7d12b8e7117 --- /dev/null +++ b/extensions/devservices/deployment/src/main/resources/dev-templates/dev-services.html @@ -0,0 +1,65 @@ +{#include main fluid=true} +{#style} +table.inner > tbody > tr:nth-of-type(odd) { + background: transparent; +} +table.inner > tbody > tr:nth-of-type(even) { + background: transparent; +} +table.inner > tbody > tr > td { + padding: 5px; +} +{/style} +{#title}Services{/title} +{#body} + + + + + + + + + {#for service in info:devServices} + + + + + {/for} + +
ServiceConfig
+
{service.name}
+
+ + + + + + + + + + + + + + + + + + + +
Container{service.containerInfo.getShortId()} {service.containerInfo.formatNames()}
Image{service.containerInfo.imageName}
Networks{service.containerInfo.formatNetworks()}
Exposed Ports{service.containerInfo.formatPorts()}
+
+
+
+ {#for config in service.configs} + + {config.key} + = {config.value} + +
+ {/for} +
+{/body} +{/include} \ No newline at end of file diff --git a/extensions/devservices/deployment/src/main/resources/dev-templates/embedded.html b/extensions/devservices/deployment/src/main/resources/dev-templates/embedded.html new file mode 100644 index 00000000000000..131785e370b098 --- /dev/null +++ b/extensions/devservices/deployment/src/main/resources/dev-templates/embedded.html @@ -0,0 +1,4 @@ + + + Dev Services {info:devServices.size()} + \ No newline at end of file diff --git a/extensions/devservices/derby/src/main/java/io/quarkus/devservices/derby/deployment/DerbyDevServicesProcessor.java b/extensions/devservices/derby/src/main/java/io/quarkus/devservices/derby/deployment/DerbyDevServicesProcessor.java index f76d2805a0ac94..4a2389fe066cf0 100644 --- a/extensions/devservices/derby/src/main/java/io/quarkus/devservices/derby/deployment/DerbyDevServicesProcessor.java +++ b/extensions/devservices/derby/src/main/java/io/quarkus/devservices/derby/deployment/DerbyDevServicesProcessor.java @@ -64,7 +64,7 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt additionalArgs.append("="); additionalArgs.append(i.getValue()); } - return new RunningDevServicesDatasource( + return new RunningDevServicesDatasource(null, "jdbc:derby://localhost:" + port + "/memory:" + datasourceName.orElse("quarkus") + ";create=true" + additionalArgs.toString(), null, diff --git a/extensions/devservices/h2/src/main/java/io/quarkus/devservices/h2/deployment/H2DevServicesProcessor.java b/extensions/devservices/h2/src/main/java/io/quarkus/devservices/h2/deployment/H2DevServicesProcessor.java index a1b1be5614a94a..1489eccd1af192 100644 --- a/extensions/devservices/h2/src/main/java/io/quarkus/devservices/h2/deployment/H2DevServicesProcessor.java +++ b/extensions/devservices/h2/src/main/java/io/quarkus/devservices/h2/deployment/H2DevServicesProcessor.java @@ -50,7 +50,7 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt String connectionUrl = "jdbc:h2:tcp://localhost:" + tcpServer.getPort() + "/mem:" + datasourceName.orElse("default") + ";DB_CLOSE_DELAY=-1" + additionalArgs.toString(); - return new RunningDevServicesDatasource( + return new RunningDevServicesDatasource(null, connectionUrl, "sa", "sa", diff --git a/extensions/devservices/mariadb/src/main/java/io/quarkus/devservices/mariadb/deployment/MariaDBDevServicesProcessor.java b/extensions/devservices/mariadb/src/main/java/io/quarkus/devservices/mariadb/deployment/MariaDBDevServicesProcessor.java index ba26d1e5d56753..b5fb5cd12b0a26 100644 --- a/extensions/devservices/mariadb/src/main/java/io/quarkus/devservices/mariadb/deployment/MariaDBDevServicesProcessor.java +++ b/extensions/devservices/mariadb/src/main/java/io/quarkus/devservices/mariadb/deployment/MariaDBDevServicesProcessor.java @@ -53,7 +53,9 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt LOG.info("Dev Services for MariaDB started."); - return new RunningDevServicesDatasource(container.getEffectiveJdbcUrl(), container.getUsername(), + return new RunningDevServicesDatasource(container.getContainerId(), + container.getEffectiveJdbcUrl(), + container.getUsername(), container.getPassword(), new Closeable() { @Override diff --git a/extensions/devservices/mssql/src/main/java/io/quarkus/devservices/mssql/deployment/MSSQLDevServicesProcessor.java b/extensions/devservices/mssql/src/main/java/io/quarkus/devservices/mssql/deployment/MSSQLDevServicesProcessor.java index f89a53b2e4bcb3..acb10195ee0719 100644 --- a/extensions/devservices/mssql/src/main/java/io/quarkus/devservices/mssql/deployment/MSSQLDevServicesProcessor.java +++ b/extensions/devservices/mssql/src/main/java/io/quarkus/devservices/mssql/deployment/MSSQLDevServicesProcessor.java @@ -47,7 +47,9 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt LOG.info("Dev Services for Microsoft SQL Server started."); - return new RunningDevServicesDatasource(container.getEffectiveJdbcUrl(), container.getUsername(), + return new RunningDevServicesDatasource(container.getContainerId(), + container.getEffectiveJdbcUrl(), + container.getUsername(), container.getPassword(), new Closeable() { @Override diff --git a/extensions/devservices/mysql/src/main/java/io/quarkus/devservices/mysql/deployment/MySQLDevServicesProcessor.java b/extensions/devservices/mysql/src/main/java/io/quarkus/devservices/mysql/deployment/MySQLDevServicesProcessor.java index 2e6c37c5cfebeb..a84785c805ad14 100644 --- a/extensions/devservices/mysql/src/main/java/io/quarkus/devservices/mysql/deployment/MySQLDevServicesProcessor.java +++ b/extensions/devservices/mysql/src/main/java/io/quarkus/devservices/mysql/deployment/MySQLDevServicesProcessor.java @@ -53,7 +53,9 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt LOG.info("Dev Services for MySQL started."); - return new RunningDevServicesDatasource(container.getEffectiveJdbcUrl(), container.getUsername(), + return new RunningDevServicesDatasource(container.getContainerId(), + container.getEffectiveJdbcUrl(), + container.getUsername(), container.getPassword(), new Closeable() { @Override diff --git a/extensions/devservices/oracle/src/main/java/io/quarkus/devservices/oracle/deployment/OracleDevServicesProcessor.java b/extensions/devservices/oracle/src/main/java/io/quarkus/devservices/oracle/deployment/OracleDevServicesProcessor.java index 90836d4aa4e8e5..b8a6f2f569b379 100644 --- a/extensions/devservices/oracle/src/main/java/io/quarkus/devservices/oracle/deployment/OracleDevServicesProcessor.java +++ b/extensions/devservices/oracle/src/main/java/io/quarkus/devservices/oracle/deployment/OracleDevServicesProcessor.java @@ -51,7 +51,9 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt LOG.info("Dev Services for Oracle started."); - return new RunningDevServicesDatasource(container.getEffectiveJdbcUrl(), container.getUsername(), + return new RunningDevServicesDatasource(container.getContainerId(), + container.getEffectiveJdbcUrl(), + container.getUsername(), container.getPassword(), new Closeable() { @Override diff --git a/extensions/devservices/pom.xml b/extensions/devservices/pom.xml index 7dc1df2431f3ac..426e5c7e37a189 100644 --- a/extensions/devservices/pom.xml +++ b/extensions/devservices/pom.xml @@ -27,6 +27,8 @@ db2 oracle common + deployment + runtime diff --git a/extensions/devservices/postgresql/src/main/java/io/quarkus/devservices/postgresql/deployment/PostgresqlDevServicesProcessor.java b/extensions/devservices/postgresql/src/main/java/io/quarkus/devservices/postgresql/deployment/PostgresqlDevServicesProcessor.java index 72ca1ae477c348..12c8afe023c6cc 100644 --- a/extensions/devservices/postgresql/src/main/java/io/quarkus/devservices/postgresql/deployment/PostgresqlDevServicesProcessor.java +++ b/extensions/devservices/postgresql/src/main/java/io/quarkus/devservices/postgresql/deployment/PostgresqlDevServicesProcessor.java @@ -54,7 +54,9 @@ public RunningDevServicesDatasource startDatabase(Optional username, Opt LOG.info("Dev Services for PostgreSQL started."); - return new RunningDevServicesDatasource(container.getEffectiveJdbcUrl(), container.getUsername(), + return new RunningDevServicesDatasource(container.getContainerId(), + container.getEffectiveJdbcUrl(), + container.getUsername(), container.getPassword(), new Closeable() { @Override diff --git a/extensions/devservices/runtime/pom.xml b/extensions/devservices/runtime/pom.xml new file mode 100644 index 00000000000000..4b8ab7d3c1d44e --- /dev/null +++ b/extensions/devservices/runtime/pom.xml @@ -0,0 +1,43 @@ + + + + quarkus-devservices-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devservices + Quarkus - DevServices - Runtime + Automatic provisioning of unconfigured services + + + + io.quarkus + quarkus-arc + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/ContainerInfo.java b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/ContainerInfo.java new file mode 100644 index 00000000000000..7f5a8ca991ee9b --- /dev/null +++ b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/ContainerInfo.java @@ -0,0 +1,154 @@ +package io.quarkus.devservices.runtime.devmode; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class ContainerInfo { + + private String id; + private String[] names; + private String imageName; + private String status; + private String[] networks; + private Map labels; + private ContainerPort[] exposedPorts; + + public ContainerInfo() { + } + + public ContainerInfo(String id, String[] names, String imageName, String status, String[] networks, + Map labels, ContainerPort[] exposedPorts) { + this.id = id; + this.names = names; + this.imageName = imageName; + this.status = status; + this.networks = networks; + this.labels = labels; + this.exposedPorts = exposedPorts; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getShortId() { + return id.substring(0, 12); + } + + public String[] getNames() { + return names; + } + + public void setNames(String[] names) { + this.names = names; + } + + public String formatNames() { + return String.join(",", getNames()); + } + + public String getImageName() { + return imageName; + } + + public void setImageName(String imageName) { + this.imageName = imageName; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String[] getNetworks() { + return networks; + } + + public void setNetworks(String[] networks) { + this.networks = networks; + } + + public String formatNetworks() { + return String.join(",", getNetworks()); + } + + public Map getLabels() { + return labels; + } + + public void setLabels(Map labels) { + this.labels = labels; + } + + public ContainerPort[] getExposedPorts() { + return exposedPorts; + } + + public void setExposedPorts(ContainerPort[] exposedPorts) { + this.exposedPorts = exposedPorts; + } + + public String formatPorts() { + return Arrays.stream(getExposedPorts()) + .filter(p -> p.getPublicPort() != null) + .map(c -> c.getIp() + ":" + c.getPublicPort() + "->" + c.getPrivatePort() + "/" + c.getType()) + .collect(Collectors.joining(" ,")); + } + + public static class ContainerPort { + private String ip; + private Integer privatePort; + private Integer publicPort; + private String type; + + public ContainerPort() { + } + + public ContainerPort(String ip, Integer privatePort, Integer publicPort, String type) { + this.ip = ip; + this.privatePort = privatePort; + this.publicPort = publicPort; + this.type = type; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public Integer getPrivatePort() { + return privatePort; + } + + public void setPrivatePort(Integer privatePort) { + this.privatePort = privatePort; + } + + public Integer getPublicPort() { + return publicPort; + } + + public void setPublicPort(Integer publicPort) { + this.publicPort = publicPort; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } +} diff --git a/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServiceDescription.java b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServiceDescription.java new file mode 100644 index 00000000000000..c5105aa36a8ea6 --- /dev/null +++ b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServiceDescription.java @@ -0,0 +1,53 @@ +package io.quarkus.devservices.runtime.devmode; + +import java.util.Map; +import java.util.stream.Collectors; + +public class DevServiceDescription { + private String name; + private ContainerInfo containerInfo; + private Map configs; + + public DevServiceDescription() { + } + + public DevServiceDescription(String name, ContainerInfo containerInfo, Map configs) { + this.name = name; + this.containerInfo = containerInfo; + this.configs = configs; + } + + public boolean hasContainerInfo() { + return containerInfo != null; + } + + public String getName() { + return name; + } + + public ContainerInfo getContainerInfo() { + return containerInfo; + } + + public Map getConfigs() { + return configs; + } + + public void setName(String name) { + this.name = name; + } + + public void setContainerInfo(ContainerInfo containerInfo) { + this.containerInfo = containerInfo; + } + + public void setConfigs(Map configs) { + this.configs = configs; + } + + public String formatConfigs() { + return configs.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining(", ")); + } +} diff --git a/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServices.java b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServices.java new file mode 100644 index 00000000000000..b48390b2f9e4db --- /dev/null +++ b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServices.java @@ -0,0 +1,25 @@ +package io.quarkus.devservices.runtime.devmode; + +import java.util.List; +import java.util.function.Supplier; + +public class DevServices implements Supplier> { + + private List devServices; + + public DevServices() { + } + + public DevServices(List devServices) { + this.devServices = devServices; + } + + public List getDevServices() { + return devServices; + } + + @Override + public List get() { + return devServices; + } +} diff --git a/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServicesRecorder.java b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServicesRecorder.java new file mode 100644 index 00000000000000..bafffa85a87c8a --- /dev/null +++ b/extensions/devservices/runtime/src/main/java/io/quarkus/devservices/runtime/devmode/DevServicesRecorder.java @@ -0,0 +1,14 @@ +package io.quarkus.devservices.runtime.devmode; + +import java.util.List; + +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class DevServicesRecorder { + + public DevServices devServices(List devServices) { + return new DevServices(devServices); + } + +} diff --git a/extensions/devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000000000..7373e2829c546b --- /dev/null +++ b/extensions/devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Dev Services" +metadata: + guide: "https://quarkus.io/guides/dev-services" + status: "stable" + unlisted: true + config: + - "quarkus.devservices." diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index 35ec24fce9010c..46f03935ec9639 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -44,7 +44,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java index eecefb279b4cc1..9a55fc0593ab6e 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java @@ -1,25 +1,31 @@ package io.quarkus.infinispan.client.deployment.devservices; import static io.quarkus.runtime.LaunchMode.DEVELOPMENT; +import static org.infinispan.server.test.core.InfinispanContainer.DEFAULT_USERNAME; import java.io.Closeable; import java.time.Duration; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.infinispan.client.hotrod.impl.ConfigurationProperties; import org.infinispan.server.test.core.InfinispanContainer; import org.jboss.logging.Logger; +import org.jetbrains.annotations.NotNull; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; @@ -47,75 +53,68 @@ public class InfinispanDevServiceProcessor { private static final String DEFAULT_PASSWORD = "password"; private static final String QUARKUS = "quarkus."; private static final String DOT = "."; - private static volatile List closeables; + private static volatile List devServices; private static volatile InfinispanClientDevServiceBuildTimeConfig.DevServiceConfiguration capturedDevServicesConfiguration; private static volatile boolean first = true; private static volatile Boolean dockerRunning = null; @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class }) - public void startInfinispanContainers(LaunchModeBuildItem launchMode, + public List startInfinispanContainers(LaunchModeBuildItem launchMode, List devServicesSharedNetworkBuildItem, - BuildProducer devConfigProducer, InfinispanClientDevServiceBuildTimeConfig config, + InfinispanClientDevServiceBuildTimeConfig config, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) { // figure out if we need to shut down and restart existing Infinispan containers // if not and the Infinispan containers have already started we just return - if (closeables != null) { + if (devServices != null) { boolean restartRequired = !config.devService.equals(capturedDevServicesConfiguration); if (!restartRequired) { - return; + return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - for (Closeable closeable : closeables) { + for (Closeable closeable : devServices) { try { closeable.close(); } catch (Throwable e) { log.error("Failed to stop infinispan container", e); } } - closeables = null; + devServices = null; capturedDevServicesConfiguration = null; } capturedDevServicesConfiguration = config.devService; - List currentCloseables = new ArrayList<>(); + List newDevServices = new ArrayList<>(); StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "Infinispan Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); try { - StartResult startResult = startContainer(config.devService.devservices, + RunningDevService devService = startContainer(config.devService.devservices, launchMode.getLaunchMode(), !devServicesSharedNetworkBuildItem.isEmpty(), devServicesConfig.timeout); - if (startResult == null) { + if (devService == null) { compressor.close(); - return; + return null; } - currentCloseables.add(startResult.closeable); - - devConfigProducer - .produce(new DevServicesConfigResultBuildItem(getConfigPrefix() + "server-list", startResult.serverList)); - devConfigProducer.produce(new DevServicesConfigResultBuildItem(getConfigPrefix() + "client-intelligence", "BASIC")); - devConfigProducer - .produce(new DevServicesConfigResultBuildItem(getConfigPrefix() + "auth-username", startResult.user)); - devConfigProducer - .produce(new DevServicesConfigResultBuildItem(getConfigPrefix() + "auth-password", startResult.password)); - log.infof("The infinispan server is ready to accept connections on %s", startResult.serverList); + newDevServices.add(devService); + log.infof("The infinispan server is ready to accept connections on %s", + devService.getConfig().get(getConfigPrefix() + "server-list")); compressor.close(); } catch (Throwable t) { compressor.closeAndDumpCaptured(); throw new RuntimeException(t); } - closeables = currentCloseables; + devServices = newDevServices; if (first) { first = false; Runnable closeTask = () -> { dockerRunning = null; - if (closeables != null) { - for (Closeable closeable : closeables) { + if (devServices != null) { + for (Closeable closeable : devServices) { try { closeable.close(); } catch (Throwable t) { @@ -124,14 +123,15 @@ public void startInfinispanContainers(LaunchModeBuildItem launchMode, } } first = true; - closeables = null; + devServices = null; capturedDevServicesConfiguration = null; }; closeBuildItem.addCloseTask(closeTask, true); } + return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - private StartResult startContainer( + private RunningDevService startContainer( InfinispanDevServicesConfig devServicesConfig, LaunchMode launchMode, boolean useSharedNetwork, Optional timeout) { if (!devServicesConfig.enabled) { @@ -157,43 +157,39 @@ private StartResult startContainer( return null; } - Supplier defaultInfinispanServerSupplier = () -> { + Supplier defaultInfinispanServerSupplier = () -> { QuarkusInfinispanContainer infinispanContainer = new QuarkusInfinispanContainer(devServicesConfig.port, launchMode == DEVELOPMENT ? devServicesConfig.serviceName : null, useSharedNetwork, devServicesConfig.artifacts); timeout.ifPresent(infinispanContainer::withStartupTimeout); infinispanContainer.start(); - String serverList = infinispanContainer.getHost() + ":" + infinispanContainer.getPort(); - String user = infinispanContainer.getUser(); - String secret = infinispanContainer.getPassword(); - return new StartResult(serverList, user, secret, () -> infinispanContainer.close()); + + return getRunningDevService(infinispanContainer.getContainerId(), infinispanContainer::close, + infinispanContainer.getHost() + ":" + infinispanContainer.getPort(), + infinispanContainer.getUser(), infinispanContainer.getPassword()); }; return infinispanContainerLocator.locateContainer(devServicesConfig.serviceName, devServicesConfig.shared, launchMode) - .map(containerAddress -> new StartResult(containerAddress.getUrl(), - InfinispanContainer.DEFAULT_USERNAME, DEFAULT_PASSWORD, null)) + .map(containerAddress -> getRunningDevService(containerAddress.getId(), null, + containerAddress.getUrl(), DEFAULT_USERNAME, DEFAULT_PASSWORD)) // TODO can this be always right ? .orElseGet(defaultInfinispanServerSupplier); + } + @NotNull + private RunningDevService getRunningDevService(String containerId, Closeable closeable, String serverList, + String username, String password) { + Map config = new HashMap<>(); + config.put(getConfigPrefix() + "server-list", serverList); + config.put(getConfigPrefix() + "client-intelligence", "BASIC"); + config.put(getConfigPrefix() + "auth-username", username); + config.put(getConfigPrefix() + "auth-password", password); + return new RunningDevService(Feature.INFINISPAN_CLIENT.getName(), containerId, closeable, config); } private String getConfigPrefix() { return QUARKUS + "infinispan-client" + DOT; } - private static class StartResult { - private final String serverList; - private final String user; - private final String password; - private final Closeable closeable; - - public StartResult(String sl, String user, String password, Closeable closeable) { - this.serverList = sl; - this.user = user; - this.password = password; - this.closeable = closeable; - } - } - private static class QuarkusInfinispanContainer extends InfinispanContainer { private final OptionalInt fixedExposedPort; private final boolean useSharedNetwork; @@ -241,7 +237,7 @@ public int getPort() { } public String getUser() { - return InfinispanContainer.DEFAULT_USERNAME; + return DEFAULT_USERNAME; } public String getPassword() { diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml index 516d233cbf4a51..98fee6a80057b4 100644 --- a/extensions/infinispan-client/runtime/pom.xml +++ b/extensions/infinispan-client/runtime/pom.xml @@ -115,6 +115,10 @@ svm provided + + io.quarkus + quarkus-devservices + diff --git a/extensions/kafka-client/deployment/pom.xml b/extensions/kafka-client/deployment/pom.xml index 70121b9657b4ef..07e5d1d7f809f8 100644 --- a/extensions/kafka-client/deployment/pom.xml +++ b/extensions/kafka-client/deployment/pom.xml @@ -60,7 +60,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaBrokerBuildItem.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaBrokerBuildItem.java deleted file mode 100644 index 9889c7c577e104..00000000000000 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaBrokerBuildItem.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.kafka.client.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; - -public final class DevServicesKafkaBrokerBuildItem extends SimpleBuildItem { - - final String bootstrapServers; - - public DevServicesKafkaBrokerBuildItem(String bs) { - this.bootstrapServers = bs; - } - - public String getBootstrapServers() { - return bootstrapServers; - } - -} diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java index dd0bd4dbab355e..f812f1ea04dfcb 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.kafka.client.deployment; -import java.io.Closeable; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; @@ -32,12 +31,13 @@ import com.github.dockerjava.api.command.InspectContainerResponse; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; @@ -67,82 +67,77 @@ public class DevServicesKafkaProcessor { private static final ContainerLocator kafkaContainerLocator = new ContainerLocator(DEV_SERVICE_LABEL, KAFKA_PORT); - static volatile Closeable closeable; + static volatile RunningDevService devService; static volatile KafkaDevServiceCfg cfg; static volatile boolean first = true; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - public DevServicesKafkaBrokerBuildItem startKafkaDevService( + public DevServicesResultBuildItem startKafkaDevService( LaunchModeBuildItem launchMode, KafkaBuildTimeConfig kafkaClientBuildTimeConfig, List devServicesSharedNetworkBuildItem, - BuildProducer devServicePropertiesProducer, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) { KafkaDevServiceCfg configuration = getConfiguration(kafkaClientBuildTimeConfig); - if (closeable != null) { + if (devService != null) { boolean shouldShutdownTheBroker = !configuration.equals(cfg); if (!shouldShutdownTheBroker) { - return null; + return devService.toBuildItem(); } shutdownBroker(); cfg = null; } - KafkaBroker kafkaBroker; - DevServicesKafkaBrokerBuildItem bootstrapServers; + StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "Kafka Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); try { - - kafkaBroker = startKafka(configuration, launchMode, + devService = startKafka(configuration, launchMode, !devServicesSharedNetworkBuildItem.isEmpty(), devServicesConfig.timeout); - bootstrapServers = null; - if (kafkaBroker != null) { - closeable = kafkaBroker.getCloseable(); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem( - KAFKA_BOOTSTRAP_SERVERS, kafkaBroker.getBootstrapServers())); - bootstrapServers = new DevServicesKafkaBrokerBuildItem(kafkaBroker.getBootstrapServers()); - } compressor.close(); } catch (Throwable t) { compressor.closeAndDumpCaptured(); throw new RuntimeException(t); } + if (devService == null) { + return null; + } + // Configure the watch dog if (first) { first = false; Runnable closeTask = () -> { - if (closeable != null) { + if (devService != null) { shutdownBroker(); } first = true; - closeable = null; + devService = null; cfg = null; }; closeBuildItem.addCloseTask(closeTask, true); } cfg = configuration; - if (bootstrapServers != null) { - if (kafkaBroker.isOwner()) { - log.infof( - "Dev Services for Kafka started. Other Quarkus applications in dev mode will find the " - + "broker automatically. For Quarkus applications in production mode, you can connect to" - + " this by starting your application with -Dkafka.bootstrap.servers=%s", - bootstrapServers.getBootstrapServers()); - } - createTopicPartitions(bootstrapServers.getBootstrapServers(), configuration); + if (devService.isOwner()) { + log.infof( + "Dev Services for Kafka started. Other Quarkus applications in dev mode will find the " + + "broker automatically. For Quarkus applications in production mode, you can connect to" + + " this by starting your application with -Dkafka.bootstrap.servers=%s", + getKafkaBootstrapServers()); } + createTopicPartitions(getKafkaBootstrapServers(), configuration); + return devService.toBuildItem(); + } - return bootstrapServers; + public static String getKafkaBootstrapServers() { + return devService.getConfig().get(KAFKA_BOOTSTRAP_SERVERS); } public void createTopicPartitions(String bootstrapServers, KafkaDevServiceCfg configuration) { @@ -188,18 +183,18 @@ public void createTopicPartitions(String bootstrapServers, KafkaDevServiceCfg co } private void shutdownBroker() { - if (closeable != null) { + if (devService != null) { try { - closeable.close(); + devService.close(); } catch (Throwable e) { log.error("Failed to stop the Kafka broker", e); } finally { - closeable = null; + devService = null; } } } - private KafkaBroker startKafka(KafkaDevServiceCfg config, + private RunningDevService startKafka(KafkaDevServiceCfg config, LaunchModeBuildItem launchMode, boolean useSharedNetwork, Optional timeout) { if (!config.devServicesEnabled) { // explicitly disabled @@ -230,7 +225,7 @@ private KafkaBroker startKafka(KafkaDevServiceCfg config, launchMode.getLaunchMode()); // Starting the broker - final Supplier defaultKafkaBrokerSupplier = () -> { + final Supplier defaultKafkaBrokerSupplier = () -> { RedPandaKafkaContainer container = new RedPandaKafkaContainer( DockerImageName.parse(config.imageName), config.fixedExposedPort, @@ -239,12 +234,17 @@ private KafkaBroker startKafka(KafkaDevServiceCfg config, timeout.ifPresent(container::withStartupTimeout); container.start(); - return new KafkaBroker( - container.getBootstrapServers(), - container::close); + return new RunningDevService(Feature.KAFKA_CLIENT.getName(), + container.getContainerId(), + container::close, + KAFKA_BOOTSTRAP_SERVERS, container.getBootstrapServers()); }; - return maybeContainerAddress.map(containerAddress -> new KafkaBroker(containerAddress.getUrl(), null)) + return maybeContainerAddress + .map(containerAddress -> new RunningDevService(Feature.KAFKA_CLIENT.getName(), + containerAddress.getId(), + null, + KAFKA_BOOTSTRAP_SERVERS, containerAddress.getUrl())) .orElseGet(defaultKafkaBrokerSupplier); } @@ -272,28 +272,6 @@ private KafkaDevServiceCfg getConfiguration(KafkaBuildTimeConfig cfg) { return new KafkaDevServiceCfg(devServicesConfig); } - private static class KafkaBroker { - private final String url; - private final Closeable closeable; - - public KafkaBroker(String url, Closeable closeable) { - this.url = url; - this.closeable = closeable; - } - - public boolean isOwner() { - return closeable != null; - } - - public String getBootstrapServers() { - return url; - } - - public Closeable getCloseable() { - return closeable; - } - } - private static final class KafkaDevServiceCfg { private final boolean devServicesEnabled; private final String imageName; diff --git a/extensions/kafka-client/runtime/pom.xml b/extensions/kafka-client/runtime/pom.xml index bfd47cc34e16b3..3b0be5455b5519 100644 --- a/extensions/kafka-client/runtime/pom.xml +++ b/extensions/kafka-client/runtime/pom.xml @@ -18,6 +18,10 @@ io.quarkus quarkus-arc + + io.quarkus + quarkus-devservices + io.quarkus quarkus-jsonb diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index 4754d275759196..d360655d4af4bf 100644 --- a/extensions/mongodb-client/deployment/pom.xml +++ b/extensions/mongodb-client/deployment/pom.xml @@ -50,7 +50,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment io.quarkus diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java index d7b2ab60da9590..48b86d6db3d5c4 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java @@ -21,12 +21,13 @@ import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.message.BasicNameValuePair; import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.net.URLEncodedUtils; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; @@ -41,17 +42,16 @@ public class DevServicesMongoProcessor { private static final Logger log = Logger.getLogger(DevServicesMongoProcessor.class); - static volatile List closeables; + static volatile List devServices; static volatile Map capturedProperties; static volatile boolean first = true; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - public void startMongo(List mongoConnections, + public List startMongo(List mongoConnections, MongoClientBuildTimeConfig mongoClientBuildTimeConfig, List devServicesSharedNetworkBuildItem, - BuildProducer devServices, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LaunchModeBuildItem launchMode, @@ -65,10 +65,10 @@ public void startMongo(List mongoConnections, // TODO: handle named connections as well if (connectionNames.size() != 1) { - return; + return null; } if (!isDefault(connectionNames.get(0))) { - return; + return null; } Map currentCapturedProperties = captureProperties(connectionNames, @@ -76,44 +76,40 @@ public void startMongo(List mongoConnections, //figure out if we need to shut down and restart existing databases //if not and the DB's have already started we just return - if (closeables != null) { + if (devServices != null) { boolean restartRequired = !currentCapturedProperties.equals(capturedProperties); if (!restartRequired) { - return; + return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - for (Closeable i : closeables) { + for (Closeable i : devServices) { try { i.close(); } catch (Throwable e) { log.error("Failed to stop database", e); } } - closeables = null; + devServices = null; capturedProperties = null; } - List currentCloseables = new ArrayList<>(mongoConnections.size()); + List newDevServices = new ArrayList<>(mongoConnections.size()); // TODO: we need to go through each connection String connectionName = connectionNames.get(0); - StartResult startResult; + RunningDevService devService; StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "Mongo Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); try { - startResult = startMongo(connectionName, currentCapturedProperties.get(connectionName), + devService = startMongo(connectionName, currentCapturedProperties.get(connectionName), !devServicesSharedNetworkBuildItem.isEmpty(), globalDevServicesConfig.timeout); compressor.close(); } catch (Throwable t) { compressor.closeAndDumpCaptured(); throw new RuntimeException(t); } - if (startResult != null) { - currentCloseables.add(startResult.getCloseable()); - String connectionStringPropertyName = getConfigPrefix(connectionName) + "connection-string"; - String connectionStringPropertyValue = startResult.getUrl(); - devServices.produce( - new DevServicesConfigResultBuildItem(connectionStringPropertyName, connectionStringPropertyValue)); + if (devService != null) { + newDevServices.add(devService); } if (first) { @@ -121,8 +117,8 @@ public void startMongo(List mongoConnections, Runnable closeTask = new Runnable() { @Override public void run() { - if (closeables != null) { - for (Closeable i : closeables) { + if (devServices != null) { + for (Closeable i : devServices) { try { i.close(); } catch (Throwable t) { @@ -131,18 +127,18 @@ public void run() { } } first = true; - closeables = null; + devServices = null; capturedProperties = null; } }; closeBuildItem.addCloseTask(closeTask, true); } - closeables = currentCloseables; + devServices = newDevServices; capturedProperties = currentCapturedProperties; - + return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - private StartResult startMongo(String connectionName, CapturedProperties capturedProperties, boolean useSharedNetwork, + private RunningDevService startMongo(String connectionName, CapturedProperties capturedProperties, boolean useSharedNetwork, Optional timeout) { if (!capturedProperties.devServicesEnabled) { // explicitly disabled @@ -187,14 +183,8 @@ private StartResult startMongo(String connectionName, CapturedProperties capture .map(e -> new BasicNameValuePair(e.getKey(), e.getValue())).collect(Collectors.toList()), StandardCharsets.UTF_8); } - return new StartResult( - effectiveURL, - new Closeable() { - @Override - public void close() { - mongoDBContainer.close(); - } - }); + return new RunningDevService(Feature.MONGODB_CLIENT.getName(), mongoDBContainer.getContainerId(), + mongoDBContainer::close, getConfigPrefix(connectionName) + "connection-string", effectiveURL); } private String getConfigPrefix(String connectionName) { @@ -226,24 +216,6 @@ private CapturedProperties captureProperties(String connectionName, MongoClientB devServicesConfig.imageName.orElse(null), devServicesConfig.port.orElse(null), devServicesConfig.properties); } - private static class StartResult { - private final String url; - private final Closeable closeable; - - public StartResult(String url, Closeable closeable) { - this.url = url; - this.closeable = closeable; - } - - public String getUrl() { - return url; - } - - public Closeable getCloseable() { - return closeable; - } - } - private static final class CapturedProperties { private final String database; private final String connectionString; diff --git a/extensions/mongodb-client/runtime/pom.xml b/extensions/mongodb-client/runtime/pom.xml index 2abb171cfbcac8..781ceaf4e53809 100644 --- a/extensions/mongodb-client/runtime/pom.xml +++ b/extensions/mongodb-client/runtime/pom.xml @@ -28,6 +28,10 @@ io.quarkus quarkus-vertx + + io.quarkus + quarkus-devservices + io.quarkus quarkus-mutiny-reactive-streams-operators diff --git a/extensions/oidc-client-filter/deployment/pom.xml b/extensions/oidc-client-filter/deployment/pom.xml index 53288fad786997..798226c3af6728 100644 --- a/extensions/oidc-client-filter/deployment/pom.xml +++ b/extensions/oidc-client-filter/deployment/pom.xml @@ -46,6 +46,12 @@ io.quarkus quarkus-test-keycloak-server test + + + junit + junit + + io.quarkus diff --git a/extensions/oidc-client/deployment/pom.xml b/extensions/oidc-client/deployment/pom.xml index 1ff773f65cedb0..2e44f29029b7c8 100644 --- a/extensions/oidc-client/deployment/pom.xml +++ b/extensions/oidc-client/deployment/pom.xml @@ -34,6 +34,10 @@ io.quarkus quarkus-oidc-common-deployment + + io.quarkus + quarkus-devservices-deployment + io.quarkus diff --git a/extensions/oidc-client/runtime/pom.xml b/extensions/oidc-client/runtime/pom.xml index 5579916875b83a..4b197b04a7e473 100644 --- a/extensions/oidc-client/runtime/pom.xml +++ b/extensions/oidc-client/runtime/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-oidc-common + + io.quarkus + quarkus-devservices + io.quarkus quarkus-junit5-internal diff --git a/extensions/oidc/deployment/pom.xml b/extensions/oidc/deployment/pom.xml index 12eb9289d154a7..ff5393df036d1b 100644 --- a/extensions/oidc/deployment/pom.xml +++ b/extensions/oidc/deployment/pom.xml @@ -37,6 +37,10 @@ io.quarkus quarkus-security-deployment + + io.quarkus + quarkus-devservices-deployment + io.quarkus quarkus-jsonp-deployment @@ -61,7 +65,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index 4c0b520e687618..03ba6fb0fb0ee9 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.oidc.deployment.devservices.keycloak; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -14,7 +13,6 @@ import java.nio.file.attribute.FileTime; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -39,12 +37,13 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; @@ -66,7 +65,6 @@ import io.vertx.mutiny.ext.web.client.WebClient; public class KeycloakDevServicesProcessor { - static volatile DevServicesConfig capturedDevServicesConfiguration; static volatile Vertx vertxInstance; private static final Logger LOG = Logger.getLogger(KeycloakDevServicesProcessor.class); @@ -115,18 +113,15 @@ public class KeycloakDevServicesProcessor { private static final ContainerLocator keycloakDevModeContainerLocator = new ContainerLocator(DEV_SERVICE_LABEL, KEYCLOAK_PORT); - private static volatile List closeables; + private static volatile RunningDevService devService; + static volatile DevServicesConfig capturedDevServicesConfiguration; private static volatile boolean first = true; - private static volatile String capturedKeycloakInternalURL; - private static volatile String capturedKeycloakHostURL; private static volatile FileTime capturedRealmFileLastModifiedDate; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); - private static volatile KeycloakDevServicesConfigBuildItem existingDevServiceConfig; @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { IsEnabled.class, GlobalDevServicesConfig.Enabled.class }) - public KeycloakDevServicesConfigBuildItem startKeycloakContainer( + public DevServicesResultBuildItem startKeycloakContainer( List devServicesSharedNetworkBuildItem, - BuildProducer devServices, Optional oidcProviderBuildItem, KeycloakBuildTimeConfig config, CuratedApplicationShutdownBuildItem closeBuildItem, @@ -143,7 +138,7 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer( DevServicesConfig currentDevServicesConfiguration = config.devservices; // Figure out if we need to shut down and restart any existing Keycloak container // if not and the Keycloak container has already started we just return - if (closeables != null) { + if (devService != null) { boolean restartRequired = !currentDevServicesConfiguration.equals(capturedDevServicesConfiguration); if (!restartRequired) { FileTime currentRealmFileLastModifiedDate = getRealmFileLastModifiedDate( @@ -155,46 +150,43 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer( } } if (!restartRequired) { - return existingDevServiceConfig; + return devService.toBuildItem(); } - for (Closeable closeable : closeables) { - try { - closeable.close(); - } catch (Throwable e) { - LOG.error("Failed to stop Keycloak container", e); - } + try { + devService.close(); + } catch (Throwable e) { + LOG.error("Failed to stop Keycloak container", e); } - closeables = null; + devService = null; capturedDevServicesConfiguration = null; - capturedKeycloakInternalURL = null; - existingDevServiceConfig = null; } capturedDevServicesConfiguration = currentDevServicesConfiguration; - StartResult startResult; StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "KeyCloak Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); + if (vertxInstance == null) { + vertxInstance = Vertx.vertx(); + } try { - startResult = startContainer(!devServicesSharedNetworkBuildItem.isEmpty(), devServicesConfig.timeout); - if (startResult == null) { + RunningDevService newDevService = startContainer(!devServicesSharedNetworkBuildItem.isEmpty(), + devServicesConfig.timeout); + if (newDevService == null) { compressor.close(); return null; } - closeables = startResult.closeable != null ? Collections.singletonList(startResult.closeable) : null; + devService = newDevService; if (first) { first = false; Runnable closeTask = new Runnable() { @Override public void run() { - if (closeables != null) { - for (Closeable closeable : closeables) { - try { - closeable.close(); - } catch (Throwable t) { - LOG.error("Failed to stop Keycloak container", t); - } + if (devService != null) { + try { + devService.close(); + } catch (Throwable t) { + LOG.error("Failed to stop Keycloak container", t); } } if (vertxInstance != null) { @@ -205,7 +197,7 @@ public void run() { } } first = true; - closeables = null; + devService = null; capturedDevServicesConfiguration = null; vertxInstance = null; capturedRealmFileLastModifiedDate = null; @@ -214,11 +206,6 @@ public void run() { closeBuildItem.addCloseTask(closeTask, true); } - capturedKeycloakInternalURL = startResult.internalURL; - capturedKeycloakHostURL = startResult.hostURL; - if (vertxInstance == null) { - vertxInstance = Vertx.vertx(); - } capturedRealmFileLastModifiedDate = getRealmFileLastModifiedDate(capturedDevServicesConfiguration.realmPath); compressor.close(); } catch (Throwable t) { @@ -227,20 +214,19 @@ public void run() { } LOG.info("Dev Services for Keycloak started."); - return prepareConfiguration(startResult.realmRep, startResult.keycloakX, devServices); + return devService.toBuildItem(); } private String startURL(String host, Integer port, boolean isKeyCloakX) { return "http://" + host + ":" + port + (isKeyCloakX ? "" : "/auth"); } - private KeycloakDevServicesConfigBuildItem prepareConfiguration(RealmRepresentation realmRep, boolean keycloakX, - BuildProducer devServices) { + private Map prepareConfiguration(String internalURL, String hostURL, RealmRepresentation realmRep, + boolean keycloakX) { final String realmName = realmRep != null ? realmRep.getRealm() : getDefaultRealmName(); - final String authServerInternalUrl = realmsURL(capturedKeycloakInternalURL, realmName); + final String authServerInternalUrl = realmsURL(internalURL, realmName); - String clientAuthServerBaseUrl = capturedKeycloakHostURL != null ? capturedKeycloakHostURL - : capturedKeycloakInternalURL; + String clientAuthServerBaseUrl = hostURL != null ? hostURL : internalURL; String clientAuthServerUrl = realmsURL(clientAuthServerBaseUrl, realmName); String oidcClientId = getOidcClientId(); @@ -255,24 +241,15 @@ private KeycloakDevServicesConfigBuildItem prepareConfiguration(RealmRepresentat } else if (realmRep != null && keycloakX) { createRealm(clientAuthServerBaseUrl, realmRep); } - devServices.produce(new DevServicesConfigResultBuildItem(KEYCLOAK_URL_KEY, capturedKeycloakInternalURL)); - devServices.produce(new DevServicesConfigResultBuildItem(AUTH_SERVER_URL_CONFIG_KEY, authServerInternalUrl)); - devServices.produce(new DevServicesConfigResultBuildItem(CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl)); - devServices.produce(new DevServicesConfigResultBuildItem(APPLICATION_TYPE_CONFIG_KEY, oidcApplicationType)); - devServices.produce(new DevServicesConfigResultBuildItem(CLIENT_ID_CONFIG_KEY, oidcClientId)); - devServices.produce(new DevServicesConfigResultBuildItem(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret)); - - Map configProperties = new HashMap<>(); - configProperties.put(KEYCLOAK_URL_KEY, capturedKeycloakInternalURL); - configProperties.put(KEYCLOAK_REALM_KEY, realmName); + + Map configProperties = new HashMap<>(); + configProperties.put(KEYCLOAK_URL_KEY, internalURL); configProperties.put(AUTH_SERVER_URL_CONFIG_KEY, authServerInternalUrl); + configProperties.put(CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl); configProperties.put(APPLICATION_TYPE_CONFIG_KEY, oidcApplicationType); configProperties.put(CLIENT_ID_CONFIG_KEY, oidcClientId); configProperties.put(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret); - configProperties.put(OIDC_USERS, users); - - existingDevServiceConfig = new KeycloakDevServicesConfigBuildItem(configProperties); - return existingDevServiceConfig; + return configProperties; } private String realmsURL(String baseURL, String realmName) { @@ -283,7 +260,7 @@ private String getDefaultRealmName() { return capturedDevServicesConfiguration.realmName.orElse("quarkus"); } - private StartResult startContainer(boolean useSharedNetwork, Optional timeout) { + private RunningDevService startContainer(boolean useSharedNetwork, Optional timeout) { if (!capturedDevServicesConfiguration.enabled) { // explicitly disabled LOG.debug("Not starting Dev Services for Keycloak as it has been disabled in the config"); @@ -311,7 +288,7 @@ private StartResult startContainer(boolean useSharedNetwork, Optional String imageName = capturedDevServicesConfiguration.imageName; DockerImageName dockerImageName = DockerImageName.parse(imageName).asCompatibleSubstituteFor(imageName); - final Supplier defaultKeycloakContainerSupplier = () -> { + final Supplier defaultKeycloakContainerSupplier = () -> { QuarkusOidcContainer oidcContainer = new QuarkusOidcContainer(dockerImageName, capturedDevServicesConfiguration.port, @@ -324,28 +301,24 @@ private StartResult startContainer(boolean useSharedNetwork, Optional timeout.ifPresent(oidcContainer::withStartupTimeout); oidcContainer.start(); - return new StartResult( - startURL(oidcContainer.getHost(), oidcContainer.getPort(), oidcContainer.keycloakX), - oidcContainer.useSharedNetwork - ? startURL("localhost", oidcContainer.fixedExposedPort.getAsInt(), oidcContainer.keycloakX) - : null, - oidcContainer.keycloakX, - oidcContainer.realmRep, - new Closeable() { - @Override - public void close() { - oidcContainer.close(); - - LOG.info("Dev Services for Keycloak shut down."); - } - }); + String internalUrl = startURL(oidcContainer.getHost(), oidcContainer.getPort(), oidcContainer.keycloakX); + String hostUrl = oidcContainer.useSharedNetwork + ? startURL("localhost", oidcContainer.fixedExposedPort.getAsInt(), oidcContainer.keycloakX) + : null; + Map configs = prepareConfiguration(internalUrl, hostUrl, oidcContainer.realmRep, + oidcContainer.keycloakX); + return new RunningDevService(Feature.KEYCLOAK_AUTHORIZATION.getName(), oidcContainer.getContainerId(), + oidcContainer::close, configs); }; return maybeContainerAddress - .map(containerAddress -> new StartResult( - getSharedContainerUrl(containerAddress), - getSharedContainerUrl(containerAddress), // TODO: this probably needs to be addressed - false, null, null)) + .map(containerAddress -> { + // TODO: this probably needs to be addressed + Map configs = prepareConfiguration(getSharedContainerUrl(containerAddress), + getSharedContainerUrl(containerAddress), null, false); + return new RunningDevService(Feature.KEYCLOAK_AUTHORIZATION.getName(), + containerAddress.getId(), null, configs); + }) .orElseGet(defaultKeycloakContainerSupplier); } @@ -360,24 +333,7 @@ private String getSharedContainerUrl(ContainerAddress containerAddress) { + ":" + containerAddress.getPort(); } - private static class StartResult { - private final String internalURL; - private final String hostURL; - private final RealmRepresentation realmRep; - private final Closeable closeable; - private final boolean keycloakX; - - public StartResult(String internalURL, String hostURL, - boolean keycloakX, RealmRepresentation realmRep, Closeable closeable) { - this.internalURL = internalURL; - this.hostURL = hostURL; - this.keycloakX = keycloakX; - this.realmRep = realmRep; - this.closeable = closeable; - } - } - - private static class QuarkusOidcContainer extends GenericContainer { + private static class QuarkusOidcContainer extends GenericContainer { private final OptionalInt fixedExposedPort; private final boolean useSharedNetwork; private final Optional realmPath; diff --git a/extensions/oidc/runtime/pom.xml b/extensions/oidc/runtime/pom.xml index 4e3cd5528829dc..b0f84c42dfa452 100644 --- a/extensions/oidc/runtime/pom.xml +++ b/extensions/oidc/runtime/pom.xml @@ -45,6 +45,10 @@ jakarta.annotation jakarta.annotation-api + + io.quarkus + quarkus-devservices + io.quarkus quarkus-junit5-internal diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/pom.xml b/extensions/panache/hibernate-orm-panache-kotlin/deployment/pom.xml index 2cf35be7c56126..4c859dfcd80430 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/pom.xml +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/pom.xml @@ -15,6 +15,12 @@ io.quarkus quarkus-hibernate-orm-panache-common-deployment + + + org.jetbrains + annotations + + io.quarkus diff --git a/extensions/redis-client/deployment/pom.xml b/extensions/redis-client/deployment/pom.xml index 87c4b51eb38b67..38fc20b97c3a27 100644 --- a/extensions/redis-client/deployment/pom.xml +++ b/extensions/redis-client/deployment/pom.xml @@ -21,7 +21,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment io.quarkus diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java index ba385ebf978adc..5933c4451642c7 100644 --- a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java @@ -13,17 +13,19 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking.IsDockerRunningSilent; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; @@ -54,15 +56,15 @@ public class DevServicesRedisProcessor { private static final String QUARKUS = "quarkus."; private static final String DOT = "."; - private static volatile List closeables; + private static volatile List devServices; private static volatile Map capturedDevServicesConfiguration; private static volatile boolean first = true; private static volatile Boolean dockerRunning = null; @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class }) - public void startRedisContainers(LaunchModeBuildItem launchMode, + public List startRedisContainers(LaunchModeBuildItem launchMode, List devServicesSharedNetworkBuildItem, - BuildProducer devConfigProducer, RedisBuildTimeConfig config, + RedisBuildTimeConfig config, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, @@ -73,24 +75,24 @@ public void startRedisContainers(LaunchModeBuildItem launchMode, // figure out if we need to shut down and restart existing redis containers // if not and the redis containers have already started we just return - if (closeables != null) { + if (devServices != null) { boolean restartRequired = !currentDevServicesConfiguration.equals(capturedDevServicesConfiguration); if (!restartRequired) { - return; + return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - for (Closeable closeable : closeables) { + for (Closeable closeable : devServices) { try { closeable.close(); } catch (Throwable e) { log.error("Failed to stop redis container", e); } } - closeables = null; + devServices = null; capturedDevServicesConfiguration = null; } capturedDevServicesConfiguration = currentDevServicesConfiguration; - List currentCloseables = new ArrayList<>(); + List newDevServices = new ArrayList<>(); StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "Redis Dev Services Starting:", consoleInstalledBuildItem, @@ -98,16 +100,16 @@ public void startRedisContainers(LaunchModeBuildItem launchMode, try { for (Entry entry : currentDevServicesConfiguration.entrySet()) { String connectionName = entry.getKey(); - StartResult startResult = startContainer(connectionName, entry.getValue().devservices, + RunningDevService devService = startContainer(connectionName, entry.getValue().devservices, launchMode.getLaunchMode(), !devServicesSharedNetworkBuildItem.isEmpty(), devServicesConfig.timeout); - if (startResult == null) { + if (devService == null) { continue; } - currentCloseables.add(startResult.closeable); + newDevServices.add(devService); String configKey = getConfigPrefix(connectionName) + RedisConfig.HOSTS_CONFIG_NAME; - devConfigProducer.produce(new DevServicesConfigResultBuildItem(configKey, startResult.url)); - log.infof("The %s redis server is ready to accept connections on %s", connectionName, startResult.url); + log.infof("The %s redis server is ready to accept connections on %s", connectionName, + devService.getConfig().get(configKey)); } compressor.close(); } catch (Throwable t) { @@ -115,14 +117,14 @@ public void startRedisContainers(LaunchModeBuildItem launchMode, throw new RuntimeException(t); } - closeables = currentCloseables; + devServices = newDevServices; if (first) { first = false; Runnable closeTask = () -> { dockerRunning = null; - if (closeables != null) { - for (Closeable closeable : closeables) { + if (devServices != null) { + for (Closeable closeable : devServices) { try { closeable.close(); } catch (Throwable t) { @@ -131,14 +133,15 @@ public void startRedisContainers(LaunchModeBuildItem launchMode, } } first = true; - closeables = null; + devServices = null; capturedDevServicesConfiguration = null; }; closeBuildItem.addCloseTask(closeTask, true); } + return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - private StartResult startContainer(String connectionName, DevServicesConfig devServicesConfig, LaunchMode launchMode, + private RunningDevService startContainer(String connectionName, DevServicesConfig devServicesConfig, LaunchMode launchMode, boolean useSharedNetwork, Optional timeout) { if (!devServicesConfig.enabled) { // explicitly disabled @@ -170,20 +173,20 @@ private StartResult startContainer(String connectionName, DevServicesConfig devS DockerImageName dockerImageName = DockerImageName.parse(devServicesConfig.imageName.orElse(REDIS_6_ALPINE)) .asCompatibleSubstituteFor(REDIS_6_ALPINE); - Supplier defaultRedisServerSupplier = () -> { + Supplier defaultRedisServerSupplier = () -> { QuarkusPortRedisContainer redisContainer = new QuarkusPortRedisContainer(dockerImageName, devServicesConfig.port, launchMode == DEVELOPMENT ? devServicesConfig.serviceName : null, useSharedNetwork); timeout.ifPresent(redisContainer::withStartupTimeout); redisContainer.start(); String redisHost = REDIS_SCHEME + redisContainer.getHost() + ":" + redisContainer.getPort(); - return new StartResult(redisHost, - redisContainer::close); + return new RunningDevService(Feature.REDIS_CLIENT.getName(), redisContainer.getContainerId(), + redisContainer::close, configPrefix + RedisConfig.HOSTS_CONFIG_NAME, redisHost); }; return redisContainerLocator.locateContainer(devServicesConfig.serviceName, devServicesConfig.shared, launchMode) - .map(containerAddress -> new StartResult(containerAddress.getUrl(), null)) + .map(containerAddress -> new RunningDevService(Feature.REDIS_CLIENT.getName(), containerAddress.getId(), + null, configPrefix + RedisConfig.HOSTS_CONFIG_NAME, containerAddress.getUrl())) .orElseGet(defaultRedisServerSupplier); - } private String getConfigPrefix(String connectionName) { @@ -194,16 +197,6 @@ private String getConfigPrefix(String connectionName) { return configPrefix; } - private static class StartResult { - private final String url; - private final Closeable closeable; - - public StartResult(String url, Closeable closeable) { - this.url = url; - this.closeable = closeable; - } - } - private static class QuarkusPortRedisContainer extends GenericContainer { private final OptionalInt fixedExposedPort; private final boolean useSharedNetwork; diff --git a/extensions/redis-client/runtime/pom.xml b/extensions/redis-client/runtime/pom.xml index ec614da3ce39b9..f95bf28923c7b3 100644 --- a/extensions/redis-client/runtime/pom.xml +++ b/extensions/redis-client/runtime/pom.xml @@ -29,6 +29,10 @@ quarkus-smallrye-health true + + io.quarkus + quarkus-devservices + org.assertj assertj-core diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml b/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml index 180508b2134aed..60bd5b514e3af9 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml @@ -103,7 +103,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java index fe8de781906c78..2fcb397a450ba6 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java @@ -2,6 +2,8 @@ import java.io.Closeable; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; @@ -14,12 +16,13 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; import io.quarkus.deployment.console.StartupLogCompressor; @@ -55,17 +58,16 @@ public class AmqpDevServicesProcessor { private static final String DEFAULT_USER = "admin"; private static final String DEFAULT_PASSWORD = "admin"; - static volatile Closeable closeable; + static volatile RunningDevService devService; static volatile AmqpDevServiceCfg cfg; static volatile boolean first = true; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - public DevServicesAmqpBrokerBuildItem startAmqpDevService( + public DevServicesResultBuildItem startAmqpDevService( LaunchModeBuildItem launchMode, AmqpBuildTimeConfig amqpClientBuildTimeConfig, - BuildProducer devServicePropertiesProducer, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, @@ -73,38 +75,30 @@ public DevServicesAmqpBrokerBuildItem startAmqpDevService( AmqpDevServiceCfg configuration = getConfiguration(amqpClientBuildTimeConfig); - if (closeable != null) { + if (devService != null) { boolean shouldShutdownTheBroker = !configuration.equals(cfg); if (!shouldShutdownTheBroker) { - return null; + return devService.toBuildItem(); } shutdownBroker(); cfg = null; } - AmqpBroker broker; - DevServicesAmqpBrokerBuildItem artemis = null; StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "AMQP Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); try { - broker = startAmqpBroker(configuration, launchMode, devServicesConfig.timeout); - if (broker != null) { - closeable = broker.getCloseable(); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_HOST_PROP, broker.host)); - devServicePropertiesProducer - .produce(new DevServicesConfigResultBuildItem(AMQP_PORT_PROP, Integer.toString(broker.port))); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_USER_PROP, broker.user)); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_PASSWORD_PROP, broker.password)); - - artemis = new DevServicesAmqpBrokerBuildItem(broker.host, broker.port, broker.user, broker.password); - - if (broker.isOwner()) { + RunningDevService newDevService = startAmqpBroker(configuration, launchMode, devServicesConfig.timeout); + if (newDevService != null) { + devService = newDevService; + Map config = devService.getConfig(); + if (newDevService.isOwner()) { log.info("Dev Services for AMQP started."); log.infof("Other Quarkus applications in dev mode will find the " + "broker automatically. For Quarkus applications in production mode, you can connect to" - + " this by starting your application with -Damqp.host=%s -Damqp.port=%d -Damqp.user=%s -Damqp.password=%s", - broker.host, broker.port, broker.user, broker.password); + + " this by starting your application with -Damqp.host=%s -Damqp.port=%s -Damqp.user=%s -Damqp.password=%s", + config.get(AMQP_HOST_PROP), config.get(AMQP_PORT_PROP), config.get(AMQP_USER_PROP), + config.get(AMQP_PASSWORD_PROP)); } } compressor.close(); @@ -113,38 +107,43 @@ public DevServicesAmqpBrokerBuildItem startAmqpDevService( throw new RuntimeException(t); } + if (devService == null) { + return null; + } + // Configure the watch dog if (first) { first = false; Runnable closeTask = () -> { - if (closeable != null) { + if (devService != null) { shutdownBroker(); log.info("Dev Services for AMQP shut down."); } first = true; - closeable = null; + devService = null; cfg = null; }; closeBuildItem.addCloseTask(closeTask, true); } cfg = configuration; - return artemis; + return devService.toBuildItem(); } private void shutdownBroker() { - if (closeable != null) { + if (devService != null) { try { - closeable.close(); + devService.close(); } catch (Throwable e) { log.error("Failed to stop the AMQP broker", e); } finally { - closeable = null; + devService = null; } } } - private AmqpBroker startAmqpBroker(AmqpDevServiceCfg config, LaunchModeBuildItem launchMode, Optional timeout) { + private RunningDevService startAmqpBroker(AmqpDevServiceCfg config, LaunchModeBuildItem launchMode, + Optional timeout) { if (!config.devServicesEnabled) { // explicitly disabled log.debug("Not starting Dev Services for AMQP, as it has been disabled in the config."); @@ -168,7 +167,7 @@ private AmqpBroker startAmqpBroker(AmqpDevServiceCfg config, LaunchModeBuildItem return null; } - final Supplier defaultAmqpBrokerSupplier = () -> { + final Supplier defaultAmqpBrokerSupplier = () -> { // Starting the broker ArtemisContainer container = new ArtemisContainer( DockerImageName.parse(config.imageName), @@ -179,20 +178,24 @@ private AmqpBroker startAmqpBroker(AmqpDevServiceCfg config, LaunchModeBuildItem timeout.ifPresent(container::withStartupTimeout); container.start(); - return new AmqpBroker( - container.getHost(), - container.getPort(), - DEFAULT_USER, - DEFAULT_PASSWORD, - container::close); + return getRunningService(container.getContainerId(), container::close, container.getHost(), container.getPort()); }; return amqpContainerLocator.locateContainer(config.serviceName, config.shared, launchMode.getLaunchMode()) - .map(containerAddress -> new AmqpBroker(containerAddress.getHost(), containerAddress.getPort(), "admin", - "admin", null)) + .map(containerAddress -> getRunningService(containerAddress.getId(), null, containerAddress.getHost(), + containerAddress.getPort())) .orElseGet(defaultAmqpBrokerSupplier); } + private RunningDevService getRunningService(String containerId, Closeable closeable, String host, int port) { + Map configMap = new HashMap<>(); + configMap.put(AMQP_HOST_PROP, host); + configMap.put(AMQP_PORT_PROP, String.valueOf(port)); + configMap.put(AMQP_USER_PROP, DEFAULT_USER); + configMap.put(AMQP_PASSWORD_PROP, DEFAULT_PASSWORD); + return new RunningDevService(Feature.SMALLRYE_REACTIVE_MESSAGING_AMQP.getName(), containerId, closeable, configMap); + } + private boolean hasAmqpChannelWithoutHostAndPort() { Config config = ConfigProvider.getConfig(); for (String name : config.getPropertyNames()) { @@ -220,30 +223,6 @@ private AmqpDevServiceCfg getConfiguration(AmqpBuildTimeConfig cfg) { return new AmqpDevServiceCfg(devServicesConfig); } - private static class AmqpBroker { - private final Closeable closeable; - private final String host; - private final int port; - private final String user; - private final String password; - - public AmqpBroker(String host, int port, String user, String password, Closeable closeable) { - this.host = host; - this.port = port; - this.user = user; - this.password = password; - this.closeable = closeable; - } - - public boolean isOwner() { - return closeable != null; - } - - public Closeable getCloseable() { - return closeable; - } - } - private static final class AmqpDevServiceCfg { private final boolean devServicesEnabled; private final String imageName; diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/DevServicesAmqpBrokerBuildItem.java b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/DevServicesAmqpBrokerBuildItem.java deleted file mode 100644 index df1dd4b3931f65..00000000000000 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/DevServicesAmqpBrokerBuildItem.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.quarkus.smallrye.reactivemessaging.amqp.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; - -public final class DevServicesAmqpBrokerBuildItem extends SimpleBuildItem { - - public final String host; - public final int port; - public final String user; - public final String password; - - public DevServicesAmqpBrokerBuildItem(String host, int port, String user, String password) { - this.host = host; - this.port = port; - this.user = user; - this.password = password; - } -} diff --git a/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml b/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml index 4da979208c034f..0c4830ba9d8185 100644 --- a/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml @@ -58,6 +58,10 @@ svm provided + + io.quarkus + quarkus-devservices + diff --git a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/pom.xml b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/pom.xml index 8684e27209aa56..a6f4d77ddebba5 100644 --- a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/pom.xml +++ b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/pom.xml @@ -60,7 +60,7 @@ io.quarkus - quarkus-devservices-common + quarkus-devservices-deployment diff --git a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/DevServicesRabbitMQBrokerBuildItem.java b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/DevServicesRabbitMQBrokerBuildItem.java deleted file mode 100644 index f3308b518288ec..00000000000000 --- a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/DevServicesRabbitMQBrokerBuildItem.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.quarkus.smallrye.reactivemessaging.rabbitmq.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; - -public final class DevServicesRabbitMQBrokerBuildItem extends SimpleBuildItem { - - public final String host; - public final int port; - public final String user; - public final String password; - - public DevServicesRabbitMQBrokerBuildItem(String host, int port, String user, String password) { - this.host = host; - this.port = port; - this.user = user; - this.password = password; - } -} diff --git a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java index cc0a4863a4100b..d26beffffd4b12 100644 --- a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java +++ b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java @@ -3,6 +3,7 @@ import java.io.Closeable; import java.time.Duration; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -19,11 +20,12 @@ import org.testcontainers.utility.DockerImageName; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDockerWorking; import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; import io.quarkus.deployment.console.StartupLogCompressor; @@ -54,57 +56,47 @@ public class RabbitMQDevServicesProcessor { private static final String RABBITMQ_USERNAME_PROP = "rabbitmq-username"; private static final String RABBITMQ_PASSWORD_PROP = "rabbitmq-password"; - static volatile Closeable closeable; + static volatile RunningDevService devService; static volatile RabbitMQDevServiceCfg cfg; static volatile boolean first = true; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - public DevServicesRabbitMQBrokerBuildItem startRabbitMQDevService( + public DevServicesResultBuildItem startRabbitMQDevService( LaunchModeBuildItem launchMode, RabbitMQBuildTimeConfig rabbitmqClientBuildTimeConfig, - BuildProducer devServicePropertiesProducer, Optional consoleInstalledBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) { RabbitMQDevServiceCfg configuration = getConfiguration(rabbitmqClientBuildTimeConfig); - if (closeable != null) { + if (devService != null) { boolean shouldShutdownTheBroker = !configuration.equals(cfg); if (!shouldShutdownTheBroker) { - return null; + return devService.toBuildItem(); } shutdownBroker(); cfg = null; } - RabbitMQBroker broker; - DevServicesRabbitMQBrokerBuildItem rabbitMQ = null; StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "RabbitMQ Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); try { - broker = startRabbitMQBroker(configuration, launchMode, devServicesConfig.timeout); - if (broker != null) { - closeable = broker.getCloseable(); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(RABBITMQ_HOST_PROP, broker.host)); - devServicePropertiesProducer - .produce(new DevServicesConfigResultBuildItem(RABBITMQ_PORT_PROP, Integer.toString(broker.port))); - devServicePropertiesProducer - .produce(new DevServicesConfigResultBuildItem(RABBITMQ_USERNAME_PROP, broker.username)); - devServicePropertiesProducer - .produce(new DevServicesConfigResultBuildItem(RABBITMQ_PASSWORD_PROP, broker.password)); - - rabbitMQ = new DevServicesRabbitMQBrokerBuildItem(broker.host, broker.port, broker.username, broker.password); - - if (broker.isOwner()) { + RunningDevService newDevService = startRabbitMQBroker(configuration, launchMode, devServicesConfig.timeout); + if (newDevService != null) { + devService = newDevService; + + Map config = devService.getConfig(); + if (devService.isOwner()) { log.info("Dev Services for RabbitMQ started."); log.infof("Other Quarkus applications in dev mode will find the " + "broker automatically. For Quarkus applications in production mode, you can connect to" - + " this by starting your application with -Drabbitmq-host=%s -Drabbitmq-port=%d -Drabbitmq-username=%s -Drabbitmq-password=%s", - broker.host, broker.port, broker.username, broker.password); + + " this by starting your application with -Drabbitmq-host=%s -Drabbitmq-port=%s -Drabbitmq-username=%s -Drabbitmq-password=%s", + config.get(RABBITMQ_HOST_PROP), config.get(RABBITMQ_PORT_PROP), + config.get(RABBITMQ_USERNAME_PROP), config.get(RABBITMQ_PASSWORD_PROP)); } } compressor.close(); @@ -113,39 +105,43 @@ public DevServicesRabbitMQBrokerBuildItem startRabbitMQDevService( throw new RuntimeException(t); } + if (devService == null) { + return null; + } + // Configure the watch dog if (first) { first = false; Runnable closeTask = () -> { - if (closeable != null) { + if (devService != null) { shutdownBroker(); log.info("Dev Services for RabbitMQ shut down."); } first = true; - closeable = null; + devService = null; cfg = null; }; QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask); } cfg = configuration; - return rabbitMQ; + return devService.toBuildItem(); } private void shutdownBroker() { - if (closeable != null) { + if (devService != null) { try { - closeable.close(); + devService.close(); } catch (Throwable e) { log.error("Failed to stop the RabbitMQ broker", e); } finally { - closeable = null; + devService = null; } } } - private RabbitMQBroker startRabbitMQBroker(RabbitMQDevServiceCfg config, LaunchModeBuildItem launchMode, + private RunningDevService startRabbitMQBroker(RabbitMQDevServiceCfg config, LaunchModeBuildItem launchMode, Optional timeout) { if (!config.devServicesEnabled) { // explicitly disabled @@ -180,25 +176,33 @@ private RabbitMQBroker startRabbitMQBroker(RabbitMQDevServiceCfg config, LaunchM config.bindings .forEach(b -> container.withBinding(b.source, b.destination, b.arguments, b.routingKey, b.destinationType)); - final Supplier defaultRabbitMQBrokerSupplier = () -> { + final Supplier defaultRabbitMQBrokerSupplier = () -> { + // Starting the broker timeout.ifPresent(container::withStartupTimeout); container.start(); - return new RabbitMQBroker( - container.getHost(), - container.getPort(), - container.getAdminUsername(), - container.getAdminPassword(), - container::close); + return getRunningDevService(container.getContainerId(), container::close, container.getHost(), + container.getPort(), container.getAdminUsername(), container.getAdminPassword()); }; return rabbitmqContainerLocator.locateContainer(config.serviceName, config.shared, launchMode.getLaunchMode()) - .map(containerAddress -> new RabbitMQBroker(containerAddress.getHost(), containerAddress.getPort(), - container.getAdminUsername(), container.getAdminPassword(), null)) + .map(containerAddress -> getRunningDevService(containerAddress.getId(), null, containerAddress.getHost(), + containerAddress.getPort(), container.getAdminUsername(), container.getAdminPassword())) .orElseGet(defaultRabbitMQBrokerSupplier); } + private RunningDevService getRunningDevService(String containerId, Closeable closeable, String host, int port, + String username, String password) { + Map configMap = new HashMap<>(); + configMap.put(RABBITMQ_HOST_PROP, host); + configMap.put(RABBITMQ_PORT_PROP, String.valueOf(port)); + configMap.put(RABBITMQ_USERNAME_PROP, username); + configMap.put(RABBITMQ_PASSWORD_PROP, password); + return new RunningDevService(Feature.SMALLRYE_REACTIVE_MESSAGING_RABBITMQ.getName(), + containerId, closeable, configMap); + } + private boolean hasRabbitMQChannelWithoutHostAndPort() { Config config = ConfigProvider.getConfig(); for (String name : config.getPropertyNames()) { @@ -226,30 +230,6 @@ private RabbitMQDevServiceCfg getConfiguration(RabbitMQBuildTimeConfig cfg) { return new RabbitMQDevServiceCfg(devServicesConfig); } - private static class RabbitMQBroker { - private final Closeable closeable; - private final String host; - private final int port; - private final String username; - private final String password; - - public RabbitMQBroker(String host, int port, String username, String password, Closeable closeable) { - this.host = host; - this.port = port; - this.username = username; - this.password = password; - this.closeable = closeable; - } - - public boolean isOwner() { - return closeable != null; - } - - public Closeable getCloseable() { - return closeable; - } - } - private static final class RabbitMQDevServiceCfg { static class Exchange { diff --git a/extensions/smallrye-reactive-messaging-rabbitmq/runtime/pom.xml b/extensions/smallrye-reactive-messaging-rabbitmq/runtime/pom.xml index b37bde2ae871b6..8004214e8d8954 100644 --- a/extensions/smallrye-reactive-messaging-rabbitmq/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging-rabbitmq/runtime/pom.xml @@ -62,6 +62,10 @@ svm provided + + io.quarkus + quarkus-devservices + diff --git a/integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleDevServicesSmokeTest.java b/integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleDevServicesSmokeTest.java new file mode 100644 index 00000000000000..4d49c94d145e94 --- /dev/null +++ b/integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleDevServicesSmokeTest.java @@ -0,0 +1,33 @@ +package io.quarkus.test.devconsole; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +/** + * Note that this test cannot be placed under the relevant {@code -deployment} module because then the DEV UI processor would + * not be able to locate the template resources correctly. + */ +public class DevConsoleDevServicesSmokeTest { + + @RegisterExtension + static final QuarkusDevModeTest config = new QuarkusDevModeTest() + .withEmptyApplication(); + + @Test + public void testDevConsoleNotBroken() { + RestAssured.with() + .get("q/dev") + .then() + .statusCode(200).body(Matchers.containsString("Dev Services")); + + RestAssured.with() + .get("q/dev/io.quarkus.quarkus-devservices/dev-services") + .then() + .statusCode(200).body(Matchers.containsString("Dev Services")); + + } +}