From b22ea9e3bd783e088b6ae1386b11e3c091039a8b Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sat, 14 Dec 2019 14:49:28 +0100 Subject: [PATCH 1/3] Move `execInContainer` and `copy*` to `ContainerState` --- .../testcontainers/containers/Container.java | 55 -------- .../containers/ContainerState.java | 131 +++++++++++++++++- .../containers/GenericContainer.java | 87 ------------ 3 files changed, 130 insertions(+), 143 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/Container.java b/core/src/main/java/org/testcontainers/containers/Container.java index ef27a95a463..631e2653ceb 100644 --- a/core/src/main/java/org/testcontainers/containers/Container.java +++ b/core/src/main/java/org/testcontainers/containers/Container.java @@ -420,61 +420,6 @@ default void followOutput(Consumer consumer, OutputFrame.OutputType @Deprecated Info fetchDockerDaemonInfo() throws IOException; - /** - * Run a command inside a running container, as though using "docker exec", and interpreting - * the output as UTF8. - *

- * @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, String...) - */ - ExecResult execInContainer(String... command) - throws UnsupportedOperationException, IOException, InterruptedException; - - /** - * Run a command inside a running container, as though using "docker exec". - *

- * @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, Charset, String...) - */ - ExecResult execInContainer(Charset outputCharset, String... command) - throws UnsupportedOperationException, IOException, InterruptedException; - - /** - * - * Copies a file or directory to the container. - * - * @param mountableFile file or directory which is copied into the container - * @param containerPath destination path inside the container - * @throws IOException if there's an issue communicating with Docker - * @throws InterruptedException if the thread waiting for the response is interrupted - */ - void copyFileToContainer(MountableFile mountableFile, String containerPath) throws IOException, InterruptedException; - - /** - * - * Copies a file to the container. - * - * @param transferable file which is copied into the container - * @param containerPath destination path inside the container - */ - void copyFileToContainer(Transferable transferable, String containerPath); - - /** - * Copies a file which resides inside the container to user defined directory - * - * @param containerPath path to file which is copied from container - * @param destinationPath destination path to which file is copied with file name - * @throws IOException if there's an issue communicating with Docker or receiving entry from TarArchiveInputStream - * @throws InterruptedException if the thread waiting for the response is interrupted - */ - void copyFileFromContainer(String containerPath, String destinationPath) throws IOException, InterruptedException; - - /** - * Streams a file which resides inside the container - * - * @param containerPath path to file which is copied from container - * @param function function that takes InputStream of the copied file - */ - T copyFileFromContainer(String containerPath, ThrowingFunction function); - List getPortBindings(); List getExtraHosts(); diff --git a/core/src/main/java/org/testcontainers/containers/ContainerState.java b/core/src/main/java/org/testcontainers/containers/ContainerState.java index 2066fe72157..ad880040684 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerState.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerState.java @@ -1,15 +1,33 @@ package org.testcontainers.containers; +import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.exception.DockerException; import com.github.dockerjava.api.model.ExposedPort; import com.github.dockerjava.api.model.PortBinding; import com.github.dockerjava.api.model.Ports; import com.google.common.base.Preconditions; +import lombok.SneakyThrows; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.apache.commons.compress.utils.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.output.OutputFrame; +import org.testcontainers.images.builder.Transferable; import org.testcontainers.utility.LogUtils; +import org.testcontainers.utility.MountableFile; +import org.testcontainers.utility.ThrowingFunction; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -170,10 +188,121 @@ default String getLogs(OutputFrame.OutputType... types) { /** * @return the id of the container */ - String getContainerId(); + default String getContainerId() { + return getContainerInfo().getId(); + } /** * @return the container info */ InspectContainerResponse getContainerInfo(); + + /** + * Run a command inside a running container, as though using "docker exec", and interpreting + * the output as UTF8. + *

+ * @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, String...) + */ + default Container.ExecResult execInContainer(String... command) throws UnsupportedOperationException, IOException, InterruptedException { + return execInContainer(StandardCharsets.UTF_8, command); + } + + /** + * Run a command inside a running container, as though using "docker exec". + *

+ * @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, Charset, String...) + */ + default Container.ExecResult execInContainer(Charset outputCharset, String... command) throws UnsupportedOperationException, IOException, InterruptedException { + return ExecInContainerPattern.execInContainer(getContainerInfo(), outputCharset, command); + } + + /** + * + * Copies a file or directory to the container. + * + * @param mountableFile file or directory which is copied into the container + * @param containerPath destination path inside the container + * @throws IOException if there's an issue communicating with Docker + * @throws InterruptedException if the thread waiting for the response is interrupted + */ + default void copyFileToContainer(MountableFile mountableFile, String containerPath) { + File sourceFile = new File(mountableFile.getResolvedPath()); + + if (containerPath.endsWith("/") && sourceFile.isFile()) { + final Logger logger = LoggerFactory.getLogger(GenericContainer.class); + logger.warn("folder-like containerPath in copyFileToContainer is deprecated, please explicitly specify a file path"); + copyFileToContainer((Transferable) mountableFile, containerPath + sourceFile.getName()); + } else { + copyFileToContainer((Transferable) mountableFile, containerPath); + } + } + + /** + * + * Copies a file to the container. + * + * @param transferable file which is copied into the container + * @param containerPath destination path inside the container + */ + @SneakyThrows(IOException.class) + default void copyFileToContainer(Transferable transferable, String containerPath) { + if (!isCreated()) { + throw new IllegalStateException("copyFileToContainer can only be used with created / running container"); + } + + try ( + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(byteArrayOutputStream) + ) { + tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); + + transferable.transferTo(tarArchive, containerPath); + tarArchive.finish(); + + DockerClientFactory.instance().client() + .copyArchiveToContainerCmd(getContainerId()) + .withTarInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())) + .withRemotePath("/") + .exec(); + } + } + + /** + * Copies a file which resides inside the container to user defined directory + * + * @param containerPath path to file which is copied from container + * @param destinationPath destination path to which file is copied with file name + * @throws IOException if there's an issue communicating with Docker or receiving entry from TarArchiveInputStream + * @throws InterruptedException if the thread waiting for the response is interrupted + */ + default void copyFileFromContainer(String containerPath, String destinationPath) throws IOException, InterruptedException { + copyFileFromContainer(containerPath, inputStream -> { + try(FileOutputStream output = new FileOutputStream(destinationPath)) { + IOUtils.copy(inputStream, output); + return null; + } + }); + } + + /** + * Streams a file which resides inside the container + * + * @param containerPath path to file which is copied from container + * @param function function that takes InputStream of the copied file + */ + @SneakyThrows + default T copyFileFromContainer(String containerPath, ThrowingFunction function) { + if (!isCreated()) { + throw new IllegalStateException("copyFileFromContainer can only be used when the Container is created."); + } + + try ( + DockerClient dockerClient = DockerClientFactory.instance().client(); + InputStream inputStream = dockerClient.copyArchiveFromContainerCmd(getContainerId(), containerPath).exec(); + TarArchiveInputStream tarInputStream = new TarArchiveInputStream(inputStream) + ) { + tarInputStream.getNextTarEntry(); + return function.apply(tarInputStream); + } + } } diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index 0062612c10d..7d158464ca8 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -1320,93 +1320,6 @@ public synchronized Info fetchDockerDaemonInfo() throws IOException { return this.dockerDaemonInfo; } - /** - * {@inheritDoc} - */ - @Override - public ExecResult execInContainer(String... command) - throws UnsupportedOperationException, IOException, InterruptedException { - - return execInContainer(UTF8, command); - } - - /** - * {@inheritDoc} - */ - @Override - public void copyFileToContainer(MountableFile mountableFile, String containerPath) { - File sourceFile = new File(mountableFile.getResolvedPath()); - - if (containerPath.endsWith("/") && sourceFile.isFile()) { - logger().warn("folder-like containerPath in copyFileToContainer is deprecated, please explicitly specify a file path"); - copyFileToContainer((Transferable) mountableFile, containerPath + sourceFile.getName()); - } else { - copyFileToContainer((Transferable) mountableFile, containerPath); - } - } - - @Override - @SneakyThrows(IOException.class) - public void copyFileToContainer(Transferable transferable, String containerPath) { - if (!isCreated()) { - throw new IllegalStateException("copyFileToContainer can only be used with created / running container"); - } - - try ( - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(byteArrayOutputStream) - ) { - tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - - transferable.transferTo(tarArchive, containerPath); - tarArchive.finish(); - - dockerClient - .copyArchiveToContainerCmd(containerId) - .withTarInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())) - .withRemotePath("/") - .exec(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void copyFileFromContainer(String containerPath, String destinationPath) { - copyFileFromContainer(containerPath, inputStream -> { - try(FileOutputStream output = new FileOutputStream(destinationPath)) { - IOUtils.copy(inputStream, output); - return null; - } - }); - } - - @Override - @SneakyThrows(Exception.class) - public T copyFileFromContainer(String containerPath, ThrowingFunction consumer) { - if (!isCreated()) { - throw new IllegalStateException("copyFileFromContainer can only be used when the Container is created."); - } - - try ( - InputStream inputStream = dockerClient.copyArchiveFromContainerCmd(containerId, containerPath).exec(); - TarArchiveInputStream tarInputStream = new TarArchiveInputStream(inputStream) - ) { - tarInputStream.getNextTarEntry(); - return consumer.apply(tarInputStream); - } - } - - /** - * {@inheritDoc} - */ - @Override - public ExecResult execInContainer(Charset outputCharset, String... command) - throws UnsupportedOperationException, IOException, InterruptedException { - return ExecInContainerPattern.execInContainer(getContainerInfo(), outputCharset, command); - } - /** * Allow container startup to be attempted more than once if an error occurs. To be if containers are * 'flaky' but this flakiness is not something that should affect test outcomes. From 9430ae610666453ee9d835b7ec59d50e7484c31b Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sat, 14 Dec 2019 15:05:46 +0100 Subject: [PATCH 2/3] fix backward compatibility --- .../org/testcontainers/containers/GenericContainer.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index 7d158464ca8..ee458339d0a 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -1320,6 +1320,15 @@ public synchronized Info fetchDockerDaemonInfo() throws IOException { return this.dockerDaemonInfo; } + /** + * {@inheritDoc} + */ + @Override + @SneakyThrows + public void copyFileFromContainer(String containerPath, String destinationPath) { + Container.super.copyFileFromContainer(containerPath, destinationPath); + } + /** * Allow container startup to be attempted more than once if an error occurs. To be if containers are * 'flaky' but this flakiness is not something that should affect test outcomes. From 4e2dbd4b07e30b94e8423236aee1d625ef972a31 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sat, 14 Dec 2019 17:51:49 +0100 Subject: [PATCH 3/3] Fix javadoc --- .../main/java/org/testcontainers/containers/ContainerState.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/ContainerState.java b/core/src/main/java/org/testcontainers/containers/ContainerState.java index ad880040684..043bae1d52f 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerState.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerState.java @@ -222,8 +222,6 @@ default Container.ExecResult execInContainer(Charset outputCharset, String... co * * @param mountableFile file or directory which is copied into the container * @param containerPath destination path inside the container - * @throws IOException if there's an issue communicating with Docker - * @throws InterruptedException if the thread waiting for the response is interrupted */ default void copyFileToContainer(MountableFile mountableFile, String containerPath) { File sourceFile = new File(mountableFile.getResolvedPath());