Skip to content

Commit

Permalink
Add DevServices support for Vault extension
Browse files Browse the repository at this point in the history
  • Loading branch information
kdubb committed May 7, 2021
1 parent 5551461 commit be82634
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 0 deletions.
5 changes: 5 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,11 @@
<artifactId>quarkus-vault-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vault-deployment-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-credentials</artifactId>
Expand Down
22 changes: 22 additions & 0 deletions extensions/vault/deployment-spi/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-vault-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-vault-deployment-spi</artifactId>
<name>Quarkus - Vault - Deployment - SPI</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.vault.deployment.devservices;

import java.util.Map;

import io.quarkus.builder.item.SimpleBuildItem;

public final class DevServicesVaultResultBuildItem extends SimpleBuildItem {
private final Map<String, String> properties;

public DevServicesVaultResultBuildItem(Map<String, String> properties) {
this.properties = properties;
}

public Map<String, String> getProperties() {
return properties;
}
}
14 changes: 14 additions & 0 deletions extensions/vault/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vault</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vault-deployment-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
Expand Down Expand Up @@ -45,6 +49,16 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health-spi</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>vault</artifactId>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package io.quarkus.vault.deployment;

import java.io.Closeable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;

import org.apache.commons.lang3.RandomStringUtils;
import org.jboss.logging.Logger;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.vault.VaultContainer;

import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
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.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.vault.deployment.devservices.DevServicesVaultResultBuildItem;
import io.quarkus.vault.runtime.config.DevServicesConfig;
import io.quarkus.vault.runtime.config.VaultBuildTimeConfig;

public class DevServicesVaultProcessor {
private static final Logger log = Logger.getLogger(DevServicesVaultProcessor.class);
private static final String VAULT_IMAGE = "vault:1.7.1";
private static final int VAULT_EXPOSED_PORT = 8200;
private static final String CONFIG_PREFIX = "quarkus.vault.";
private static final String URL_CONFIG_KEY = CONFIG_PREFIX + "url";
private static final String AUTH_CONFIG_PREFIX = CONFIG_PREFIX + "authentication.";
private static final String CLIENT_TOKEN_CONFIG_KEY = AUTH_CONFIG_PREFIX + "client-token";
private static volatile List<Closeable> closeables;
private static volatile DevServicesConfig capturedDevServicesConfiguration;
private static volatile boolean first = true;
private final IsDockerWorking isDockerWorking = new IsDockerWorking(true);

@BuildStep(onlyIfNot = IsNormal.class)
public DevServicesVaultResultBuildItem startVaultContainers(LaunchModeBuildItem launchMode,
BuildProducer<RunTimeConfigurationDefaultBuildItem> runTimeConfiguration,
BuildProducer<ServiceStartBuildItem> serviceStartBuildItemBuildProducer, VaultBuildTimeConfig config) {

DevServicesConfig currentDevServicesConfiguration = config.devservices;

// figure out if we need to shut down and restart any existing Vault container
// if not and the Vault container have already started we just return
if (closeables != null) {
boolean restartRequired = launchMode.getLaunchMode() == LaunchMode.TEST;
if (!restartRequired) {
restartRequired = !currentDevServicesConfiguration.equals(capturedDevServicesConfiguration);
}
if (!restartRequired) {
return null;
}
for (Closeable closeable : closeables) {
try {
closeable.close();
} catch (Throwable e) {
log.error("Failed to stop Vault container", e);
}
}
closeables = null;
capturedDevServicesConfiguration = null;
}

capturedDevServicesConfiguration = currentDevServicesConfiguration;

StartResult startResult = startContainer(currentDevServicesConfiguration);
if (startResult == null) {
return null;
}

runTimeConfiguration.produce(new RunTimeConfigurationDefaultBuildItem(URL_CONFIG_KEY, startResult.url));
runTimeConfiguration
.produce(new RunTimeConfigurationDefaultBuildItem(CLIENT_TOKEN_CONFIG_KEY, startResult.clientToken));

Map<String, String> connectionProperties = new HashMap<>();
connectionProperties.put(URL_CONFIG_KEY, startResult.url);
connectionProperties.put(CLIENT_TOKEN_CONFIG_KEY, startResult.clientToken);

closeables = Collections.singletonList(startResult.closeable);

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 Vault container", t);
}
}
}
first = true;
closeables = null;
capturedDevServicesConfiguration = null;
}
};
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader();
((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask);
Thread closeHookThread = new Thread(closeTask, "Vault container shutdown thread");
Runtime.getRuntime().addShutdownHook(closeHookThread);
((QuarkusClassLoader) cl.parent()).addCloseTask(new Runnable() {
@Override
public void run() {
Runtime.getRuntime().removeShutdownHook(closeHookThread);
}
});
}
return new DevServicesVaultResultBuildItem(connectionProperties);
}

