From 80df3a2f3031e7872f9cd0dbb7ead28091cdfc7f Mon Sep 17 00:00:00 2001 From: AbdulMateenzwl Date: Sat, 15 Aug 2026 23:40:31 +0100 Subject: [PATCH] [JENKINS-73447] Do not fail the build when `docker top` is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `docker top` requires cgroups, so it fails on rootless Docker and rootless dind even though the container is running normally. Its result is only a diagnostic — a mismatch merely logs an error and the build continues — but an `IOException` from `listProcess` propagated out of the step and aborted the build. Treat an unobtainable process list the same way: report it and skip the check. `InterruptedException` still propagates. --- .../docker/workflow/WithContainerStep.java | 13 +++++- .../workflow/WithContainerStepTest.java | 46 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java index b3428f7c5..8b2acfe0e 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -198,8 +198,17 @@ public Execution() { String command = launcher.isUnix() ? "cat" : "cmd.exe"; container = dockerClient.run(env, step.image, step.args, ws, volumes, volumesFromContainers, envReduced, dockerClient.whoAmI(), /* expected to hang until killed */ command); - final List ps = dockerClient.listProcess(env, container); - if (!ps.contains(command)) { + // The process list is only a diagnostic: `docker top` needs cgroups, and fails on configurations such as + // rootless Docker or rootless dind where the container itself is perfectly healthy (JENKINS-73447). + // Skip the check rather than failing the build when the process list cannot be obtained. + List ps = null; + try { + ps = dockerClient.listProcess(env, container); + } catch (IOException x) { + LOGGER.log(Level.FINE, "failed to list processes in container " + container, x); + listener.getLogger().println("Could not verify the command running in the container: " + x.getMessage()); + } + if (ps != null && !ps.contains(command)) { listener.error( "The container started but didn't run the expected command. " + "Please double check your ENTRYPOINT does execute the command passed as docker run argument, " + diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/WithContainerStepTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/WithContainerStepTest.java index 84b22e455..5660bf5d7 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/WithContainerStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/WithContainerStepTest.java @@ -37,7 +37,9 @@ import hudson.util.ArgumentListBuilder; import hudson.util.Secret; import hudson.util.StreamTaskListener; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; import java.util.logging.Level; @@ -69,6 +71,7 @@ import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; import org.junit.Assume; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import org.junit.ClassRule; import org.junit.Ignore; @@ -122,6 +125,49 @@ public class WithContainerStepTest { }); } + @Issue("JENKINS-73447") + @Test public void topFailureIsNotFatal() { + story.addStep(new Statement() { + @Override public void evaluate() throws Throwable { + DockerTestUtil.assumeDocker(); + DockerTestUtil.assumeNotWindows(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Launcher.LocalLauncher localLauncher = new Launcher.LocalLauncher(StreamTaskListener.NULL); + localLauncher.launch().cmds("/bin/sh", "-c", "command -v docker").stdout(out).start(). + joinWithTimeout(DockerClient.CLIENT_TIMEOUT, TimeUnit.SECONDS, localLauncher.getListener()); + String realDocker = out.toString(StandardCharsets.UTF_8).trim(); + assumeTrue("found docker on $PATH", !realDocker.isEmpty()); + // Stand in for a daemon where `docker top` fails because cgroups are unavailable, as with rootless Docker. + File toolHome = tmp.newFolder("no-cgroups"); + File bin = new File(toolHome, "bin"); + assertTrue(bin.mkdirs()); + File fakeDocker = new File(bin, "docker"); + FileUtils.writeStringToFile(fakeDocker, + "#!/bin/sh\n" + + "if [ \"$1\" = top ]; then\n" + + " echo 'Error response from daemon: runc did not terminate successfully: unable to get all container pids: operation not supported' >&2\n" + + " exit 1\n" + + "fi\n" + + "exec " + realDocker + " \"$@\"\n", StandardCharsets.UTF_8); + assertTrue(fakeDocker.setExecutable(true)); + story.j.jenkins.getDescriptorByType(DockerTool.DescriptorImpl.class).setInstallations( + new DockerTool("no-cgroups", toolHome.getAbsolutePath(), Collections.>emptyList())); + WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "prj"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + + " withDockerContainer(image: 'httpd:2.4.59', toolName: 'no-cgroups') {\n" + + " sh 'echo hello from the container'\n" + + " }\n" + + "}", true)); + WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + story.j.assertLogContains("Could not verify the command running in the container", b); + story.j.assertLogContains("hello from the container", b); + // The process list was unavailable, so the entrypoint diagnostic must not be reported as a failure. + story.j.assertLogNotContains("didn't run the expected command", b); + } + }); + } + @Issue("JENKINS-37719") @Ignore //Not working locally for cert release @Test public void hungDaemon() { story.addStep(new Statement() {