Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rnorth committed Apr 12, 2015
0 parents commit bf37c50
Show file tree
Hide file tree
Showing 7 changed files with 437 additions and 0 deletions.
60 changes: 60 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Created by .ignore support plugin (hsz.mobi)
### Maven template
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties



### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm

*.iml

## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:

# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries

# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml

# Gradle:
# .idea/gradle.xml
# .idea/libraries

# Mongo Explorer plugin:
# .idea/mongoSettings.xml

## File-based project format:
*.ipr
*.iws

## Plugin-specific files:

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties


60 changes: 60 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.testpackage</groupId>
<artifactId>testpackage-containers</artifactId>
<version>1.0-SNAPSHOT</version>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>com.spotify</groupId>
<artifactId>docker-client</artifactId>
<classifier>shaded</classifier>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.3.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
</project>
143 changes: 143 additions & 0 deletions src/main/java/AbstractContainerRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import com.spotify.docker.client.DefaultDockerClient;
import com.spotify.docker.client.DockerCertificates;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.DockerException;
import com.spotify.docker.client.messages.*;
import org.junit.rules.ExternalResource;
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.ProcessResult;

import java.io.IOException;
import java.net.Socket;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;

/**
* @author richardnorth
*/
public abstract class AbstractContainerRule extends ExternalResource {

protected String dockerHostIpAddress;
private String containerId;
private DockerClient dockerClient;
private boolean normalTermination = false;

@Override
protected void before() throws Throwable {

DefaultDockerClient.Builder builder = DefaultDockerClient.builder();

customizeBuilderForOs(builder);

dockerClient = builder.build();

pullImageIfNeeded(getDockerImageName());

ContainerConfig containerConfig = getContainerConfig();

HostConfig.Builder hostConfigBuilder = HostConfig.builder()
.publishAllPorts(true);
customizeHostConfigBuilder(hostConfigBuilder);
HostConfig hostConfig = hostConfigBuilder.build();

ContainerCreation containerCreation = dockerClient.createContainer(containerConfig);

containerId = containerCreation.id();
dockerClient.startContainer(containerId, hostConfig);

ContainerInfo containerInfo = dockerClient.inspectContainer(containerId);

containerIsStarting(containerInfo);

waitForListeningPort(dockerHostIpAddress, getLivenessCheckPort());

// If the container stops before the after() method, its termination was unexpected
Executors.newSingleThreadExecutor().submit(() -> {
Exception caughtException = null;
try {
dockerClient.waitContainer(containerId);
} catch (DockerException | InterruptedException e) {
caughtException = e;
}

if (!normalTermination) {
throw new RuntimeException("Container exited unexpectedly", caughtException);
}
});
}

protected void customizeHostConfigBuilder(HostConfig.Builder hostConfigBuilder) {

}

private void pullImageIfNeeded(String imageName) throws DockerException, InterruptedException {
List<Image> images = dockerClient.listImages(DockerClient.ListImagesParam.create("name", getDockerImageName()));
for (Image image : images) {
if (image.repoTags().contains(imageName)) {
// the image exists
return;
}
}

dockerClient.pull(getDockerImageName());
}

@Override
protected void after() {
try {
normalTermination = true;
dockerClient.killContainer(containerId);
} catch (DockerException | InterruptedException e) {
e.printStackTrace();
}
}

protected abstract void containerIsStarting(ContainerInfo containerInfo);

protected abstract String getLivenessCheckPort();

protected abstract ContainerConfig getContainerConfig();

protected abstract String getDockerImageName();

private void waitForListeningPort(String ipAddress, String port) {
for (int i = 0; i < 100; i++) {
try {
new Socket(ipAddress, Integer.valueOf(port));
return;
} catch (IOException e) {
try {
Thread.sleep(100L);
} catch (InterruptedException ignored) {
}
}
}
throw new IllegalStateException("Timed out waiting for container port to open (" + ipAddress + ":" + port + " should be listening)");
}

private void customizeBuilderForOs(DefaultDockerClient.Builder builder) throws Exception {
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
// Running on a Mac therefore use boot2docker
runShellCommand("/usr/local/bin/boot2docker", "up");
dockerHostIpAddress = runShellCommand("/usr/local/bin/boot2docker", "ip");

builder.uri("https://" + dockerHostIpAddress + ":2376")
.dockerCertificates(new DockerCertificates(Paths.get(System.getProperty("user.home") + "/.boot2docker/certs/boot2docker-vm")));
} else {
dockerHostIpAddress = "127.0.0.1";
}
}

