From 49e0ddd9e092063a272db5222ee3d2bef96c2e26 Mon Sep 17 00:00:00 2001 From: "Meggle (Sebastian Bathke)" Date: Fri, 14 Jul 2023 22:21:40 +0200 Subject: [PATCH] Exec in container with custom user * allows to run commands with a custom user --- .../containers/ContainerState.java | 30 +++++++- .../containers/ExecInContainerPattern.java | 72 ++++++++++++++++--- .../junit/GenericContainerRuleTest.java | 13 ++++ 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/ContainerState.java b/core/src/main/java/org/testcontainers/containers/ContainerState.java index f355bf445fb..c29054a3420 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerState.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerState.java @@ -253,13 +253,39 @@ default Container.ExecResult execInContainer(String... command) } /** - * Run a command inside a running container, as though using "docker exec". + * Run a command inside a running container as a given user, as using "docker exec -u user". *

* @see ExecInContainerPattern#execInContainer(com.github.dockerjava.api.command.InspectContainerResponse, Charset, String...) */ default Container.ExecResult execInContainer(Charset outputCharset, String... command) + throws UnsupportedOperationException, IOException, InterruptedException { + return execInContainerWithUser(outputCharset, null, command); + } + + /** + * Run a command inside a running container as a given user, as using "docker exec -u user". + *

+ * @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". + *

+ * @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.execInContainer(getDockerClient(), getContainerInfo(), outputCharset, command); + return ExecInContainerPattern.execInContainerWithUser( + getDockerClient(), + getContainerInfo(), + outputCharset, + user, + command + ); } /** diff --git a/core/src/main/java/org/testcontainers/containers/ExecInContainerPattern.java b/core/src/main/java/org/testcontainers/containers/ExecInContainerPattern.java index fe641618cc4..0686de12f08 100644 --- a/core/src/main/java/org/testcontainers/containers/ExecInContainerPattern.java +++ b/core/src/main/java/org/testcontainers/containers/ExecInContainerPattern.java @@ -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; @@ -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); } /** @@ -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. + *

+ * @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. *

* 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". + *

+ * 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()) { @@ -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(); @@ -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, diff --git a/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java b/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java index a3628a9302f..4cb670d8093 100644 --- a/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java +++ b/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java @@ -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);