Skip to content

Commit

Permalink
Feat/add consul testcontainer (#650)
Browse files Browse the repository at this point in the history
* (feat) adding test container support for Consul

* (feat) adding test container support for Consul

* (feat) adding test container support for Consul

* (feat) adding test container support for Consul
  • Loading branch information
nsteffan authored Jul 5, 2024
1 parent 066f53d commit e431165
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 0 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ boms-testcontainers = { module = "org.testcontainers:testcontainers-bom", versio

managed-opensearch-testcontainers = { module = "org.opensearch:opensearch-testcontainers", version.ref = "managed-opensearch-testcontainers" }


managed-testcontainers-core = { module = "org.testcontainers:testcontainers", version.ref = "managed-testcontainers" }
managed-testcontainers-consul = { module = "org.testcontainers:consul", version.ref = "managed-testcontainers" }
managed-testcontainers-elasticsearch = { module = "org.testcontainers:elasticsearch", version.ref = "managed-testcontainers" }
managed-testcontainers-jdbc = { module = "org.testcontainers:jdbc", version.ref = "managed-testcontainers" }
managed-testcontainers-hivemq = { module = "org.testcontainers:hivemq", version.ref = "managed-testcontainers" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ include 'test-resources-rabbitmq'
include 'test-resources-server'
include 'test-resources-testcontainers'
include 'test-resources-hashicorp-vault'
include 'test-resources-hashicorp-consul'

extensionModules.each {
String projectName = "test-resources-extensions-$it"
Expand Down
15 changes: 15 additions & 0 deletions src/main/docs/guide/modules-hashicorp-consul.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Consul support will automatically start a https://www.consul.io/[Hashicorp Consul container] and provide the value of `consul.client.default-zone` property.

- The default image can be overwritten by setting the `test-resources.containers.hashicorp-consul.image-name` property.
- Properties should be inserted into Hashicorp Consul at startup by adding the config:
[configuration]
----
test-resources:
containers:
hashicorp-consul:
image-name: "hashicorp/consul:1.9.3"
properties:
- "key1=value1"
- "key2=value2"
----
22 changes: 22 additions & 0 deletions test-resources-hashicorp-consul/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'io.micronaut.build.internal.testcontainers-module'
}

description = """
Provides support for launching a Consul test container.
"""

micronautBuild {
binaryCompatibility {
enabledAfter '2.6.0'
}
}

dependencies {
implementation(libs.managed.testcontainers.consul)

testImplementation(testFixtures(project(":micronaut-test-resources-testcontainers")))
testImplementation(libs.micronaut.discovery)
testImplementation(mn.micronaut.http.client)
testImplementation(mnReactor.micronaut.reactor)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2017-2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.testresources.consul;

import io.micronaut.testresources.testcontainers.AbstractTestContainersProvider;
import org.testcontainers.consul.ConsulContainer;
import org.testcontainers.utility.DockerImageName;

import java.util.*;

/**
* A test resource provider which will spawn a Consul test container.
*/
public class ConsulTestResourceProvider extends AbstractTestContainersProvider<ConsulContainer> {

public static final String PREFIX = "consul.client";
public static final String PROPERTY_CONSUL_CLIENT_HOST = "consul.client.host";
public static final String PROPERTY_CONSUL_CLIENT_PORT = "consul.client.port";
public static final String PROPERTY_CONSUL_CLIENT_DEFAULT_ZONE = "consul.client.default-zone";

public static final List<String> RESOLVABLE_PROPERTIES_LIST = Collections.unmodifiableList(Arrays.asList(
PROPERTY_CONSUL_CLIENT_HOST,
PROPERTY_CONSUL_CLIENT_PORT
));
public static final String HASHICORP_CONSUL_KV_PROPERTIES_KEY = "containers.hashicorp-consul.kv-properties";
public static final String SIMPLE_NAME = "hashicorp-consul";
public static final String DEFAULT_IMAGE = "hashicorp/consul";
public static final String DISPLAY_NAME = "Consul";

public static final int CONSUL_HTTP_PORT = 8500;

@Override
public List<String> getResolvableProperties(Map<String, Collection<String>> propertyEntries, Map<String, Object> testResourcesConfig) {
return RESOLVABLE_PROPERTIES_LIST;
}

@Override
public String getDisplayName() {
return DISPLAY_NAME;
}

@Override
protected String getSimpleName() {
return SIMPLE_NAME;
}

@Override
protected String getDefaultImageName() {
return DEFAULT_IMAGE;
}

@Override
protected ConsulContainer createContainer(DockerImageName imageName, Map<String, Object> requestedProperties, Map<String, Object> testResourcesConfig) {
ConsulContainer consulContainer = new ConsulContainer(imageName);
// Micronaut Discovery Consul will only listen to the default port 8500
consulContainer.setPortBindings(Collections.singletonList(CONSUL_HTTP_PORT + ":" + CONSUL_HTTP_PORT));

// Set startup properties
if (testResourcesConfig.containsKey(HASHICORP_CONSUL_KV_PROPERTIES_KEY)) {
@SuppressWarnings("unchecked")
List<String> properties = (List<String>) testResourcesConfig.get(HASHICORP_CONSUL_KV_PROPERTIES_KEY);
if(null != properties && !properties.isEmpty()) {
properties.stream().forEach((property) -> consulContainer.withConsulCommand("kv put " + property.replace("=", " ")));
}
}

return consulContainer;
}

@Override
protected Optional<String> resolveProperty(String propertyName, ConsulContainer container) {
if (PROPERTY_CONSUL_CLIENT_HOST.equals(propertyName)) {
return Optional.of(container.getHost());
} else if (PROPERTY_CONSUL_CLIENT_PORT.equals(propertyName)) {
return Optional.of(container.getMappedPort(CONSUL_HTTP_PORT).toString());
} else if (PROPERTY_CONSUL_CLIENT_DEFAULT_ZONE.equals(propertyName)) {
return Optional.of(container.getHost() + ":" + container.getMappedPort(CONSUL_HTTP_PORT));
}
return Optional.empty();
}

@Override
protected boolean shouldAnswer(String propertyName, Map<String, Object> properties, Map<String, Object> testResourcesConfig) {
return propertyName != null && propertyName.startsWith(PREFIX);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.micronaut.testresources.consul.ConsulTestResourceProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.micronaut.testresources.hashicorp.consul

import io.micronaut.context.annotation.Value
import io.micronaut.discovery.consul.client.v1.ConsulClient
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import io.micronaut.testresources.testcontainers.AbstractTestContainersSpec
import jakarta.inject.Inject
import org.testcontainers.DockerClientFactory
import reactor.core.publisher.Flux

@MicronautTest
class ConsulStartedTest extends AbstractTestContainersSpec {

@Value('${consul.client.host}')
String host

@Value('${consul.client.port}')
int port

@Value('${consul.client.default-zone}')
String defaultZone

@Inject
ConsulClient consulClient

@Override
String getScopeName() {
'consul'
}

def "automatically starts a Consul container"() {
given:
// host is different on different platforms (localhost on linux, but 127.0.0.1 on osx)
def dockerHost = DockerClientFactory.instance().dockerHostIpAddress()

expect:
dockerHost in ["localhost", "127.0.0.1"]
dockerHost == host
listContainers().collectMany { it.ports as List }.any { defaultZone == "$dockerHost:$it.publicPort" }
listContainers().collectMany { it.ports as List }.any { port == it.publicPort }
}

def "get consul kv properties"() {
given:
def testKeyKeyValues = Flux.from(consulClient.readValues("test-key")).blockFirst()
def testKey2KeyValues = Flux.from(consulClient.readValues("test-key2")).blockFirst()

expect:
"test-value" == new String(Base64.getDecoder().decode(testKeyKeyValues[0].value))
"test-value2" == new String(Base64.getDecoder().decode(testKey2KeyValues[0].value))
}
}
16 changes: 16 additions & 0 deletions test-resources-hashicorp-consul/src/test/resources/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
micronaut:
application:
name: consul-test
config-client:
enabled: true
consul:
client:
config:
enabled: true
default-zone: "localhost:8500"
test-resources:
containers:
hashicorp-consul:
kv-properties:
- "test-key=test-value"
- "test-key2=test-value2"
14 changes: 14 additions & 0 deletions test-resources-hashicorp-consul/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

0 comments on commit e431165

Please sign in to comment.