org.testcontainers.images.builder.ImageFromDockerfile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of testcontainers Show documentation
Show all versions of testcontainers Show documentation
Isolated container management for Java code testing
package org.testcontainers.images.builder;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.BuildImageCmd;
import com.github.dockerjava.api.model.BuildResponseItem;
import com.github.dockerjava.core.command.BuildImageResultCallback;
import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.profiler.Profiler;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.images.builder.traits.*;
import org.testcontainers.utility.Base58;
import org.testcontainers.utility.DockerLoggerFactory;
import org.testcontainers.utility.LazyFuture;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
@Slf4j
@Getter
public class ImageFromDockerfile extends LazyFuture implements
BuildContextBuilderTrait,
ClasspathTrait,
FilesTrait,
StringsTrait,
DockerfileTrait {
private static final Set imagesToDelete = Sets.newConcurrentHashSet();
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try (DockerClient dockerClientForCleaning = DockerClientFactory.instance().client(false)) {
for (String dockerImageName : imagesToDelete) {
log.info("Removing image tagged {}", dockerImageName);
try {
dockerClientForCleaning.removeImageCmd(dockerImageName).withForce(true).exec();
} catch (Throwable e) {
log.warn("Unable to delete image " + dockerImageName, e);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
private final String dockerImageName;
private boolean deleteOnExit = true;
private final Map transferables = new HashMap<>();
public ImageFromDockerfile() {
this("testcontainers/" + Base58.randomString(16).toLowerCase());
}
public ImageFromDockerfile(String dockerImageName) {
this(dockerImageName, true);
}
public ImageFromDockerfile(String dockerImageName, boolean deleteOnExit) {
this.dockerImageName = dockerImageName;
this.deleteOnExit = deleteOnExit;
}
@Override
public ImageFromDockerfile withFileFromTransferable(String path, Transferable transferable) {
Transferable oldValue = transferables.put(path, transferable);
if (oldValue != null) {
log.warn("overriding previous mapping for '{}'", path);
}
return this;
}
@Override
protected final String resolve() {
Logger logger = DockerLoggerFactory.getLogger(dockerImageName);
Profiler profiler = new Profiler("Rule creation - build image");
profiler.setLogger(logger);
try (DockerClient dockerClient = DockerClientFactory.instance().client(false)) {
if (deleteOnExit) {
imagesToDelete.add(dockerImageName);
}
BuildImageResultCallback resultCallback = new BuildImageResultCallback() {
@Override
public void onNext(BuildResponseItem item) {
super.onNext(item);
if (item.isErrorIndicated()) {
logger.error(item.getErrorDetail().getMessage());
} else {
logger.debug(StringUtils.chomp(item.getStream(), "\n"));
}
}
};
// We have to use pipes to avoid high memory consumption since users might want to build really big images
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream(in);
profiler.start("Configure image");
BuildImageCmd buildImageCmd = dockerClient.buildImageCmd(in);
configure(buildImageCmd);
profiler.start("Build image");
BuildImageResultCallback exec = buildImageCmd.exec(resultCallback);
// To build an image, we have to send the context to Docker in TAR archive format
profiler.start("Send context as TAR");
try (TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(new GZIPOutputStream(out))) {
for (Map.Entry entry : transferables.entrySet()) {
TarArchiveEntry tarEntry = new TarArchiveEntry(entry.getKey());
Transferable transferable = entry.getValue();
tarEntry.setSize(transferable.getSize());
tarEntry.setMode(transferable.getFileMode());
tarArchive.putArchiveEntry(tarEntry);
transferable.transferTo(tarArchive);
tarArchive.closeArchiveEntry();
}
tarArchive.finish();
}
profiler.start("Wait for an image id");
exec.awaitImageId();
return dockerImageName;
} catch(IOException e) {
throw new RuntimeException("Can't close DockerClient", e);
} finally {
profiler.stop().log();
}
}
protected void configure(BuildImageCmd buildImageCmd) {
buildImageCmd.withTag(this.getDockerImageName());
}
}