Skip to content

Commit

Permalink
Move execInContainer and copy* to ContainerState (#2176)
Browse files Browse the repository at this point in the history
* Move `execInContainer` and `copy*` to `ContainerState`

* fix backward compatibility

* Fix javadoc
  • Loading branch information
bsideup committed Jan 12, 2020
1 parent dab1a94 commit 6622578
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 136 deletions.
55 changes: 0 additions & 55 deletions core/src/main/java/org/testcontainers/containers/Container.java
Expand Up @@ -420,61 +420,6 @@ default void followOutput(Consumer<OutputFrame> 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.
* <p>
* @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".
* <p>
* @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> T copyFileFromContainer(String containerPath, ThrowingFunction<InputStream, T> function);

List<String> getPortBindings();

List<String> getExtraHosts();
Expand Down
129 changes: 128 additions & 1 deletion 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;
Expand Down Expand Up @@ -170,10 +188,119 @@ 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.
* <p>
* @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".
* <p>
* @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
*/
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> T copyFileFromContainer(String containerPath, ThrowingFunction<InputStream, T> 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);
}
}
}
Expand Up @@ -1324,87 +1324,9 @@ public synchronized Info fetchDockerDaemonInfo() throws IOException {
* {@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
@SneakyThrows
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> T copyFileFromContainer(String containerPath, ThrowingFunction<InputStream, T> 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);
Container.super.copyFileFromContainer(containerPath, destinationPath);
}

/**
Expand Down

0 comments on commit 6622578

Please sign in to comment.