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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exec in container with custom user #7311

Merged
merged 4 commits into from
Aug 15, 2023
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
2 changes: 2 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ tasks.japicmp {
methodExcludes = [
"org.testcontainers.containers.Container#getDockerClient()",
"org.testcontainers.containers.ContainerState#getDockerClient()",
"org.testcontainers.containers.ContainerState#execInContainerWithUser(java.lang.String,java.lang.String[])",
"org.testcontainers.containers.ContainerState#execInContainerWithUser(java.nio.charset.Charset,java.lang.String,java.lang.String[])",
]

fieldExcludes = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ default String getContainerId() {
* 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...)
* @see #execInContainer(Charset, String...)
*/
default Container.ExecResult execInContainer(String... command)
throws UnsupportedOperationException, IOException, InterruptedException {
Expand All @@ -255,13 +255,39 @@ default Container.ExecResult execInContainer(String... 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...)
* @see ExecInContainerPattern#execInContainer(DockerClient, InspectContainerResponse, Charset, String...)
*/
default Container.ExecResult execInContainer(Charset outputCharset, String... command)
throws UnsupportedOperationException, IOException, InterruptedException {
return ExecInContainerPattern.execInContainer(getDockerClient(), getContainerInfo(), outputCharset, command);
}

/**
* Run a command inside a running container as a given user, as using "docker exec -u user".
* <p>
* @see ExecInContainerPattern#execInContainerWithUser(DockerClient, InspectContainerResponse, String, String...)
*/
default Container.ExecResult execInContainerWithUser(String user, String... command)
throws UnsupportedOperationException, IOException, InterruptedException {
return ExecInContainerPattern.execInContainerWithUser(getDockerClient(), getContainerInfo(), user, command);
}

/**
* Run a command inside a running container as a given user, as using "docker exec -u user".
* <p>
* @see ExecInContainerPattern#execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String, String...)
*/
default Container.ExecResult execInContainerWithUser(Charset outputCharset, String user, String... command)
throws UnsupportedOperationException, IOException, InterruptedException {
return ExecInContainerPattern.execInContainerWithUser(
getDockerClient(),
getContainerInfo(),
outputCharset,
user,
command
);
}

/**
*
* Copies a file or directory to the container.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.testcontainers.containers;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmd;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.exception.DockerException;
Expand Down Expand Up @@ -45,7 +46,7 @@ public Container.ExecResult execInContainer(
String... command
) throws UnsupportedOperationException, IOException, InterruptedException {
DockerClient dockerClient = DockerClientFactory.instance().client();
return execInContainer(dockerClient, containerInfo, outputCharset, command);
return execInContainerWithUser(dockerClient, containerInfo, outputCharset, null, command);
}

/**
Expand All @@ -55,34 +56,77 @@ public Container.ExecResult execInContainer(
* @param dockerClient the {@link DockerClient}
* @param containerInfo the container info
* @param command the command to execute
* @see #execInContainer(DockerClient, InspectContainerResponse, Charset, String...)
* @see #execInContainerWithUser(DockerClient, InspectContainerResponse, String, String...)
*/
public Container.ExecResult execInContainer(
DockerClient dockerClient,
InspectContainerResponse containerInfo,
String... command
) throws UnsupportedOperationException, IOException, InterruptedException {
return execInContainer(dockerClient, containerInfo, StandardCharsets.UTF_8, command);
return execInContainerWithUser(dockerClient, containerInfo, StandardCharsets.UTF_8, null, command);
}

/**
* Run a command inside a running container, as though using "docker exec".
* Run a command inside a running container, as though using "docker exec", and interpreting
* the output as UTF8.
* <p></p>
* @param dockerClient the {@link DockerClient}
* @param containerInfo the container info
* @param outputCharset the character set used to interpret the output.
* @param command the command to execute
* @see #execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String, String...)
*/
public Container.ExecResult execInContainer(
DockerClient dockerClient,
InspectContainerResponse containerInfo,
Charset outputCharset,
String... command
) throws UnsupportedOperationException, IOException, InterruptedException {
return execInContainerWithUser(dockerClient, containerInfo, outputCharset, null, command);
}

/**
* Run a command inside a running container as a given user, as using "docker exec -u user" and
* interpreting the output as UTF8.
* <p>
* This functionality is not available on a docker daemon running the older "lxc" execution driver. At
* the time of writing, CircleCI was using this driver.
* @param dockerClient the {@link DockerClient}
* @param containerInfo the container info
* @param user the user to run the command with, optional
* @param command the command to execute
* @see #execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String,
* String...)
*/
public Container.ExecResult execInContainerWithUser(
DockerClient dockerClient,
InspectContainerResponse containerInfo,
String user,
String... command
) throws UnsupportedOperationException, IOException, InterruptedException {
return execInContainerWithUser(dockerClient, containerInfo, StandardCharsets.UTF_8, user, command);
}

/**
* Run a command inside a running container as a given user, as using "docker exec -u user".
* <p>
* This functionality is not available on a docker daemon running the older "lxc" execution
* driver. At the time of writing, CircleCI was using this driver.
* @param dockerClient the {@link DockerClient}
* @param containerInfo the container info
* @param outputCharset the character set used to interpret the output.
* @param user the user to run the command with, optional
* @param command the parts of the command to run
* @return the result of execution
* @throws IOException if there's an issue communicating with Docker
* @throws InterruptedException if the thread waiting for the response is interrupted
* @throws UnsupportedOperationException if the docker daemon you're connecting to doesn't support "exec".
*/
public Container.ExecResult execInContainer(
public Container.ExecResult execInContainerWithUser(
DockerClient dockerClient,
InspectContainerResponse containerInfo,
Charset outputCharset,
String user,
String... command
) throws UnsupportedOperationException, IOException, InterruptedException {
if (!TestEnvironment.dockerExecutionDriverSupportsExec()) {
Expand All @@ -100,12 +144,17 @@ public Container.ExecResult execInContainer(
String containerName = containerInfo.getName();

log.debug("{}: Running \"exec\" command: {}", containerName, String.join(" ", command));
final ExecCreateCmdResponse execCreateCmdResponse = dockerClient
final ExecCreateCmd execCreateCmd = dockerClient
.execCreateCmd(containerId)
.withAttachStdout(true)
.withAttachStderr(true)
.withCmd(command)
.exec();
.withCmd(command);
if (user != null && !user.isEmpty()) {
log.debug("{}: Running \"exec\" command with user: {}", containerName, user);
execCreateCmd.withUser(user);
}

final ExecCreateCmdResponse execCreateCmdResponse = execCreateCmd.exec();

final ToStringConsumer stdoutConsumer = new ToStringConsumer();
final ToStringConsumer stderrConsumer = new ToStringConsumer();
Expand All @@ -116,7 +165,7 @@ public Container.ExecResult execInContainer(

dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(callback).awaitCompletion();
}
Integer exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCode();
int exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCodeLong().intValue();

final Container.ExecResult result = new Container.ExecResult(
exitCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,21 @@ public void testExecInContainer() throws Exception {
// We expect to reach this point for modern Docker versions.
}

@Test
public void testExecInContainerWithUser() throws Exception {
// The older "lxc" execution driver doesn't support "exec". At the time of writing (2016/03/29),
// that's the case for CircleCI.
// Once they resolve the issue, this clause can be removed.
Assume.assumeTrue(TestEnvironment.dockerExecutionDriverSupportsExec());

final GenericContainer.ExecResult result = redis.execInContainerWithUser("redis", "whoami");
assertThat(result.getStdout())
.as("Output for \"whoami\" command should start with \"redis\"")
.startsWith("redis");
assertThat(result.getStderr()).as("Stderr for \"whoami\" command should be empty").isEmpty();
// We expect to reach this point for modern Docker versions.
}

@Test
public void extraHostTest() throws IOException {
BufferedReader br = getReaderForContainerPort80(alpineExtrahost);
Expand Down