diff --git a/bom/application/pom.xml b/bom/application/pom.xml index a8e24a8106ba3..831c4580688fd 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -639,6 +639,11 @@ quarkus-devservices-common ${project.version} + + io.quarkus + quarkus-devservices-deployment + ${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 0d98a21d0c42e..675e755bbd291 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(since = "https://github.com/quarkusio/quarkus/pull/23048") 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 0000000000000..5ed1385142e11 --- /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/dev/devservices/ContainerInfo.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/ContainerInfo.java new file mode 100644 index 0000000000000..9227f0b1a06e1 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/ContainerInfo.java @@ -0,0 +1,154 @@ +package io.quarkus.deployment.dev.devservices; + +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/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/DevServiceDescriptionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/DevServiceDescriptionBuildItem.java new file mode 100644 index 0000000000000..9d9374a342c4c --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/DevServiceDescriptionBuildItem.java @@ -0,0 +1,55 @@ +package io.quarkus.deployment.dev.devservices; + +import java.util.Map; +import java.util.stream.Collectors; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class DevServiceDescriptionBuildItem extends MultiBuildItem { + private String name; + private ContainerInfo containerInfo; + private Map configs; + + public DevServiceDescriptionBuildItem() { + } + + public DevServiceDescriptionBuildItem(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/core/deployment/src/main/java/io/quarkus/deployment/steps/DevServicesConfigBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/DevServicesConfigBuildStep.java index 5c01b2b7bbf1b..c035e78d5e324 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/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java index 8a8eaea1e625a..a6e717def63cc 100644 --- a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java +++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java @@ -2,6 +2,8 @@ import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; @@ -11,12 +13,13 @@ import io.quarkus.amazon.lambda.runtime.LambdaHotReplacementRecorder; import io.quarkus.amazon.lambda.runtime.MockEventServer; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.RuntimeApplicationShutdownBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; @@ -54,7 +57,7 @@ private boolean legacyTestingEnabled() { public void startEventServer(LaunchModeBuildItem launchMode, LambdaConfig config, Optional override, - BuildProducer devServicePropertiesProducer, + BuildProducer devServicePropertiesProducer, BuildProducer runtimeApplicationShutdownBuildItemBuildProducer) throws Exception { if (!launchMode.getLaunchMode().isDevOrTest()) @@ -77,8 +80,10 @@ public void startEventServer(LaunchModeBuildItem launchMode, startMode = launchMode.getLaunchMode(); server.start(port); String baseUrl = "localhost:" + port + MockEventServer.BASE_PATH; + Map properties = new HashMap<>(); + properties.put(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API, baseUrl); devServicePropertiesProducer.produce( - new DevServicesConfigResultBuildItem(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API, baseUrl)); + new DevServicesResultBuildItem(Feature.AMAZON_LAMBDA.getName(), null, properties)); Runnable closeTask = () -> { if (server != null) { try { diff --git a/extensions/apicurio-registry-avro/deployment/pom.xml b/extensions/apicurio-registry-avro/deployment/pom.xml index 388a8ad5b82fb..4df4d4cbfd532 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 ae450b9c24993..6bcf7a83f93e7 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 8544a4c7a3069..7f4be7fd6dbc6 100644 --- a/extensions/apicurio-registry-avro/runtime/pom.xml +++ b/extensions/apicurio-registry-avro/runtime/pom.xml @@ -46,11 +46,10 @@ io.quarkus quarkus-vertx - - - org.apache.commons - commons-lang3 - + + org.apache.commons + commons-lang3 + 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 fe5666027f196..25bd51c5571ed 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/pom.xml b/extensions/datasource/deployment/pom.xml index 3cae04a4341ce..676f7b24bed92 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 c5f746cbaa685..cb85cc34041f3 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 @@ -25,7 +25,8 @@ 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 +39,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; @@ -53,7 +54,7 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura DataSourcesBuildTimeConfig dataSourceBuildTimeConfig, LaunchModeBuildItem launchMode, List configurationHandlerBuildItems, - BuildProducer devServicesResultBuildItemBuildProducer, + BuildProducer devServicesResultBuildItemBuildProducer, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, @@ -82,6 +83,10 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura } } if (!restartRequired) { + for (RunningDevService database : databases) { + devServicesResultBuildItemBuildProducer.produce(database.toBuildItem()); + } + // keep the previous behaviour of producing DevServicesDatasourceResultBuildItem only when the devservice first starts. return null; } for (Closeable i : databases) { @@ -104,7 +109,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 +123,26 @@ 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); } + defaultResult = toDbResult(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); + namedResults.put(entry.getKey(), toDbResult(namedDevService)); } } - for (DevServicesConfigResultBuildItem i : dbConfig) { - devServicesResultBuildItemBuildProducer - .produce(i); - } if (first) { first = false; @@ -168,8 +165,11 @@ public void run() { }; closeBuildItem.addCloseTask(closeTask, true); } - databases = closeableList; + databases = runningDevServices; cachedProperties = propertiesMap; + for (RunningDevService database : databases) { + devServicesResultBuildItemBuildProducer.produce(database.toBuildItem()); + } return new DevServicesDatasourceResultBuildItem(defaultResult, namedResults); } @@ -180,13 +180,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.orElse(true)); @@ -266,7 +266,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."; @@ -305,10 +304,17 @@ 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(defaultDbKind.get(), datasource.getId(), datasource.getCloseTask(), devDebProperties); } catch (Throwable t) { compressor.closeAndDumpCaptured(); throw new RuntimeException(t); } } + + private DevServicesDatasourceResultBuildItem.DbResult toDbResult(RunningDevService devService) { + if (devService == null) { + return null; + } + return new DevServicesDatasourceResultBuildItem.DbResult(devService.getName(), devService.getConfig()); + } } 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 3d2f90d307bd5..2773132dfc9fc 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 70de1108b2b5d..5ac10a7408e58 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 3d1a02510b3a5..6fb9218f41c2a 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 @@ -44,7 +44,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 0000000000000..f78183ff0248b --- /dev/null +++ b/extensions/devservices/deployment/pom.xml @@ -0,0 +1,45 @@ + + + + quarkus-devservices-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devservices-deployment + Quarkus - DevServices - Deployment + + + + 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 0000000000000..a7c366c9772b8 --- /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.deployment.dev.devservices.DevServiceDescriptionBuildItem; + +public class ContainerLogForwarder implements Closeable { + + private final DevServiceDescriptionBuildItem 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(DevServiceDescriptionBuildItem devService) { + this.devService = devService; + this.logger = Logger.getLogger(devService.getName()); + this.shortId = devService.getContainerInfo().getShortId(); + } + + public DevServiceDescriptionBuildItem 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 0000000000000..03546bfe4fad2 --- /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.deployment.dev.devservices.DevServiceDescriptionBuildItem; + +@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(DevServiceDescriptionBuildItem::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 0000000000000..56f142ff343ac --- /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.deployment.dev.devservices.DevServiceDescriptionBuildItem; + +@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 (DevServiceDescriptionBuildItem 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 0000000000000..de75cd0cdbca2 --- /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.deployment.dev.devservices.DevServiceDescriptionBuildItem; +import io.quarkus.devservices.deployment.DevServicesCommand.DevServiceCompleter; + +@CommandDefinition(name = "logs", description = "Print container logs") +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()) { + DevServiceDescriptionBuildItem 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 0000000000000..caf05ad26ef7e --- /dev/null +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java @@ -0,0 +1,218 @@ +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.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.dev.devservices.ContainerInfo; +import io.quarkus.deployment.dev.devservices.DevServiceDescriptionBuildItem; +import io.quarkus.dev.spi.DevModeType; + +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 }) + public List config( + BuildProducer commandBuildItemBuildProducer, + LaunchModeBuildItem launchModeBuildItem, + Optional devServicesLauncherConfig, + List devServicesResults) { + List serviceDescriptions = buildServiceDescriptions(devServicesResults, + devServicesLauncherConfig); + + for (DevServiceDescriptionBuildItem devService : serviceDescriptions) { + if (devService.hasContainerInfo()) { + containerLogForwarders.compute(devService.getContainerInfo().getId(), + (id, forwarder) -> Objects.requireNonNullElseGet(forwarder, + () -> new ContainerLogForwarder(devService))); + } + } + + // Build commands if we are in local dev mode + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return serviceDescriptions; + } + + 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 (DevServiceDescriptionBuildItem 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)); + return serviceDescriptions; + } + + 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(DevServiceDescriptionBuildItem::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 DevServiceDescriptionBuildItem("Other Dev Services", null, config)); + } + } + return descriptions; + } + + private Map fetchContainerInfos(Set containerIds) { + if (containerIds.isEmpty() || !isDockerWorking.getAsBoolean()) { + return Collections.emptyMap(); + } + return DockerClientFactory.lazyClient().listContainersCmd() + .withIdFilter(containerIds) + .withShowAll(true) + .exec() + .stream() + .collect(Collectors.toMap(Container::getId, Function.identity())); + } + + private DevServiceDescriptionBuildItem toDevServiceDescription(DevServicesResultBuildItem buildItem, Container container) { + if (container == null) { + return new DevServiceDescriptionBuildItem(buildItem.getName(), null, buildItem.getConfig()); + } else { + return new DevServiceDescriptionBuildItem(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, DevServiceDescriptionBuildItem 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/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 f76d2805a0ac9..4a2389fe066cf 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 a1b1be5614a94..1489eccd1af19 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 acbc20871749b..d0d6bc62f6428 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 @@ -52,7 +52,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 4b9d4fab083b9..412e0cf84c931 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 @@ -42,7 +42,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 0947aacb927ed..2f6c834bff0eb 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 @@ -52,7 +52,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 0f7b87ebb409f..c8372040eca3c 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 @@ -50,7 +50,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 7dc1df2431f3a..84d2af8ab037e 100644 --- a/extensions/devservices/pom.xml +++ b/extensions/devservices/pom.xml @@ -27,6 +27,7 @@ db2 oracle common + deployment 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 32fb572e75bba..575e6612ba1d6 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 @@ -52,7 +52,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/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index 35ec24fce9010..46f03935ec963 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 eecefb279b4cc..9a55fc0593ab6 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/kafka-client/deployment/pom.xml b/extensions/kafka-client/deployment/pom.xml index fd25e333212f2..c1312bd48e115 100644 --- a/extensions/kafka-client/deployment/pom.xml +++ b/extensions/kafka-client/deployment/pom.xml @@ -64,7 +64,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 9889c7c577e10..0000000000000 --- 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 062f4e46fc533..2ce80507699e9 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.time.Duration; import java.util.HashMap; import java.util.List; @@ -26,12 +25,13 @@ import org.jboss.logging.Logger; 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; @@ -65,82 +65,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) { @@ -186,18 +181,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 @@ -228,7 +223,7 @@ private KafkaBroker startKafka(KafkaDevServiceCfg config, launchMode.getLaunchMode()); // Starting the broker - final Supplier defaultKafkaBrokerSupplier = () -> { + final Supplier defaultKafkaBrokerSupplier = () -> { if (config.imageName.contains("strimzi")) { StrimziKafkaContainer container = new StrimziKafkaContainer(config.imageName) .withBrokerId(1) @@ -244,9 +239,10 @@ 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()); } else { RedPandaKafkaContainer container = new RedPandaKafkaContainer( DockerImageName.parse(config.imageName), @@ -256,13 +252,18 @@ 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); } @@ -290,28 +291,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/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index 4754d27575919..d360655d4af4b 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 d7b2ab60da959..48b86d6db3d5c 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/oidc-client-filter/deployment/pom.xml b/extensions/oidc-client-filter/deployment/pom.xml index 53288fad78699..798226c3af672 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 1ff773f65cedb..2e44f29029b7c 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/deployment/pom.xml b/extensions/oidc/deployment/pom.xml index 12eb9289d154a..ff5393df036d1 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 4c0b520e68761..03ba6fb0fb0ee 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/panache/hibernate-orm-panache-kotlin/deployment/pom.xml b/extensions/panache/hibernate-orm-panache-kotlin/deployment/pom.xml index 2cf35be7c5612..4c859dfcd8043 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 87c4b51eb38b6..38fc20b97c3a2 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 ba385ebf978ad..5933c4451642c 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/smallrye-reactive-messaging-amqp/deployment/pom.xml b/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml index 180508b2134ae..60bd5b514e3af 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 fe8de781906c7..2fcb397a450ba 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 df1dd4b3931f6..0000000000000 --- 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-rabbitmq/deployment/pom.xml b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/pom.xml index 8684e27209aa5..a6f4d77ddebba 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 f3308b518288e..0000000000000 --- 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 cc0a4863a4100..d26beffffd4b1 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/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index 8989a0946d91f..2d69afe056560 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -53,6 +53,7 @@ import io.quarkus.deployment.builditem.WebSocketLogHandlerBuildItem; import io.quarkus.deployment.console.ConsoleCommand; import io.quarkus.deployment.console.ConsoleStateManager; +import io.quarkus.deployment.dev.devservices.DevServiceDescriptionBuildItem; import io.quarkus.deployment.ide.EffectiveIdeBuildItem; import io.quarkus.deployment.ide.Ide; import io.quarkus.deployment.logging.LoggingSetupBuildItem; @@ -395,6 +396,11 @@ public WebJarBuildItem setupWebJar( .build(); } + @BuildStep(onlyIf = { IsDevelopment.class }) + public DevConsoleTemplateInfoBuildItem config(List serviceDescriptions) { + return new DevConsoleTemplateInfoBuildItem("devServices", serviceDescriptions); + } + @Record(ExecutionTime.RUNTIME_INIT) @Consume(LoggingSetupBuildItem.class) @BuildStep(onlyIf = IsDevelopment.class) diff --git a/extensions/vertx-http/deployment/src/main/resources/dev-templates/index.html b/extensions/vertx-http/deployment/src/main/resources/dev-templates/index.html index 97ace11515e0b..4debd03720629 100644 --- a/extensions/vertx-http/deployment/src/main/resources/dev-templates/index.html +++ b/extensions/vertx-http/deployment/src/main/resources/dev-templates/index.html @@ -17,6 +17,12 @@ Config Editor

+

+ + + Dev Services + +

diff --git a/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/dev-services.html b/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/dev-services.html new file mode 100644 index 0000000000000..2d04b3c25c5b1 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/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}Dev 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/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 0000000000000..4776a45bba0f7 --- /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-vertx-http/dev-services") + .then() + .statusCode(200).body(Matchers.containsString("Dev Services")); + + } +}