private StartResult startContainer(DevServicesConfig devServicesConfig) {
if (!devServicesConfig.enabled) {
// explicitly disabled
log.debug("Not starting devservices for Vault as it has been disabled in the config");
return null;
}

if (!isDockerWorking.getAsBoolean()) {
log.warn("Please configure Vault URL or get a working docker instance");
return null;
}

boolean needToStart = !ConfigUtils.isPropertyPresent(URL_CONFIG_KEY);
if (!needToStart) {
log.debug("Not starting devservices for default Vault client as url have been provided");
return null;
}

String token = RandomStringUtils.randomAlphanumeric(10);

DockerImageName dockerImageName = DockerImageName.parse(devServicesConfig.imageName.orElse(VAULT_IMAGE))
.asCompatibleSubstituteFor(VAULT_IMAGE);
FixedPortVaultContainer vaultContainer = new FixedPortVaultContainer(dockerImageName, devServicesConfig.port)
.withVaultToken(token);

if (devServicesConfig.transitEnabled) {
vaultContainer.withInitCommand("secrets enable transit");
}

if (devServicesConfig.pkiEnabled) {
vaultContainer.withInitCommand("secrets enable pki");
}

vaultContainer.start();

String url = "http://" + vaultContainer.getHost() + ":" + vaultContainer.getPort();
return new StartResult(url, token,
new Closeable() {
@Override
public void close() {
vaultContainer.close();
}
});
}

private static class StartResult {
private final String url;
private final String clientToken;
private final Closeable closeable;

public StartResult(String url, String clientToken, Closeable closeable) {
this.url = url;
this.clientToken = clientToken;
this.closeable = closeable;
}
}

private static class FixedPortVaultContainer extends VaultContainer<FixedPortVaultContainer> {
OptionalInt fixedExposedPort;

public FixedPortVaultContainer(DockerImageName dockerImageName, OptionalInt fixedExposedPort) {
super(dockerImageName);
this.fixedExposedPort = fixedExposedPort;
}

@Override
protected void configure() {
super.configure();
if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), VAULT_EXPOSED_PORT);
} else {
addExposedPort(VAULT_EXPOSED_PORT);
}
}

public int getPort() {
if (fixedExposedPort.isPresent()) {
return fixedExposedPort.getAsInt();
}
return super.getMappedPort(VAULT_EXPOSED_PORT);
}
}
}
1 change: 1 addition & 0 deletions extensions/vault/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>deployment-spi</module>
<module>runtime</module>
<module>model</module>
</modules>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.quarkus.vault.runtime.config;

import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class DevServicesConfig {

/**
* If DevServices has been explicitly enabled or disabled. DevServices is generally enabled
* by default, unless there is an existing configuration present.
* <p>
* When DevServices is enabled Quarkus will attempt to automatically configure and start
* a database when running in Dev or Test mode and when Docker is running.
*/
@ConfigItem(defaultValue = "true")
public boolean enabled;

/**
* The container image name to use, for container based DevServices providers.
*/
@ConfigItem
public Optional<String> imageName;

/**
* Optional fixed port the dev service will listen to.
* <p>
* If not defined, the port will be chosen randomly.
*/
@ConfigItem
public OptionalInt port;

/**
* Should the Transit secret engine be enabled
*/
@ConfigItem(defaultValue = "false")
public boolean transitEnabled;

/**
* Should the PKI secret engine be enabled
*/
@ConfigItem(defaultValue = "false")
public boolean pkiEnabled;

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DevServicesConfig that = (DevServicesConfig) o;
return enabled == that.enabled && Objects.equals(imageName,
that.imageName) && Objects.equals(port,
that.port);
}

@Override
public int hashCode() {
return Objects.hash(enabled, imageName, port);
}

@Override
public String toString() {
return "DevServicesConfig{" +
"enabled=" + enabled +
", imageName=" + imageName +
", port=" + port +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ public class VaultBuildTimeConfig {
@ConfigDocSection
public HealthConfig health;

/**
* Dev services configuration.
*/
@ConfigItem
public DevServicesConfig devservices;

@Override
public String toString() {
return "VaultBuildTimeConfig{" +
"health=" + health +
", devservices=" + devservices +
'}';
}
}
4 changes: 4 additions & 0 deletions test-framework/junit5/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-client-deployment-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vault-deployment-spi</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
Expand Down
Loading

0 comments on commit be82634

Please sign in to comment.