|
/* |
|
* Usage examples (the important bit). Please cut some slack on syntax etc. |
|
*/ |
|
class JUnit4Usage { |
|
@Rule |
|
public ContainerRule redis = Container.builder() |
|
// as core features 'image' and 'volume' are baked in to the Container class |
|
// (there will be many other such features we deem to be 'core') |
|
.image(named("redis:4.0")) |
|
.volume(mount("/foo", "/bar", "ro")) |
|
// 'buildAs' actually provides a Container wrapped in a ContainerRule, for JUnit 4 usage |
|
.buildAs(RULE); |
|
|
|
@Test |
|
public void testSomething() { |
|
// invoking a runtime action on the running container. |
|
// Some indirection via 'container()' call due to ContainerRule actually being a wrapper |
|
redis.container().exec("echo 'Hello world'"); |
|
} |
|
} |
|
|
|
@RunWith(TestcontainersJUnit5Runner.class) |
|
class JUnit5Usage { |
|
|
|
// JUnit 5 benefits from having a cleaner API. |
|
public Container redis = Container.builder() |
|
.image(named("redis:4.0")) |
|
.build(); |
|
|
|
@Test |
|
public void testSomething() { |
|
redis.exec("echo 'Hello world'"); |
|
} |
|
} |
|
|
|
class JUnit4PluginUsage { |
|
|
|
/* |
|
* A 'plugin' would be anything that is not a core feature that needs to (a) influence container config/creation, |
|
* (b) perform tasks at runtime |
|
*/ |
|
|
|
@Rule |
|
public ContainerRule redis = Container.builder() |
|
.image(named("redis:4.0")) |
|
.plugin(fakeTime(new Date(0))) // <-- example 'faketime' plugin - applying a setting which affects config |
|
.buildAs(RULE); |
|
|
|
@Test |
|
public void testSomething() { |
|
redis.container() |
|
.plugin(FakeTimePlugin.class) // obtaining a ref to a plugin instance at runtime, to do something with it |
|
.changeTime(new Date(1000)); |
|
} |
|
} |
|
|
|
|
|
/* |
|
* INTERNALS |
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
@Builder |
|
public class Container { |
|
|
|
private Image image; |
|
@Singular private List<Volume> volumes; |
|
@Singular private Set<Plugin> plugins = new TreeSet<>(comparingInt(Plugin::priority)); |
|
|
|
public class ContainerBuilder { |
|
@SneakyThrows |
|
public <T> T buildAs(Class<T> toType) { |
|
return toType.getConstructor(Container.class).newInstance(this.build()); |
|
} |
|
} |
|
|
|
private ContainerConfig configure() { |
|
plugins.forEach(it -> it.beforePullingImage(this)); |
|
|
|
// resolve image and pull |
|
|
|
plugins.forEach(it -> it.afterPullingImage(this)); |
|
|
|
// create a container config object |
|
ContainerConfig config = new ContainerConfig(); |
|
|
|
plugins.forEach(it -> it.beforeCoreConfiguration(this, config)); |
|
|
|
// do normal config for core features - e.g. setting up volume mounts, networks, etc |
|
// .... |
|
|
|
// then return |
|
return config; |
|
} |
|
|
|
public void start() { |
|
// do normal setup, configure(), and start |
|
|
|
ContainerConfig config = configure(); |
|
|
|
plugins.forEach(it -> it.beforeCreate(this, config)); |
|
|
|
com.github.dockerjava.api.model.Container dockerJavaContainer = // create it |
|
|
|
plugins.forEach(it -> it.beforeStart(this, dockerJavaContainer)); |
|
|
|
// start it |
|
// ... |
|
|
|
plugins.forEach(it -> it.afterStart(this, dockerJavaContainer)); |
|
} |
|
|
|
public void stop() { |
|
// do container shutdown |
|
} |
|
|
|
/* |
|
* 'exec' is an example of a core feature that can be used at runtime |
|
*/ |
|
public int exec(String command) { |
|
// exec in the container |
|
return 0; |
|
} |
|
|
|
/* |
|
* Obtain a reference to a plugin that has been used on this container, to enable runtime actions to be performed |
|
*/ |
|
public <T> T plugin(Class<T> pluginClass) { |
|
return (T) plugins.stream() |
|
.filter(it -> it.getClass().equals(pluginClass)) |
|
.findFirst() |
|
.get(); |
|
} |
|
} |
|
|
|
@RequiredArgsConstructor |
|
class Image { |
|
private final String imageName; |
|
} |
|
|
|
@RequiredArgsConstructor |
|
class Volume { |
|
private final String host; |
|
private final String container; |
|
private final String mode; |
|
} |
|
|
|
/* |
|
* This class provides static factories for core features. It is provided to aid discoverability; |
|
* its methods should be used via static import(s) |
|
*/ |
|
@UtilityClass |
|
class CoreConfigurationElements { |
|
|
|
public static Image named(String imageName) { |
|
return new Image(imageName); |
|
} |
|
|
|
public static Volume mount(String hostPath, String containerPath, String mode) { |
|
return new Volume(hostPath, containerPath, mode); |
|
} |
|
} |
|
|
|
|
|
/* |
|
* A plugin has the ability to take control at any point during container creation/startup |
|
*/ |
|
interface Plugin { |
|
default void beforePullingImage(Container container) {} |
|
default void afterPullingImage(Container container) {} |
|
default void beforeCoreConfiguration(Container container, ContainerConfig config) {} |
|
default void beforeCreate(Container container, ContainerConfig config) {} |
|
default void beforeStart(Container container, com.github.dockerjava.api.model.Container dockerJavaContainer) {} |
|
default void afterStart(Container container, com.github.dockerjava.api.model.Container dockerJavaContainer) {} |
|
default int priority() { return 100; } |
|
} |
|
|
|
/* |
|
* A made-up example of a plugin. This example shows that a plugin can amend ContainerConfiguration before it is |
|
* created, and also provides a method that can be invoked from test code while the container is running. |
|
*/ |
|
@NoArgsConstructor |
|
class FakeTimePlugin implements Plugin { |
|
|
|
private Date fakeTime; |
|
|
|
public FakeTimePlugin(Date fakeTime) { |
|
this.fakeTime = fakeTime; |
|
} |
|
|
|
public static FakeTimePlugin fakeTime(Date fakeTime) { |
|
return new FakeTimePlugin(fakeTime); |
|
} |
|
|
|
@Override |
|
public void beforeCreate(Container container, ContainerConfig config) { |
|
// do necessary installation/mounting; amend cmd |
|
|
|
// hold onto Container for any future manipulation |
|
} |
|
|
|
public void changeTime(Date newFakeTime) { |
|
this.fakeTime = newFakeTime; |
|
// do something with Container |
|
} |
|
} |
|
|
|
/* |
|
* Simplified example of a shim that wraps a Container so that it can be used as a Rule. |
|
*/ |
|
class ContainerRule extends ExternalResource { |
|
public static Class<ContainerRule> RULE = ContainerRule.class; |
|
private final Container container; |
|
|
|
ContainerRule(Container container) { |
|
this.container = container; |
|
} |
|
|
|
@Override |
|
protected void before() throws Throwable { |
|
container.start(); |
|
} |
|
|
|
@Override |
|
protected void after() { |
|
container.stop(); |
|
} |
|
|
|
public Container container() { |
|
return container; |
|
} |
|
} |
|
|
|
/* |
|
* ignore the implementation of this - this is based on JUnit 4 libs |
|
*/ |
|
class TestcontainersJUnit5Runner extends Runner { |
|
... |
|
} |