Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move execInContainer and copy* to ContainerState #2176

Merged
merged 3 commits into from Jan 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it was defined as throws IOException, InterruptedException in Container

}

/**
Expand Down