Skip to content

Commit ea0b163

Browse files
Allow to execute commands with a custom user (#7311)
Co-authored-by: Eddú Meléndez Gonzales <[email protected]>
1 parent 104af18 commit ea0b163

File tree

4 files changed

+103
-11
lines changed

4 files changed

+103
-11
lines changed

core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ tasks.japicmp {
5353
methodExcludes = [
5454
"org.testcontainers.containers.Container#getDockerClient()",
5555
"org.testcontainers.containers.ContainerState#getDockerClient()",
56+
"org.testcontainers.containers.ContainerState#execInContainerWithUser(java.lang.String,java.lang.String[])",
57+
"org.testcontainers.containers.ContainerState#execInContainerWithUser(java.nio.charset.Charset,java.lang.String,java.lang.String[])",
5658
]
5759

5860
fieldExcludes = []

core/src/main/java/org/testcontainers/containers/ContainerState.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ default String getContainerId() {
245245
* Run a command inside a running container, as though using "docker exec", and interpreting
246246
* the output as UTF8.
247247
* <p>
248-
* @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, String...)
248+
* @see #execInContainer(Charset, String...)
249249
*/
250250
default Container.ExecResult execInContainer(String... command)
251251
throws UnsupportedOperationException, IOException, InterruptedException {
@@ -255,13 +255,39 @@ default Container.ExecResult execInContainer(String... command)
255255
/**
256256
* Run a command inside a running container, as though using "docker exec".
257257
* <p>
258-
* @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, Charset, String...)
258+
* @see ExecInContainerPattern#execInContainer(DockerClient, InspectContainerResponse, Charset, String...)
259259
*/
260260
default Container.ExecResult execInContainer(Charset outputCharset, String... command)
261261
throws UnsupportedOperationException, IOException, InterruptedException {
262262
return ExecInContainerPattern.execInContainer(getDockerClient(), getContainerInfo(), outputCharset, command);
263263
}
264264

265+
/**
266+
* Run a command inside a running container as a given user, as using "docker exec -u user".
267+
* <p>
268+
* @see ExecInContainerPattern#execInContainerWithUser(DockerClient, InspectContainerResponse, String, String...)
269+
*/
270+
default Container.ExecResult execInContainerWithUser(String user, String... command)
271+
throws UnsupportedOperationException, IOException, InterruptedException {
272+
return ExecInContainerPattern.execInContainerWithUser(getDockerClient(), getContainerInfo(), user, command);
273+
}
274+
275+
/**
276+
* Run a command inside a running container as a given user, as using "docker exec -u user".
277+
* <p>
278+
* @see ExecInContainerPattern#execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String, String...)
279+
*/
280+
default Container.ExecResult execInContainerWithUser(Charset outputCharset, String user, String... command)
281+
throws UnsupportedOperationException, IOException, InterruptedException {
282+
return ExecInContainerPattern.execInContainerWithUser(
283+
getDockerClient(),
284+
getContainerInfo(),
285+
outputCharset,
286+
user,
287+
command
288+
);
289+
}
290+
265291
/**
266292
*
267293
* Copies a file or directory to the container.

core/src/main/java/org/testcontainers/containers/ExecInContainerPattern.java

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.testcontainers.containers;
22

33
import com.github.dockerjava.api.DockerClient;
4+
import com.github.dockerjava.api.command.ExecCreateCmd;
45
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
56
import com.github.dockerjava.api.command.InspectContainerResponse;
67
import com.github.dockerjava.api.exception.DockerException;
@@ -45,7 +46,7 @@ public Container.ExecResult execInContainer(
4546
String... command
4647
) throws UnsupportedOperationException, IOException, InterruptedException {
4748
DockerClient dockerClient = DockerClientFactory.instance().client();
48-
return execInContainer(dockerClient, containerInfo, outputCharset, command);
49+
return execInContainerWithUser(dockerClient, containerInfo, outputCharset, null, command);
4950
}
5051

5152
/**
@@ -55,34 +56,77 @@ public Container.ExecResult execInContainer(
5556
* @param dockerClient the {@link DockerClient}
5657
* @param containerInfo the container info
5758
* @param command the command to execute
58-
* @see #execInContainer(DockerClient, InspectContainerResponse, Charset, String...)
59+
* @see #execInContainerWithUser(DockerClient, InspectContainerResponse, String, String...)
5960
*/
6061
public Container.ExecResult execInContainer(
6162
DockerClient dockerClient,
6263
InspectContainerResponse containerInfo,
6364
String... command
6465
) throws UnsupportedOperationException, IOException, InterruptedException {
65-
return execInContainer(dockerClient, containerInfo, StandardCharsets.UTF_8, command);
66+
return execInContainerWithUser(dockerClient, containerInfo, StandardCharsets.UTF_8, null, command);
6667
}
6768

6869
/**
69-
* Run a command inside a running container, as though using "docker exec".
70+
* Run a command inside a running container, as though using "docker exec", and interpreting
71+
* the output as UTF8.
72+
* <p></p>
73+
* @param dockerClient the {@link DockerClient}
74+
* @param containerInfo the container info
75+
* @param outputCharset the character set used to interpret the output.
76+
* @param command the command to execute
77+
* @see #execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String, String...)
78+
*/
79+
public Container.ExecResult execInContainer(
80+
DockerClient dockerClient,
81+
InspectContainerResponse containerInfo,
82+
Charset outputCharset,
83+
String... command
84+
) throws UnsupportedOperationException, IOException, InterruptedException {
85+
return execInContainerWithUser(dockerClient, containerInfo, outputCharset, null, command);
86+
}
87+
88+
/**
89+
* Run a command inside a running container as a given user, as using "docker exec -u user" and
90+
* interpreting the output as UTF8.
7091
* <p>
7192
* This functionality is not available on a docker daemon running the older "lxc" execution driver. At
7293
* the time of writing, CircleCI was using this driver.
7394
* @param dockerClient the {@link DockerClient}
7495
* @param containerInfo the container info
96+
* @param user the user to run the command with, optional
97+
* @param command the command to execute
98+
* @see #execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String,
99+
* String...)
100+
*/
101+
public Container.ExecResult execInContainerWithUser(
102+
DockerClient dockerClient,
103+
InspectContainerResponse containerInfo,
104+
String user,
105+
String... command
106+
) throws UnsupportedOperationException, IOException, InterruptedException {
107+
return execInContainerWithUser(dockerClient, containerInfo, StandardCharsets.UTF_8, user, command);
108+
}
109+
110+
/**
111+
* Run a command inside a running container as a given user, as using "docker exec -u user".
112+
* <p>
113+
* This functionality is not available on a docker daemon running the older "lxc" execution
114+
* driver. At the time of writing, CircleCI was using this driver.
115+
* @param dockerClient the {@link DockerClient}
116+
* @param containerInfo the container info
75117
* @param outputCharset the character set used to interpret the output.
118+
* @param user the user to run the command with, optional
76119
* @param command the parts of the command to run
77120
* @return the result of execution
78121
* @throws IOException if there's an issue communicating with Docker
79122
* @throws InterruptedException if the thread waiting for the response is interrupted
80123
* @throws UnsupportedOperationException if the docker daemon you're connecting to doesn't support "exec".
81124
*/
82-
public Container.ExecResult execInContainer(
125+
public Container.ExecResult execInContainerWithUser(
83126
DockerClient dockerClient,
84127
InspectContainerResponse containerInfo,
85128
Charset outputCharset,
129+
String user,
86130
String... command
87131
) throws UnsupportedOperationException, IOException, InterruptedException {
88132
if (!TestEnvironment.dockerExecutionDriverSupportsExec()) {
@@ -100,12 +144,17 @@ public Container.ExecResult execInContainer(
100144
String containerName = containerInfo.getName();
101145

102146
log.debug("{}: Running \"exec\" command: {}", containerName, String.join(" ", command));
103-
final ExecCreateCmdResponse execCreateCmdResponse = dockerClient
147+
final ExecCreateCmd execCreateCmd = dockerClient
104148
.execCreateCmd(containerId)
105149
.withAttachStdout(true)
106150
.withAttachStderr(true)
107-
.withCmd(command)
108-
.exec();
151+
.withCmd(command);
152+
if (user != null && !user.isEmpty()) {
153+
log.debug("{}: Running \"exec\" command with user: {}", containerName, user);
154+
execCreateCmd.withUser(user);
155+
}
156+
157+
final ExecCreateCmdResponse execCreateCmdResponse = execCreateCmd.exec();
109158

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

117166
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(callback).awaitCompletion();
118167
}
119-
Integer exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCode();
168+
int exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCodeLong().intValue();
120169

121170
final Container.ExecResult result = new Container.ExecResult(
122171
exitCode,

core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,21 @@ public void testExecInContainer() throws Exception {
369369
// We expect to reach this point for modern Docker versions.
370370
}
371371

372+
@Test
373+
public void testExecInContainerWithUser() throws Exception {
374+
// The older "lxc" execution driver doesn't support "exec". At the time of writing (2016/03/29),
375+
// that's the case for CircleCI.
376+
// Once they resolve the issue, this clause can be removed.
377+
Assume.assumeTrue(TestEnvironment.dockerExecutionDriverSupportsExec());
378+
379+
final GenericContainer.ExecResult result = redis.execInContainerWithUser("redis", "whoami");
380+
assertThat(result.getStdout())
381+
.as("Output for \"whoami\" command should start with \"redis\"")
382+
.startsWith("redis");
383+
assertThat(result.getStderr()).as("Stderr for \"whoami\" command should be empty").isEmpty();
384+
// We expect to reach this point for modern Docker versions.
385+
}
386+
372387
@Test
373388
public void extraHostTest() throws IOException {
374389
BufferedReader br = getReaderForContainerPort80(alpineExtrahost);

0 commit comments

Comments
 (0)