diff --git a/.bettercodehub.yml b/.bettercodehub.yml
new file mode 100644
index 0000000..dc42046
--- /dev/null
+++ b/.bettercodehub.yml
@@ -0,0 +1 @@
+component_depth: 9
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..d3dd412
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,95 @@
+version: 2.1
+
+# -----------------------------------------------------------------------------
+# YAML Anchors
+# -----------------------------------------------------------------------------
+
+_anchors:
+ - &mvn-cache-key consul-{{ .Branch }}-{{ checksum "pom.xml" }}
+ - &mvn-cache-key-fallback consul-{{ .Branch }}
+
+ - &build-image cimg/openjdk:8.0
+
+ - &default-env
+ MAVEN_CONFIG_DIR: .circleci/mvn
+
+# -----------------------------------------------------------------------------
+# Jobs
+# -----------------------------------------------------------------------------
+
+jobs:
+
+ # jobs: build ---------------------------------------------------------------
+
+ build:
+ docker: [ image: *build-image ]
+ steps:
+ - checkout
+
+ - restore_cache:
+ keys: [ *mvn-cache-key, *mvn-cache-key-fallback ]
+
+ - run: mvn verify
+
+ - save_cache:
+ paths: [ ~/.m2 ]
+ key: *mvn-cache-key
+
+ # jobs: deploy --------------------------------------------------------------
+
+ deploy:
+ docker: [ image: *build-image ]
+ steps:
+ - checkout
+
+ - run:
+ name: Install GPG signing keys
+ environment:
+ <<: *default-env
+ GPG_KEY_FILENAME: codesigning.asc
+ command: |
+ openssl aes-256-cbc -d -a -in ${MAVEN_CONFIG_DIR}/${GPG_KEY_FILENAME}.enc -out ${MAVEN_CONFIG_DIR}/${GPG_KEY_FILENAME} -k "${ENCRYPTION_KEY}"
+ gpg --batch --fast-import ${MAVEN_CONFIG_DIR}/${GPG_KEY_FILENAME}
+
+ - restore_cache:
+ keys: [ *mvn-cache-key, *mvn-cache-key-fallback ]
+
+ - run:
+ name: mvn deploy
+ environment:
+ <<: *default-env
+ command: |
+ export GPG_TTY=$(tty)
+ mvn -B -s ${MAVEN_CONFIG_DIR}/settings.xml -Dstyle.color=always -Dmaven.install.skip deploy
+
+ - save_cache:
+ paths: [ ~/.m2 ]
+ key: *mvn-cache-key
+
+
+# -----------------------------------------------------------------------------
+# Workflows
+# -----------------------------------------------------------------------------
+
+workflows:
+
+ version: 2
+
+ # workflow: pr-check --------------------------------------------------------
+
+ pr-check:
+ jobs:
+ - build:
+ filters:
+ branches:
+ ignore: master
+
+ # workflow: master ----------------------------------------------------------
+
+ deploy:
+ jobs:
+ - deploy:
+ context: ossrh
+ filters:
+ branches:
+ only: master
diff --git a/.circleci/mvn/codesigning.asc.enc b/.circleci/mvn/codesigning.asc.enc
new file mode 100644
index 0000000..13b708f
--- /dev/null
+++ b/.circleci/mvn/codesigning.asc.enc
@@ -0,0 +1,157 @@
+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
diff --git a/.circleci/mvn/settings.xml b/.circleci/mvn/settings.xml
new file mode 100644
index 0000000..99cc307
--- /dev/null
+++ b/.circleci/mvn/settings.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ ossrh
+ ${env.OSSRH_USERNAME}
+ ${env.OSSRH_PASSWORD}
+
+
+
+
+
+ ossrh
+
+ true
+
+
+ gpg
+ ${env.GPG_KEYNAME}
+ ${env.GPG_PASSPHRASE}
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index 8360da6..2f7896d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1 @@
-.settings/
-.classpath
-.project
target/
diff --git a/.lgtm.yml b/.lgtm.yml
new file mode 100644
index 0000000..21c370a
--- /dev/null
+++ b/.lgtm.yml
@@ -0,0 +1,7 @@
+extraction:
+ java:
+ index:
+ java_version: 8
+ xml_mode: ALL
+ maven:
+ version: 3.6.1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f61f3d1..d1a528b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,26 +6,28 @@ and this project adheres to [semantic versioning](https://semver.org/spec/v2.0.0
## Unreleased
-### Changed
-
-- Bump docker-java to 3.1.1
-- Rewritten discovery to utilise containers internal IPs and ports
-- Renamed `@EnableConsul` to `@TCPHealthCheck` and its property `exposedPort` to `port`
-- Renamed property `exposedPort` in `@WebHealthCheck` to `port`
-- `@UseConsulDns` now sets the Consul container IP (instead of the Docker bridge one) as primary DNS for your containers and this makes it usable on MacOS
+## 0.1.1 - (11 Nov 2020)
+### Changed
-### Removed
-
-- Goodbye to gliderlabs/registrator
-- Goodbye to `qzagarese` in package names :-)
+ - Bumped `lombok` to version `1.8.16`
+ - Bumped `jackson` to version `2.11.3`
+ - Bumped `httpcore` to version `4.4.13`
+ - Introduced new system property called `consul.image` for overriding the default DockerHub Consul image
+ - Minor formatting changes
-## 1.0.1 [01 Mar 2019]
+## [0.1.0] - (01 Mar 2019)
-### Added
+### Changed
-- New boolean flag `-Dconsul.dns.enabled` to disable consul DNS (fixes [#12](https://github.com/qzagarese/dockerunit/issues/12))
+ - Bump docker-java to 3.1.1
+ - Rewritten discovery to utilise containers internal IPs and ports
+ - Renamed `@EnableConsul` to `@TCPHealthCheck` and its property `exposedPort` to `port`
+ - Renamed property `exposedPort` in `@WebHealthCheck` to `port`
+ - `@UseConsulDns` now sets the Consul container IP (instead of the Docker bridge one) as primary DNS for your containers and this makes it usable on MacOS
-## 1.0.0 [06 Feb 2019]
+### Removed
-_Initial Release_
+ - Goodbye to gliderlabs/registrator
+ - Goodbye to `qzagarese` in package names :-)
+ - New boolean flag `-Dconsul.dns.enabled` to disable Consul DNS (fixes [#12](https://github.com/qzagarese/dockerunit/issues/12))
diff --git a/LICENSE.md b/LICENSE
similarity index 100%
rename from LICENSE.md
rename to LICENSE
diff --git a/README.md b/README.md
index 1a88529..04ffc63 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,36 @@
-dockerunit-consul - A Consul based discovery provider for Dockerunit
-===========================================================================
+[](https://circleci.com/gh/dockerunit/dockerunit-consul/tree/master)
+
+[](https://app.codacy.com/project/dockerunit/dockerunit-consul/dashboard)
+
+[](https://scan.coverity.com/projects/dockerunit-dockerunit-consul)
+
+[](https://choosealicense.com/licenses/apache-2.0/)
+
+[](https://app.codacy.com/project/dockerunit/dockerunit-consul/dashboard)
+
+[](https://bettercodehub.com/)
+
+[](https://lgtm.com/projects/g/dockerunit/dockerunit-consul/context:java)
+
+[](https://lgtm.com/projects/g/dockerunit/dockerunit-consul/alerts)
+
+[](https://search.maven.org/search?q=g:com.github.dockerunit%20AND%20a:dockerunit-consul&core=gav)
+
+[](https://www.javadoc.io/doc/com.github.dockerunit/dockerunit-consul)
+
+[](https://oss.sonatype.org/index.html#nexus-search;gav~com.github.dockerunit~dockerunit-consul~~~)
+
+[](https://discordapp.com/channels/587583543081959435/587583543081959437)
This module allows you to configure the discovery phase for the testing of your services.
-Service Discovery is necessary for dockerunit to start and for your tests to execute successfully.
+Service Discovery is necessary for dockerunit to start and for your tests to execute successfully.
Usage (set `dockerunit.consul.version` property to the version you intend to use):
```xml
com.github.dockerunit
dockerunit-consul
- ${dockerunit.consul.version}
+ ${dockerunit.version}
test
```
@@ -17,27 +38,26 @@ Usage (set `dockerunit.consul.version` property to the version you intend to use
Discovery configuration can be specified by means of the following Java annotations:
-- `@TCPHealthCheck`: A basic health-check that probes your container to check if it's
+- `@TCPHealthCheck`: A basic health-check that probes your container to check if it's
able to accept TCP connections.
-- `@WebHealthCheck`: Health-check for HTTP/HTTPS services. Consul will consider the health-check as passing only
+- `@WebHealthCheck`: Health-check for HTTP/HTTPS services. Consul will consider the health-check as passing only
when it returns a 200 HTTP status code.
-- `@UseConsulDns`: Injects the Consul container IP as a DNS server for your container(s). This allows
-your services to reference other services by using their `dockerunit name`, which is the value
-that you have set in the corresponding `@Named` annotation.
+- `@UseConsulDns`: Injects the Consul container IP as a DNS server for your container(s). This allows
+your services to reference other services by using their `dockerunit name`, which is the value
+that you have set in the corresponding `@Svc` annotation.
The last annotation can be better explained with an example.
Say you have two services: `service-a` and `service-b`.
Our test only talks to `service-a` which is a client of `service-b`.
Both services listen on port `8080`.
-
+
Hereafter the dockerunit descriptors for the two services:
```java
-@Named("service-a")
-@Image("service-a-image")
+@Svc(name="service-a", image="service-a-image")
@WebHealthCheck(port=8080)
-@PortBinding(exposedPort=8080, hostPort=8080)
+@PublishPort(host=8080, container=8080)
@UseConsulDns
public class DescriptorForA{}
@@ -46,8 +66,12 @@ public class DescriptorForA{}
@WebHealthCheck(port=8080)
public class DescriptorForB{}
```
-- Because `service-a` is using `@UseConsulDns`, it can reference `service-b`
+- Because `service-a` is using `@UseConsulDns`, it can reference `service-b`
by using the `service-b.service.consul` name.
-- `service-b` is not declaring any `@PortBinding` because our test does not need to talk to it.
-Only `service-a` will talk to it, but it can do it by using the container IP to which
-the `service-b.service.consul` name resolves to.
+- `service-b` is not declaring any `@PublishPort` because our test does not need to talk to it.
+Only `service-a` will talk to it, but it can do it by using the container IP to which
+the `service-b.service.consul` name resolves to.
+
+---
+By default, the `dockerunit-consul` module uses Consul Docker image from DockerHub.
+To override the default using a different Consul Docker image the System property `consul.image` can be provided.
diff --git a/pom.xml b/pom.xml
index 6c6d2bd..4931388 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,83 +1,134 @@
-
+
+
+
4.0.0
com.github.dockerunit
dockerunit-parent
- 2.0.0-SNAPSHOT
+ 0.2.0-SNAPSHOT
+
dockerunit-consul
- 2.0.0-SNAPSHOT
- dockerunit-consul
Java Framework for testing of dockerised applications and services. Consul based service discovery package.
https://github.com/dockerunit/dockerunit-consul
-
-
-
- Quirino Zagarese
-
-
- Pedro Nuno Santos
-
-
- Vincenzo Candela
-
-
- Francesco Sorice
-
-
+
+
+ scm:https://github.com/dockerunit/dockerunit-consul.git
+ scm:https://github.com/dockerunit/dockerunit-consul.git
+ https://github.com/dockerunit/dockerunit-consul.git
+
+
- 2.0.0-SNAPSHOT
- 1.16.6
+ 1.18.16
+ 2.11.3
+ 3.2.7
+ 4.5.12
+ 4.4.13
-
-
- The Apache License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.txt
-
-
+
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ ${jackson.version}
+ import
+ pom
+
+
+
-
- com.github.dockerunit
- dockerunit-core
- ${dockerunit.core.version}
-
org.projectlombok
lombok
${lombok.version}
provided
+
+ ${project.groupId}
+ dockerunit-core
+ ${project.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.github.docker-java
+ docker-java-api
+ ${docker-java.version}
+
+
+ com.github.docker-java
+ docker-java-core
+ ${docker-java.version}
+ runtime
+
+
+ org.apache.httpcomponents
+ httpcore
+ ${httpcore.version}
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclient.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ runtime
+
+
+
+ ossrh-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots
+
+ true
+
+
+
+
maven-compiler-plugin
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
+ maven-surefire-plugin
-
- maven-source-plugin
+ org.jacoco
+ jacoco-maven-plugin
-
- maven-javadoc-plugin
+ maven-source-plugin
-
- maven-gpg-plugin
+ maven-javadoc-plugin
-
+
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDescriptor.java b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDescriptor.java
index b42724e..70dea16 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDescriptor.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDescriptor.java
@@ -1,5 +1,11 @@
package com.github.dockerunit.discovery.consul;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Logger;
+
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.HostConfig;
@@ -9,42 +15,37 @@
import com.github.dockerunit.core.annotation.Svc;
import com.github.dockerunit.discovery.consul.annotation.UseConsulDns;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.logging.Logger;
-
-import static com.github.dockerunit.discovery.consul.ConsulDiscoveryConfig.CONSUL_DNS_ENABLED_DEFAULT;
-import static com.github.dockerunit.discovery.consul.ConsulDiscoveryConfig.CONSUL_DNS_ENABLED_PROPERTY;
-
-@Svc(name = "consul", image = "consul:1.4.4")
+@Svc(name = "consul", image = "")
public class ConsulDescriptor {
+ private static final Logger logger = Logger.getLogger(ConsulDescriptor.class.getSimpleName());
static final int CONSUL_DNS_PORT = 53;
static final int CONSUL_PORT = 8500;
- private static final Logger logger = Logger.getLogger(ConsulDescriptor.class.getSimpleName());
-
-
@ContainerBuilder
public CreateContainerCmd setup(CreateContainerCmd cmd) {
- List ports = new ArrayList<>(Arrays.asList(cmd.getExposedPorts()));
+ List ports = new ArrayList<>(Arrays.asList(Objects.requireNonNull(cmd.getExposedPorts())));
ExposedPort consulPort = ExposedPort.tcp(CONSUL_PORT);
ports.add(consulPort);
-
- Ports bindings = cmd.getHostConfig().getPortBindings();
+
+ Ports bindings = Objects.requireNonNull(cmd.getHostConfig()).getPortBindings();
if (bindings == null) {
bindings = new Ports();
}
- boolean enableDnsFlag = Boolean.parseBoolean(System.getProperty(CONSUL_DNS_ENABLED_PROPERTY, CONSUL_DNS_ENABLED_DEFAULT));
+ boolean enableDnsFlag = Boolean.parseBoolean(
+ System.getProperty(
+ ConsulDiscoveryConfig.CONSUL_DNS_ENABLED_PROPERTY,
+ ConsulDiscoveryConfig.CONSUL_DNS_ENABLED_DEFAULT)
+ );
- if(enableDnsFlag) {
+ if (enableDnsFlag) {
activateDns(ports);
} else {
- logger.warning("Consul DNS has been disabled. Usages of @" + UseConsulDns.class.getSimpleName() + " will be ignored.");
+ logger.warning("Consul DNS has been disabled. Usages of @" + UseConsulDns.class.getSimpleName()
+ + " will be ignored.");
}
bindings.bind(consulPort, Binding.bindPort(8500));
@@ -53,13 +54,19 @@ public CreateContainerCmd setup(CreateContainerCmd cmd) {
return cmd.withHostConfig(hc)
.withExposedPorts(ports)
+ .withImage(
+ System.getProperty(
+ ConsulDiscoveryConfig.CONSUL_IMAGE,
+ ConsulDiscoveryConfig.CONSUL_DEFAULT_IMAGE
+ )
+ )
.withCmd("sh", "-c", "exec consul agent -dev -client=0.0.0.0 -enable-script-checks -dns-port=53");
-
}
private void activateDns(List ports) {
ExposedPort dnsPort = ExposedPort.udp(CONSUL_DNS_PORT);
ports.add(dnsPort);
}
+
}
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryConfig.java b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryConfig.java
index 00f91ab..74edba6 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryConfig.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryConfig.java
@@ -1,21 +1,26 @@
package com.github.dockerunit.discovery.consul;
-import com.github.dockerunit.core.annotation.WithSvc;
-
import static com.github.dockerunit.discovery.consul.ConsulDiscoveryConfig.CONSUL_CONTAINER_NAME;
-@WithSvc(svc =ConsulDescriptor.class, containerNamePrefix =CONSUL_CONTAINER_NAME)
+import com.github.dockerunit.core.annotation.WithSvc;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@WithSvc(svc = ConsulDescriptor.class, containerNamePrefix = CONSUL_CONTAINER_NAME)
public class ConsulDiscoveryConfig {
- public static final String DOCKER_BRIDGE_IP_PROPERTY = "docker.bridge.ip";
- public static final String DOCKER_BRIDGE_IP_DEFAULT = "172.17.42.1";
- public static final String DOCKER_HOST_PROPERTY = "docker.host";
- public static final String SERVICE_DISCOVERY_TIMEOUT = "service.discovery.timeout";
- public static final String SERVICE_DISCOVERY_TIMEOUT_DEFAULT = "30";
- public static final String CONSUL_POLLING_PERIOD = "consul.polling.period";
- public static final String CONSUL_POLLING_PERIOD_DEFAULT = "1";
- public static final String CONSUL_DNS_ENABLED_PROPERTY = "consul.dns.enabled";
- public static final String CONSUL_DNS_ENABLED_DEFAULT = "true";
- public static final String CONSUL_CONTAINER_NAME = "consul";
+ public static final String DOCKER_BRIDGE_IP_PROPERTY = "docker.bridge.ip";
+ public static final String DOCKER_BRIDGE_IP_DEFAULT = "172.17.42.1";
+ public static final String DOCKER_HOST_PROPERTY = "docker.host";
+ public static final String SERVICE_DISCOVERY_TIMEOUT = "service.discovery.timeout";
+ public static final String SERVICE_DISCOVERY_TIMEOUT_DEFAULT = "30";
+ public static final String CONSUL_POLLING_PERIOD = "consul.polling.period";
+ public static final String CONSUL_POLLING_PERIOD_DEFAULT = "1";
+ public static final String CONSUL_DNS_ENABLED_PROPERTY = "consul.dns.enabled";
+ public static final String CONSUL_DNS_ENABLED_DEFAULT = "true";
+ public static final String CONSUL_CONTAINER_NAME = "consul";
+ public static final String CONSUL_IMAGE = "consul.image";
+ public static final String CONSUL_DEFAULT_IMAGE = "consul:1.4.4";
}
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProvider.java b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProvider.java
index ff66140..e9518b5 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProvider.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProvider.java
@@ -83,8 +83,11 @@ private Service doDiscovery(Service s) {
Set withPorts = s.getInstances().stream()
.map(si -> {
InspectContainerResponse r = dockerClient.inspectContainerCmd(si.getContainerId()).exec();
- return si.withPort(findPort(r, records).orElse(0))
- .withIp(DOCKER_HOST)
+ return si.withGatewayPort(findPort(r, records).orElse(0))
+ .withContainerPort(records.stream().findFirst().map(sr -> sr.getPort()).orElse(0))
+ .withGatewayAddress(DOCKER_HOST)
+ .withContainerName(r.getName())
+ .withContainerIp(ContainerUtils.extractBridgeIpAddress(r.getNetworkSettings()).orElse(""))
.withStatus(ServiceInstance.Status.DISCOVERED)
.withStatusDetails("Discovered via consul");
}).collect(Collectors.toSet());
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProviderFactory.java b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProviderFactory.java
index 3a3b992..6e65c70 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProviderFactory.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ConsulDiscoveryProviderFactory.java
@@ -5,9 +5,9 @@
public class ConsulDiscoveryProviderFactory implements DiscoveryProviderFactory {
- @Override
- public DiscoveryProvider getProvider() {
- return new ConsulDiscoveryProvider();
- }
+ @Override
+ public DiscoveryProvider getProvider() {
+ return new ConsulDiscoveryProvider();
+ }
}
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ConsulRegistrator.java b/src/main/java/com/github/dockerunit/discovery/consul/ConsulRegistrator.java
index 1215496..3d21230 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ConsulRegistrator.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ConsulRegistrator.java
@@ -1,27 +1,24 @@
package com.github.dockerunit.discovery.consul;
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Container;
import org.apache.http.HttpResponse;
-import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPut;
-import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import java.util.logging.Logger;
-
public class ConsulRegistrator {
public static final String ACCEPT = "Accept";
@@ -41,7 +38,6 @@ public class ConsulRegistrator {
private final ObjectWriter objectWriter;
private final ConsulServiceFactory svcFactory;
-
public ConsulRegistrator(DockerClient client, int pollingPeriod, String consulHost, int consulPort) {
this.client = client;
this.pollingPeriod = pollingPeriod;
@@ -65,7 +61,7 @@ private void onDetectHook(Container container) {
}
private void onDestroyHook(Container container) {
- if(container != null && services.containsKey(container.getId())) {
+ if (container != null && services.containsKey(container.getId())) {
deregisterSvc(services.get(container.getId()));
trackers.remove(container.getId());
services.remove(container.getId());
@@ -77,7 +73,7 @@ private void registerSvc(ConsulService svc) {
try {
executePut("/v1/agent/service/register", objectWriter.writeValueAsString(svc),
errorMessage,
- ex -> {
+ ex -> {
throw new RuntimeException(errorMessage.get(), ex);
});
} catch (JsonProcessingException e) {
@@ -86,18 +82,22 @@ private void registerSvc(ConsulService svc) {
}
private void deregisterSvc(ConsulService svc) {
- Supplier errorMessage = () ->"Could not deregister container " + svc.getContainerId() + " from Consul.";
+ Supplier errorMessage = () -> "Could not deregister container " + svc.getContainerId()
+ + " from Consul.";
executePut("/v1/agent/service/deregister/" + svc.getId(), null,
errorMessage,
ex -> logger.info("Consul has already stopped. Service de-registration aborted."));
}
- private void executePut(String endpoint, String body, Supplier errorMessage, Consumer onFailure) {
+ private void executePut(String endpoint,
+ String body,
+ Supplier errorMessage,
+ Consumer onFailure) {
HttpPut put = new HttpPut("http://" + host + ":" + port + endpoint);
put.setHeader(ACCEPT, APPLICATION_JSON);
put.setHeader(CONTENT_TYPE, APPLICATION_JSON);
- if(body != null) {
+ if (body != null) {
try {
put.setEntity(new StringEntity(body));
} catch (UnsupportedEncodingException e) {
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ConsulServiceFactory.java b/src/main/java/com/github/dockerunit/discovery/consul/ConsulServiceFactory.java
index 8b80ba1..fbfc1cc 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ConsulServiceFactory.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ConsulServiceFactory.java
@@ -1,18 +1,19 @@
package com.github.dockerunit.discovery.consul;
-import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.command.InspectContainerResponse;
-import com.github.dockerunit.discovery.consul.annotation.TCPHealthCheck;
-import com.github.dockerunit.discovery.consul.annotation.WebHealthCheck;
-
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.command.InspectContainerResponse;
+import com.github.dockerunit.discovery.consul.annotation.TCPHealthCheck;
+import com.github.dockerunit.discovery.consul.annotation.WebHealthCheck;
+
public class ConsulServiceFactory {
private static final String SERVICE_NAME_PREFIX = "SERVICE_";
@@ -21,7 +22,8 @@ public class ConsulServiceFactory {
+ "health-check by using @" + WebHealthCheck.class.getSimpleName()
+ " or @" + TCPHealthCheck.class.getSimpleName();
- private static final String SVC_HEALTH_CHECK_PORT_NOT_FOUND_MSG = "No health-check port definition detected" + DEFINE_HEALTH_CHECK_MSG;
+ private static final String SVC_HEALTH_CHECK_PORT_NOT_FOUND_MSG =
+ "No health-check port definition detected" + DEFINE_HEALTH_CHECK_MSG;
private static final String SVC_NAME_NOT_FOUND_ERROR_MSG = "No svc name detected. " + DEFINE_HEALTH_CHECK_MSG;
private static final String DOCKERUNIT = "dockerunit";
@@ -42,13 +44,19 @@ public ConsulService createSvc(String containerId) {
InspectContainerResponse r = client.inspectContainerCmd(containerId).exec();
Map options = buildKeyValueMap(r.getConfig().getLabels(), r.getConfig().getEnv());
- String svcName = null;
+
+ String svcName;
try {
- svcName = URLEncoder.encode(findName(options).orElseThrow(() -> new RuntimeException(SVC_NAME_NOT_FOUND_ERROR_MSG)), "UTF-8");
+ svcName = URLEncoder.encode(
+ findName(options).orElseThrow(() -> new RuntimeException(SVC_NAME_NOT_FOUND_ERROR_MSG)),
+ StandardCharsets.UTF_8.name()
+ );
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Invalid svc name detected.", e);
}
- Integer port = extractHealthCheckPort(options).orElseThrow(() -> new RuntimeException(SVC_HEALTH_CHECK_PORT_NOT_FOUND_MSG));
+
+ Integer port = extractHealthCheckPort(options)
+ .orElseThrow(() -> new RuntimeException(SVC_HEALTH_CHECK_PORT_NOT_FOUND_MSG));
String address = ContainerUtils.extractBridgeIpAddress(r.getNetworkSettings())
.orElseThrow(() -> new RuntimeException(SVC_ADDRESS_NOT_FOUND_ERROR_MSG));
@@ -63,10 +71,10 @@ public ConsulService createSvc(String containerId) {
}
private Map buildKeyValueMap(Map labels, String[] env) {
- if(env == null) {
+ if (env == null) {
return labels != null ? labels : new HashMap<>();
}
- if(labels == null) {
+ if (labels == null) {
return new HashMap<>();
}
Map keyValueMap = labels.entrySet()
@@ -83,8 +91,9 @@ private ConsulService.ConsulCheck buildCheck(Map options, String
ConsulService.ConsulCheck.ConsulCheckBuilder builder = ConsulService.ConsulCheck.builder();
builder.interval(options.get(SERVICE_CHECK_INTERVAL));
builder.http(interpolateCheckScript(
- options.getOrDefault(SERVICE_CHECK_HTTP, null), address, port));
- builder.tcp(interpolateCheckScript(options.getOrDefault(SERVICE_CHECK_TCP, null), address,port));
+ options.getOrDefault(SERVICE_CHECK_HTTP, null), address, port)
+ );
+ builder.tcp(interpolateCheckScript(options.getOrDefault(SERVICE_CHECK_TCP, null), address, port));
builder.method(options.getOrDefault(SERVICE_CHECK_METHOD, null));
builder.tlsSkipVerify(true);
builder.status(options.getOrDefault(SERVICE_CHECK_INITIAL_STATUS, null));
@@ -99,10 +108,10 @@ private String interpolateCheckScript(String script, String address, Integer por
private Optional extractHealthCheckPort(Map options) {
return options.entrySet().stream()
- .filter(kv -> hasServiceName(kv))
+ .filter(this::hasServiceName)
.findFirst()
.map(kv -> extractPortString(kv.getKey()))
- .map(port -> asInteger(port));
+ .map(this::asInteger);
}
private Integer asInteger(String port) {
@@ -115,9 +124,9 @@ private Integer asInteger(String port) {
private Optional findName(Map options) {
return options.entrySet().stream()
- .filter(kv -> hasServiceName(kv))
- .findFirst()
- .map(kv -> kv.getValue());
+ .filter(this::hasServiceName)
+ .findFirst()
+ .map(Map.Entry::getValue);
}
private boolean hasServiceName(Map.Entry kv) {
@@ -126,7 +135,7 @@ private boolean hasServiceName(Map.Entry kv) {
}
private String extractPortString(String s) {
- if (! (s.lastIndexOf("_") > (s.indexOf("_") + 1))) {
+ if (!(s.lastIndexOf("_") > (s.indexOf("_") + 1))) {
return null;
}
return s.substring(s.indexOf("_") + 1, s.lastIndexOf("_"));
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ContainerTracker.java b/src/main/java/com/github/dockerunit/discovery/consul/ContainerTracker.java
index a58f877..c4948cc 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ContainerTracker.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ContainerTracker.java
@@ -1,15 +1,15 @@
package com.github.dockerunit.discovery.consul;
-import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.model.Container;
-
-import java.util.Arrays;
+import java.util.Collections;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Consumer;
import java.util.logging.Logger;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.model.Container;
+
public class ContainerTracker {
private static final Logger logger = Logger.getLogger(ContainerTracker.class.getSimpleName());
@@ -17,11 +17,19 @@ public class ContainerTracker {
private final DockerClient client;
private final String containerId;
private final int pollingPeriod;
+
private final Consumer onDetect;
private final Consumer onDestroy;
+
private Container latestContainer;
- public ContainerTracker(DockerClient client, String containerId, int pollingPeriod, Consumer onDetect, Consumer onDestroy) {
+ public ContainerTracker(
+ DockerClient client,
+ String containerId,
+ int pollingPeriod,
+ Consumer onDetect,
+ Consumer onDestroy
+ ) {
this.client = client;
this.containerId = containerId;
this.pollingPeriod = pollingPeriod;
@@ -37,22 +45,24 @@ public ContainerTracker(DockerClient client, String containerId, int pollingPeri
}
private void init() {
- latestContainer = findContainer().orElseThrow(() -> new RuntimeException("Could not detect container with id: " + containerId));
+ latestContainer = findContainer().orElseThrow(() -> new RuntimeException(
+ "Could not detect container with id: " + containerId));
onDetect.accept(latestContainer);
track();
}
private Optional findContainer() {
- return client.listContainersCmd().withIdFilter(Arrays.asList(containerId)).exec()
- .stream()
- .findFirst();
+ return client.listContainersCmd().withIdFilter(Collections.singletonList(containerId)).exec()
+ .stream()
+ .findFirst();
}
private void track() {
TimerTask repeatedTask = new TimerTask() {
public void run() {
try {
- latestContainer = findContainer().orElseThrow(() -> new RuntimeException("Container " + containerId + " has been removed."));
+ latestContainer = findContainer()
+ .orElseThrow(() -> new RuntimeException("Container " + containerId + " has been removed."));
} catch (Exception e) {
this.cancel();
onDestroy.accept(latestContainer);
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ContainerUtils.java b/src/main/java/com/github/dockerunit/discovery/consul/ContainerUtils.java
index f59fe35..bcb991d 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ContainerUtils.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ContainerUtils.java
@@ -1,20 +1,22 @@
package com.github.dockerunit.discovery.consul;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.ContainerNetwork;
import com.github.dockerjava.api.model.ContainerNetworkSettings;
import com.github.dockerjava.api.model.NetworkSettings;
import com.github.dockerunit.core.internal.docker.DefaultDockerClientProvider;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Optional;
-
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ContainerUtils {
private static final com.github.dockerjava.api.DockerClient dockerClient = new DefaultDockerClientProvider().getClient();
-
public static Optional extractBridgeIpAddress(ContainerNetworkSettings settings) {
return extractIp(settings.getNetworks());
}
@@ -29,22 +31,20 @@ public static Optional extractMappedPort(int port, NetworkSettings netw
.map(Map.Entry::getValue)
.filter(bindings -> bindings != null && bindings.length > 0)
.findFirst()
- .flatMap(bindings -> parsePort(bindings[0].getHostPortSpec()));
+ .flatMap(bindings -> parsePort(bindings[0].getHostPortSpec()));
}
public static Container getConsulContainer() {
return dockerClient.listContainersCmd().exec().stream()
- .filter(c -> isConsul(c))
+ .filter(ContainerUtils::isConsul)
.findFirst()
.orElseThrow(() -> new RuntimeException(("Could not detect the Consul container.")));
}
-
private static boolean isConsul(Container c) {
- return Arrays.stream(c.getNames()).anyMatch(s -> s.equals("/" + ConsulDiscoveryConfig.CONSUL_CONTAINER_NAME));
+ return Arrays.asList(c.getNames()).contains("/" + ConsulDiscoveryConfig.CONSUL_CONTAINER_NAME);
}
-
private static Optional extractIp(Map networks) {
return Optional.ofNullable(networks.entrySet().stream()
.filter(network -> "bridge".equals(network.getKey()))
@@ -61,4 +61,5 @@ private static Optional parsePort(String s) {
return Optional.empty();
}
}
+
}
diff --git a/src/main/java/com/github/dockerunit/discovery/consul/ServiceRecord.java b/src/main/java/com/github/dockerunit/discovery/consul/ServiceRecord.java
index ab33c93..712d020 100644
--- a/src/main/java/com/github/dockerunit/discovery/consul/ServiceRecord.java
+++ b/src/main/java/com/github/dockerunit/discovery/consul/ServiceRecord.java
@@ -4,78 +4,81 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
-
+import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
-import lombok.experimental.Wither;
+import lombok.NoArgsConstructor;
+import lombok.With;
-@Wither
+@With
@Getter
@AllArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ServiceRecord {
- @JsonProperty("ServiceName")
- private final String name;
-
- @JsonProperty("Address")
- private final String address;
-
- @JsonProperty("ServicePort")
- private final int port;
-
- @JsonProperty("ServiceAddress")
- private String serviceAddress;
-
- @JsonProperty("Service")
- private final Service service;
-
- @JsonProperty("Checks")
- private final List checks;
-
-
- public String getName() {
- return service != null ? service.getName() : name;
- }
-
- public String getAddress() {
- return service != null ? service.getAddress() : address;
- }
-
- public int getPort() {
- return service != null ? service.getPort() : port;
- }
-
- @Wither
- @Getter
- @AllArgsConstructor
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Service {
-
- @JsonProperty("Service")
- private final String name;
-
- @JsonProperty("Address")
- private final String address;
-
- @JsonProperty("Port")
- private final int port;
-
- }
-
- @Wither
- @Getter
- @AllArgsConstructor
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Check {
-
- public static final String PASSING = "passing";
-
- @JsonProperty("Name")
- private final String name;
-
- @JsonProperty("Status")
- private final String status;
-
- }
+ @JsonProperty("ServiceName")
+ private String name;
+
+ @JsonProperty("Address")
+ private String address;
+
+ @JsonProperty("ServicePort")
+ private int port;
+
+ @JsonProperty("ServiceAddress")
+ private String serviceAddress;
+
+ @JsonProperty("Service")
+ private Service service;
+
+ @JsonProperty("Checks")
+ private List checks;
+
+ public String getName() {
+ return service != null ? service.getName() : name;
+ }
+
+ public String getAddress() {
+ return service != null ? service.getAddress() : address;
+ }
+
+ public int getPort() {
+ return service != null ? service.getPort() : port;
+ }
+
+ @With
+ @Getter
+ @AllArgsConstructor
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Service {
+
+ @JsonProperty("Service")
+ private String name;
+
+ @JsonProperty("Address")
+ private String address;
+
+ @JsonProperty("Port")
+ private int port;
+
+ }
+
+ @With
+ @Getter
+ @AllArgsConstructor
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Check {
+
+ public static final String PASSING = "passing";
+
+ @JsonProperty("Name")
+ private String name;
+
+ @JsonProperty("Status")
+ private String status;
+
+ }
}