Skip to content

Commit

Permalink
Fix getting container information by using 'docker ps' instead of 'do…
Browse files Browse the repository at this point in the history
…cker-compose ps' command (#11)

Fix a bug: it was a failure in creating a service with some versions of DockerCompose. It is fixed by using 'docker ps' with custom 'format' flag instead of 'docker-compose ps' command
  • Loading branch information
saeidrastak authored Jan 16, 2022
1 parent 5af63f3 commit 82c00cf
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 38 deletions.
13 changes: 9 additions & 4 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ jobs:

runs-on: ubuntu-latest

strategy:
matrix:
java-version: [ 8, 11 ]

steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'adopt'
java-version: ${{ matrix.java-version }}
- name: Build with Maven
run: mvn test --file pom.xml
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<modelVersion>4.0.0</modelVersion>
<groupId>ir.sahab</groupId>
<version>1.3.0</version>
<version>1.3.1</version>
<artifactId>docker-compose-wrapper</artifactId>
<properties>
<java.version>1.8</java.version>
Expand Down
103 changes: 70 additions & 33 deletions src/main/java/ir/sahab/dockercomposer/DockerComposeRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -105,59 +106,59 @@ public void stopService(String serviceName) {
executeDockerCompose("stop", serviceName);
}

private Service createService(String name) {
List<String> serviceStateLines = executeDockerCompose("ps", name).getOutput().getLines();

if (serviceStateLines.size() < 3) {
throw new IllegalStateException("Unexpected output of docker-compose ps for service " + name
private Service createService(String serviceName) {
String serviceInfo = getContainerNameAndPorts(serviceName, projectName);
if (StringUtils.isEmpty(serviceInfo)) {
throw new IllegalStateException("Unexpected output of docker ps for service " + serviceName
+ ". This is mostly due to your container is terminated early.");
}

// The third line plus following lines are the state of docker service
StringBuilder serviceState = new StringBuilder();
for (int i = 2; i < serviceStateLines.size(); i++) {
serviceState.append(serviceStateLines.get(i).trim());
}
return parseServiceState(serviceState.toString(), name);
return createService(serviceInfo, serviceName);
}

/**
* Parses the docker-compose service state line and creates a service from the parsed result.
* Parses the 'docker ps' state line and creates a service from the parsed result.
*/
// We might later convert this to an strategy based on docker-compose version
private Service parseServiceState(String serviceState, String name) {
/* Docker-compose output is like this:
* zookeeper-test-1 ... 2888/tcp, 3888/tcp, 0.0.0.0:32768->2181/tcp
private Service createService(String serviceInfo, String serviceName) {
/* Service info is like this:
* zookeeper-test-1#2888/tcp, 3888/tcp, 0.0.0.0:32768->2181/tcp
* We want to extract these: zookeeper-test-1 as id and 2181 as the default port which
* is mapped to port 32768. The service externalIp is 0.0.0.0 which means local host.
*/
final Pattern portPattern
= Pattern.compile("((\\d+).(\\d+).(\\d+).(\\d+)):(\\d+)->(\\d+)/tcp");
final Pattern portPattern = Pattern.compile("((\\d+).(\\d+).(\\d+).(\\d+)):(\\d+)->(\\d+)/tcp");
final int externalPort = 6;
final int internalPort = 7;
final String externalIp = "127.0.0.1";

int firstSpace = serviceState.indexOf(' ');
String id = serviceState.substring(0, firstSpace);

Matcher matcher = portPattern.matcher(serviceState);
Map<Integer, Integer> portMappings = new HashMap<>();
// Each match will be a mapping from some internal port to some external port.
// The first published port will be considered as the default port.
while (matcher.find()) {
int exPort = Integer.parseInt(matcher.group(externalPort));
int inPort = Integer.parseInt(matcher.group(internalPort));
portMappings.put(inPort, exPort);
String[] serviceInfoParts = serviceInfo.split("#");
if (serviceInfoParts.length < 1 || serviceInfoParts.length > 2) {
throw new IllegalArgumentException("Invalid service info: " + serviceInfo);
}

// Extract container's internal IP and add service name to internal IP resolution into Java DNS.
String id = serviceInfoParts[0];
String internalIp;
try {
internalIp = getContainerIp(id);
NameServiceEditor.addHostnameToNameService(name, internalIp);
NameServiceEditor.addHostnameToNameService(serviceName, internalIp);
} catch (Exception e) {
throw new IllegalStateException("Could not set mapping of service name and internal IP", e);
}

return new Service(id, name, externalIp, internalIp, portMappings, this);
// Extract port mapping if defined for the container
Map<Integer, Integer> portMappings = new HashMap<>();
if (serviceInfoParts.length == 2) {
Matcher matcher = portPattern.matcher(serviceInfoParts[1]);
// Each match will be a mapping from some internal port to some external port.
// The first published port will be considered as the default port.
while (matcher.find()) {
int exPort = Integer.parseInt(matcher.group(externalPort));
int inPort = Integer.parseInt(matcher.group(internalPort));
portMappings.put(inPort, exPort);
}
}

return new Service(id, serviceName, externalIp, internalIp, portMappings, this);
}

/**
Expand Down Expand Up @@ -242,12 +243,48 @@ private static ProcessResult execute(ProcessExecutor executor, List<String> comm
/**
* Gets container IP using "{@code docker inspect}" command and container name.
*
* @return The IP can be found in the JSON output of "docker inspect" command expected to be in node
* {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}
* @return The IP can be found in the JSON output of "docker inspect" command expected to be in node {{range
* .NetworkSettings.Networks}}{{.IPAddress}}{{end}}
*/
private String getContainerIp(String containerId) {
ProcessResult result = execute("docker", "inspect", "-f",
"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}", containerId);
return result.outputString().trim();
}

/**
* Returns container info by using "{@code docker ps}" command. The output format is
* CONTAINER_NAME#CONTAINER_PORTS.
*/
private String getContainerNameAndPorts(String serviceName, String projectName) {
List<String> dockerCommands = new ArrayList<>();
dockerCommands.add("docker");
dockerCommands.add("ps");

// Filter containers by docker-compose project name label
if (StringUtils.isNotEmpty(projectName)) {
dockerCommands.add("-f");
dockerCommands.add("label=com.docker.compose.project=" + projectName);
}

// Filter containers by docker-compose service name label
dockerCommands.add("-f");
dockerCommands.add("label=com.docker.compose.service=" + serviceName);

// Format output to only includes name and ports section of container
dockerCommands.add("--format");
dockerCommands.add("{{.Names}}#{{.Ports}}");

// Execute command and make the result
ProcessExecutor executor = new ProcessExecutor()
.environment(environment);
List<String> results = execute(executor, dockerCommands).getOutput().getLines();
StringBuilder resultBuilder = new StringBuilder();
if (results != null && !results.isEmpty()) {
for (String result : results) {
resultBuilder.append(result);
}
}
return resultBuilder.toString();
}
}

0 comments on commit 82c00cf

Please sign in to comment.