private String runShellCommand(String... command) throws IOException, InterruptedException, TimeoutException {
ProcessResult result;
result = new ProcessExecutor().command(command)
.readOutput(true).execute();

if (result.getExitValue() != 0) {
throw new IllegalStateException();
}
return result.outputUTF8().trim();
}
}
48 changes: 48 additions & 0 deletions src/main/java/MySQLContainerRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import com.spotify.docker.client.messages.ContainerConfig;
import com.spotify.docker.client.messages.ContainerInfo;

/**
* @author richardnorth
*/
public class MySQLContainerRule extends AbstractContainerRule {

private static final String MYSQL_IMAGE = "mysql:5.6.23";
private String mySqlPort;

@Override
protected void containerIsStarting(ContainerInfo containerInfo) {
mySqlPort = containerInfo.networkSettings().ports().get("3306/tcp").get(0).hostPort();
}

@Override
protected String getLivenessCheckPort() {
return mySqlPort;
}

@Override
protected ContainerConfig getContainerConfig() {
return ContainerConfig.builder()
.image(getDockerImageName())
.exposedPorts("3306")
.env("MYSQL_DATABASE=test", "MYSQL_USER=test", "MYSQL_PASSWORD=test", "MYSQL_ROOT_PASSWORD=test")
.cmd("mysqld")
.build();
}

@Override
protected String getDockerImageName() {
return MYSQL_IMAGE;
}

public String getJdbcUrl() {
return "jdbc:mysql://" + dockerHostIpAddress + ":" + mySqlPort + "/test";
}

public String getUsername() {
return "test";
}

public String getPassword() {
return "test";
}
}
58 changes: 58 additions & 0 deletions src/main/java/NginxContainerRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import com.spotify.docker.client.messages.ContainerConfig;
import com.spotify.docker.client.messages.ContainerInfo;
import com.spotify.docker.client.messages.HostConfig;
import com.spotify.docker.client.messages.PortBinding;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;

/**
* @author richardnorth
*/
public class NginxContainerRule extends AbstractContainerRule {
private String nginxPort;
private String htmlContentPath;
private Map<String, List<PortBinding>> ports;
private List<String> binds;

@Override
protected void containerIsStarting(ContainerInfo containerInfo) {
ports = containerInfo.networkSettings().ports();
nginxPort = ports.get("80/tcp").get(0).hostPort();
}

@Override
protected String getLivenessCheckPort() {
return nginxPort;
}

@Override
protected ContainerConfig getContainerConfig() {
return ContainerConfig.builder()
.image(getDockerImageName())
.exposedPorts("80")
.cmd("nginx", "-g", "daemon off;")
.build();
}

@Override
protected void customizeHostConfigBuilder(HostConfig.Builder hostConfigBuilder) {
hostConfigBuilder.binds(binds);
}

@Override
protected String getDockerImageName() {
return "nginx:1.7.11";
}

public URL getBaseUrl(String scheme, int port) throws MalformedURLException {
return new URL(scheme + "://" + dockerHostIpAddress + ":" + ports.get(port + "/tcp").get(0).hostPort());
}

public NginxContainerRule withCustomConfig(String htmlContentPath) {
binds.add(htmlContentPath + ":/usr/share/nginx/html:ro");
return this;
}
}
Loading

0 comments on commit bf37c50

Please sign in to comment.