Skip to content

Commit

Permalink
Allow to execute commands with a custom user (#7311)
Browse files Browse the repository at this point in the history
Co-authored-by: Eddú Meléndez Gonzales <eddu.melendez@gmail.com>
  • Loading branch information
megglos and eddumelendez committed Aug 15, 2023
1 parent 104af18 commit ea0b163
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 11 deletions.
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

0 comments on commit ea0b163

Please sign in to comment.