Skip to content

Commit

Permalink
Exec in container with custom user
Browse files Browse the repository at this point in the history
* allows to run commands with a custom user
  • Loading branch information
megglos committed Jul 17, 2023
1 parent 1835f97 commit 89a9f0a
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,33 @@ default Container.ExecResult execInContainer(String... command)
*/
default Container.ExecResult execInContainer(Charset outputCharset, String... command)
throws UnsupportedOperationException, IOException, InterruptedException {
return ExecInContainerPattern.execInContainer(getDockerClient(), getContainerInfo(), outputCharset, command);
return execInContainerWithUser(outputCharset, null, command);
}

/**
* Run a command inside a running container as a given user, as using "docker exec -u user".
* <p>
* @see ExecInContainerPattern#execInContainerWithUser(DockerClient, com.github.dockerjava.api.command.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, com.github.dockerjava.api.command.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
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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;
import com.google.common.base.Strings;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.DockerClientFactory;
Expand Down Expand Up @@ -45,7 +47,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 +57,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 +145,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 (!Strings.isNullOrEmpty(user)) {
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 +166,11 @@ public Container.ExecResult execInContainer(

dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(callback).awaitCompletion();
}
Integer exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCode();
Integer 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,19 @@ 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 equal \"redis\"").isEqualTo("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 89a9f0a

Please sign in to comment.