From 12f27b0f3916b86b4d3804fd8d8657cd50d6bcad Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Wed, 1 Aug 2018 13:55:30 -0400 Subject: [PATCH 01/11] Added support to run Docker stages on Windows slaves --- .../docker/workflow/WithContainerStep.java | 20 ++- .../workflow/client/WindowsDockerClient.java | 129 ++++++++++++++++++ .../docker/workflow/DockerTestUtil.java | 20 +++ .../workflow/client/DockerClientTest.java | 22 +-- .../client/WindowsDockerClientTest.java | 57 ++++++++ 5 files changed, 222 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java create mode 100644 src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java 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 5569208e6..64c6afeac 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -24,6 +24,9 @@ package org.jenkinsci.plugins.docker.workflow; import com.google.common.base.Optional; +import hudson.slaves.NodeProperty; +import hudson.slaves.NodePropertyDescriptor; +import hudson.util.DescribableList; import org.jenkinsci.plugins.docker.workflow.client.DockerClient; import com.google.inject.Inject; import hudson.AbortException; @@ -63,6 +66,7 @@ import javax.annotation.CheckForNull; import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.jenkinsci.plugins.docker.commons.tools.DockerTool; +import org.jenkinsci.plugins.docker.workflow.client.WindowsDockerClient; import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; @@ -111,7 +115,6 @@ private static void destroy(String container, Launcher launcher, Node node, EnvV // TODO switch to GeneralNonBlockingStepExecution public static class Execution extends AbstractStepExecutionImpl { - private static final long serialVersionUID = 1; @Inject(optional=true) private transient WithContainerStep step; @StepContextParameter private transient Launcher launcher; @@ -138,7 +141,9 @@ public static class Execution extends AbstractStepExecutionImpl { workspace.mkdirs(); // otherwise it may be owned by root when created for -v String ws = workspace.getRemote(); toolName = step.toolName; - DockerClient dockerClient = new DockerClient(launcher, node, toolName); + DockerClient dockerClient = launcher.isUnix() + ? new DockerClient(launcher, node, toolName) + : new WindowsDockerClient(launcher, node, toolName); VersionNumber dockerVersion = dockerClient.version(); if (dockerVersion != null) { @@ -166,7 +171,11 @@ public static class Execution extends AbstractStepExecutionImpl { // check if there is any volume which contains the directory boolean found = false; for (String vol : mountedVolumes) { - if (dir.startsWith(vol)) { + boolean dirStartsWithVol = launcher.isUnix() + ? dir.startsWith(vol) // Linux + : dir.toLowerCase().startsWith(vol.toLowerCase()); // Windows + + if (dirStartsWithVol) { volumesFromContainers.add(containerId.get()); found = true; break; @@ -183,9 +192,10 @@ public static class Execution extends AbstractStepExecutionImpl { volumes.put(tmp, tmp); } - container = dockerClient.run(env, step.image, step.args, ws, volumes, volumesFromContainers, envReduced, dockerClient.whoAmI(), /* expected to hang until killed */ "cat"); + 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("cat")) { + if (!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/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java new file mode 100644 index 000000000..81a1cab53 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java @@ -0,0 +1,129 @@ +package org.jenkinsci.plugins.docker.workflow.client; + +import com.google.common.base.Optional; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Node; +import hudson.util.ArgumentListBuilder; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import java.io.*; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class WindowsDockerClient extends DockerClient { + private static final Logger LOGGER = Logger.getLogger(WindowsDockerClient.class.getName()); + + private final Launcher launcher; + private final Node node; + + public WindowsDockerClient(@Nonnull Launcher launcher, @CheckForNull Node node, @CheckForNull String toolName) { + super(launcher, node, toolName); + this.launcher = launcher; + this.node = node; + } + + @Override + public String run(@Nonnull EnvVars launchEnv, @Nonnull String image, @CheckForNull String args, @CheckForNull String workdir, @Nonnull Map volumes, @Nonnull Collection volumesFromContainers, @Nonnull EnvVars containerEnv, @Nonnull String user, @Nonnull String... command) throws IOException, InterruptedException { + ArgumentListBuilder argb = new ArgumentListBuilder("docker", "run", "-d", "-t"); + if (args != null) { + argb.addTokenized(args); + } + + if (workdir != null) { + argb.add("-w", workdir); + } + for (Map.Entry volume : volumes.entrySet()) { + argb.add("-v", volume.getKey() + ":" + volume.getValue()); + } + for (String containerId : volumesFromContainers) { + argb.add("--volumes-from", containerId); + } + for (Map.Entry variable : containerEnv.entrySet()) { + argb.add("-e"); + argb.addMasked(variable.getKey()+"="+variable.getValue()); + } + argb.add(image).add(command); + + LaunchResult result = launch(launchEnv, false, null, argb); + if (result.getStatus() == 0) { + return result.getOut(); + } else { + throw new IOException(String.format("Failed to run image '%s'. Error: %s", image, result.getErr())); + } + } + + @Override + public List listProcess(@Nonnull EnvVars launchEnv, @Nonnull String containerId) throws IOException, InterruptedException { + LaunchResult result = launch(launchEnv, false, null, "docker", "top", containerId); + if (result.getStatus() != 0) { + throw new IOException(String.format("Failed to run top '%s'. Error: %s", containerId, result.getErr())); + } + List processes = new ArrayList<>(); + try (Reader r = new StringReader(result.getOut()); + BufferedReader in = new BufferedReader(r)) { + String line; + in.readLine(); // ps header + while ((line = in.readLine()) != null) { + final StringTokenizer stringTokenizer = new StringTokenizer(line, " "); + if (stringTokenizer.countTokens() < 1) { + throw new IOException("Unexpected `docker top` output : "+line); + } + + processes.add(stringTokenizer.nextToken()); // COMMAND + } + } + return processes; + } + + @Override + public Optional getContainerIdIfContainerized() throws IOException, InterruptedException { + if (node == null || + launch(new EnvVars(), true, null, "sc.exe", "query", "cexecsvc").getStatus() != 0) { + return Optional.absent(); + } + + LaunchResult getComputerName = launch(new EnvVars(), true, null, "hostname"); + if(getComputerName.getStatus() != 0) { + throw new IOException("Failed to get hostname."); + } + + String shortID = getComputerName.getOut().toLowerCase(); + LaunchResult getLongIdResult = launch(new EnvVars(), true, null, "docker", "inspect", shortID, "--format={{.Id}}"); + if(getLongIdResult.getStatus() != 0) { + LOGGER.log(Level.INFO, "Running inside of a container but cannot determine container ID from current environment."); + return Optional.absent(); + } + + return Optional.of(getLongIdResult.getOut()); + } + + private LaunchResult launch(@Nonnull EnvVars env, boolean quiet, FilePath workDir, String... args) throws IOException, InterruptedException { + return launch(env, quiet, workDir, new ArgumentListBuilder(args)); + } + private LaunchResult launch(@CheckForNull @Nonnull EnvVars env, boolean quiet, FilePath workDir, ArgumentListBuilder argb) throws IOException, InterruptedException { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Executing command \"{0}\"", argb); + } + + Launcher.ProcStarter procStarter = launcher.launch(); + if (workDir != null) { + procStarter.pwd(workDir); + } + + LaunchResult result = new LaunchResult(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + result.setStatus(procStarter.quiet(quiet).cmds(argb).envs(env).stdout(out).stderr(err).start().joinWithTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS, launcher.getListener())); + final String charsetName = Charset.defaultCharset().name(); + result.setOut(out.toString(charsetName)); + result.setErr(err.toString(charsetName)); + + return result; + } +} diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/DockerTestUtil.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/DockerTestUtil.java index 97eaffd43..486a433d5 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/DockerTestUtil.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/DockerTestUtil.java @@ -23,6 +23,7 @@ */ package org.jenkinsci.plugins.docker.workflow; +import hudson.EnvVars; import org.jenkinsci.plugins.docker.workflow.client.DockerClient; import hudson.Launcher; import hudson.util.StreamTaskListener; @@ -64,6 +65,25 @@ public static void assumeNotWindows() throws Exception { Assume.assumeFalse(System.getProperty("os.name").toLowerCase().contains("windows")); } + public static EnvVars newDockerLaunchEnv() { + // Create the KeyMaterial for connecting to the docker host/server. + // E.g. currently need to add something like the following to your env + // -DDOCKER_HOST_FOR_TEST="tcp://192.168.x.y:2376" + // -DDOCKER_HOST_KEY_DIR_FOR_TEST="/Users/tfennelly/.boot2docker/certs/boot2docker-vm" + final String docker_host_for_test = System.getProperty("DOCKER_HOST_FOR_TEST"); + final String docker_host_key_dir_for_test = System.getProperty("DOCKER_HOST_KEY_DIR_FOR_TEST"); + + EnvVars env = new EnvVars(); + if (docker_host_for_test != null) { + env.put("DOCKER_HOST", docker_host_for_test); + } + if (docker_host_key_dir_for_test != null) { + env.put("DOCKER_TLS_VERIFY", "1"); + env.put("DOCKER_CERT_PATH", docker_host_key_dir_for_test); + } + return env; + } + private DockerTestUtil() {} } diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/client/DockerClientTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/client/DockerClientTest.java index 3052aef0c..2351e8eef 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/client/DockerClientTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/client/DockerClientTest.java @@ -57,7 +57,7 @@ public void setup() throws Exception { @Test public void test_run() throws IOException, InterruptedException { - EnvVars launchEnv = newLaunchEnv(); + EnvVars launchEnv = DockerTestUtil.newDockerLaunchEnv(); String containerId = dockerClient.run(launchEnv, "learn/tutorial", null, null, Collections.emptyMap(), Collections.emptyList(), new EnvVars(), dockerClient.whoAmI(), "cat"); @@ -87,24 +87,4 @@ public void test_valid_version() { public void test_invalid_version() { Assert.assertNull(DockerClient.parseVersionNumber("xxx")); } - - - private EnvVars newLaunchEnv() { - // Create the KeyMaterial for connecting to the docker host/server. - // E.g. currently need to add something like the following to your env - // -DDOCKER_HOST_FOR_TEST="tcp://192.168.x.y:2376" - // -DDOCKER_HOST_KEY_DIR_FOR_TEST="/Users/tfennelly/.boot2docker/certs/boot2docker-vm" - final String docker_host_for_test = System.getProperty("DOCKER_HOST_FOR_TEST"); - final String docker_host_key_dir_for_test = System.getProperty("DOCKER_HOST_KEY_DIR_FOR_TEST"); - - EnvVars env = new EnvVars(); - if (docker_host_for_test != null) { - env.put("DOCKER_HOST", docker_host_for_test); - } - if (docker_host_key_dir_for_test != null) { - env.put("DOCKER_TLS_VERIFY", "1"); - env.put("DOCKER_CERT_PATH", docker_host_key_dir_for_test); - } - return env; - } } diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java new file mode 100644 index 000000000..909f6c383 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java @@ -0,0 +1,57 @@ +package org.jenkinsci.plugins.docker.workflow.client; + +import hudson.EnvVars; +import hudson.Launcher; +import hudson.model.TaskListener; +import hudson.util.StreamTaskListener; +import org.jenkinsci.plugins.docker.commons.fingerprint.ContainerRecord; +import org.jenkinsci.plugins.docker.workflow.DockerTestUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; + +public class WindowsDockerClientTest { + + private DockerClient dockerClient; + + @Before + public void setup() throws Exception { + DockerTestUtil.assumeDocker(); + + TaskListener taskListener = StreamTaskListener.fromStderr(); + Launcher.LocalLauncher launcher = new Launcher.LocalLauncher(taskListener); + + dockerClient = new WindowsDockerClient(launcher, null, null); + } + + @Test + public void test_run() throws IOException, InterruptedException { + EnvVars launchEnv = DockerTestUtil.newDockerLaunchEnv(); + String containerId = dockerClient.run( + launchEnv, + "microsoft/nanoserver", + null, + null, + Collections.emptyMap(), + Collections.emptyList(), + new EnvVars(), + dockerClient.whoAmI(), + "cmd"); + + Assert.assertEquals(64, containerId.length()); + ContainerRecord containerRecord = dockerClient.getContainerRecord(launchEnv, containerId); + Assert.assertEquals(dockerClient.inspect(launchEnv, "microsoft/nanoserver", ".Id"), containerRecord.getImageId()); + Assert.assertTrue(containerRecord.getContainerName().length() > 0); + Assert.assertTrue(containerRecord.getHost().length() > 0); + Assert.assertTrue(containerRecord.getCreated() > 1000000000000L); + Assert.assertEquals(Collections.emptyList(), dockerClient.getVolumes(launchEnv, containerId)); + + // Also test that the stop works and cleans up after itself + Assert.assertNotNull(dockerClient.inspect(launchEnv, containerId, ".Name")); + dockerClient.stop(launchEnv, containerId); + Assert.assertNull(dockerClient.inspect(launchEnv, containerId, ".Name")); + } +} From 3b3d7986dfd8f3935993b036851a9dfc148d0c05 Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Thu, 2 Aug 2018 10:09:36 -0400 Subject: [PATCH 02/11] Updated changes to support docker.inside in reference to PR-98 --- .../docker/workflow/WithContainerStep.java | 3 +++ .../plugins/docker/workflow/Docker.groovy | 24 ++++++++++++------- 2 files changed, 18 insertions(+), 9 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 64c6afeac..8622a31a6 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -151,6 +151,9 @@ public static class Execution extends AbstractStepExecutionImpl { throw new AbortException("The docker version is less than v1.7. Pipeline functions requiring 'docker exec' (e.g. 'docker.inside') or SELinux labeling will not work."); } else if (dockerVersion.isOlderThan(new VersionNumber("1.8"))) { listener.error("The docker version is less than v1.8. Running a 'docker.inside' from inside a container will not work."); + } else if (dockerVersion.isOlderThan(new VersionNumber("1.13"))) { + if (!launcher.isUnix()) + throw new AbortException("The docker version is less than v1.13. Running a 'docker.inside' from inside a Windows container will not work."); } } else { listener.error("Failed to parse docker version. Please note there is a minimum docker version requirement of v1.7."); diff --git a/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy b/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy index b88ad6973..4790a0163 100644 --- a/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy +++ b/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy @@ -75,11 +75,17 @@ class Docker implements Serializable { new Image(this, id) } + String shell() { + node { + script.isUnix() ? "sh" : "bat" + } + } + public Image build(String image, String args = '.') { node { def commandLine = "docker build -t ${image} ${args}" - script.sh commandLine + script."${shell()}" commandLine this.image(image) } } @@ -107,11 +113,11 @@ class Docker implements Serializable { public V inside(String args = '', Closure body) { docker.node { def toRun = imageName() - if (toRun != id && docker.script.sh(script: "docker inspect -f . ${id}", returnStatus: true) == 0) { + if (toRun != id && docker.script."${shell()}"(script: "docker inspect -f . ${id}", returnStatus: true) == 0) { // Can run it without registry prefix, because it was locally built. toRun = id } else { - if (docker.script.sh(script: "docker inspect -f . ${toRun}", returnStatus: true) != 0) { + if (docker.script."${shell()}"(script: "docker inspect -f . ${toRun}", returnStatus: true) != 0) { // Not yet present locally. // withDockerContainer requires the image to be available locally, since its start phase is not a durable task. pull() @@ -125,13 +131,13 @@ class Docker implements Serializable { public void pull() { docker.node { - docker.script.sh "docker pull ${imageName()}" + docker.script."${shell()}" "docker pull ${imageName()}" } } public Container run(String args = '', String command = "") { docker.node { - def container = docker.script.sh(script: "docker run -d${args != '' ? ' ' + args : ''} ${id}${command != '' ? ' ' + command : ''}", returnStdout: true).trim() + def container = docker.script."${shell()}"(script: "docker run -d${args != '' ? ' ' + args : ''} ${id}${command != '' ? ' ' + command : ''}", returnStdout: true).trim() new Container(docker, container) } } @@ -150,7 +156,7 @@ class Docker implements Serializable { public void tag(String tagName = parsedId.tag, boolean force = true) { docker.node { def taggedImageName = toQualifiedImageName(parsedId.userAndRepo + ':' + tagName) - docker.script.sh "docker tag ${id} ${taggedImageName}" + docker.script."${shell()}" "docker tag ${id} ${taggedImageName}" return taggedImageName; } } @@ -160,7 +166,7 @@ class Docker implements Serializable { // The image may have already been tagged, so the tagging may be a no-op. // That's ok since tagging is cheap. def taggedImageName = tag(tagName, force) - docker.script.sh "docker push ${taggedImageName}" + docker.script."${shell()}" "docker push ${taggedImageName}" } } @@ -177,11 +183,11 @@ class Docker implements Serializable { } public void stop() { - docker.script.sh "docker stop ${id} && docker rm -f ${id}" + docker.script."${shell()}" "docker stop ${id} && docker rm -f ${id}" } public String port(int port) { - docker.script.sh(script: "docker port ${id} ${port}", returnStdout: true).trim() + docker.script."${shell()}"(script: "docker port ${id} ${port}", returnStdout: true).trim() } } From 348b1d9dea979f497c3e943cf5c559366f5224d0 Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Thu, 2 Aug 2018 10:11:29 -0400 Subject: [PATCH 03/11] Cleaning up imports --- .../docker/workflow/WithContainerStep.java | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 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 8622a31a6..d5d6be46b 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -24,45 +24,36 @@ package org.jenkinsci.plugins.docker.workflow; import com.google.common.base.Optional; -import hudson.slaves.NodeProperty; -import hudson.slaves.NodePropertyDescriptor; -import hudson.util.DescribableList; -import org.jenkinsci.plugins.docker.workflow.client.DockerClient; import com.google.inject.Inject; -import hudson.AbortException; -import hudson.EnvVars; -import hudson.Extension; -import hudson.FilePath; -import hudson.Launcher; -import hudson.LauncherDecorator; -import hudson.Proc; -import hudson.Util; +import hudson.*; import hudson.model.Computer; import hudson.model.Node; import hudson.model.Run; import hudson.model.TaskListener; import hudson.slaves.WorkspaceList; +import hudson.util.VersionNumber; +import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; +import org.jenkinsci.plugins.docker.commons.tools.DockerTool; +import org.jenkinsci.plugins.docker.workflow.client.DockerClient; +import org.jenkinsci.plugins.docker.workflow.client.WindowsDockerClient; +import org.jenkinsci.plugins.workflow.steps.*; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; import hudson.util.VersionNumber; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import javax.annotation.CheckForNull; import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.jenkinsci.plugins.docker.commons.tools.DockerTool; From 3bcadcd3e4aa416043d13e57d2b85bc831fe9ef7 Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Thu, 2 Aug 2018 10:48:23 -0400 Subject: [PATCH 04/11] Fixing build issues - Fixing haphazard changes to Docker.groovy - Fixing FindBugs issue with annotations on WindowsDockerClient.launch() --- .../workflow/client/WindowsDockerClient.java | 2 +- .../plugins/docker/workflow/Docker.groovy | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java index 81a1cab53..1c92a6972 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java @@ -106,7 +106,7 @@ public Optional getContainerIdIfContainerized() throws IOException, Inte private LaunchResult launch(@Nonnull EnvVars env, boolean quiet, FilePath workDir, String... args) throws IOException, InterruptedException { return launch(env, quiet, workDir, new ArgumentListBuilder(args)); } - private LaunchResult launch(@CheckForNull @Nonnull EnvVars env, boolean quiet, FilePath workDir, ArgumentListBuilder argb) throws IOException, InterruptedException { + private LaunchResult launch(@Nonnull EnvVars env, boolean quiet, FilePath workDir, ArgumentListBuilder argb) throws IOException, InterruptedException { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Executing command \"{0}\"", argb); } diff --git a/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy b/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy index 4790a0163..71308f1f1 100644 --- a/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy +++ b/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy @@ -113,11 +113,11 @@ class Docker implements Serializable { public V inside(String args = '', Closure body) { docker.node { def toRun = imageName() - if (toRun != id && docker.script."${shell()}"(script: "docker inspect -f . ${id}", returnStatus: true) == 0) { + if (toRun != id && docker.script."${docker.shell()}"(script: "docker inspect -f . ${id}", returnStatus: true) == 0) { // Can run it without registry prefix, because it was locally built. toRun = id } else { - if (docker.script."${shell()}"(script: "docker inspect -f . ${toRun}", returnStatus: true) != 0) { + if (docker.script."${docker.shell()}"(script: "docker inspect -f . ${toRun}", returnStatus: true) != 0) { // Not yet present locally. // withDockerContainer requires the image to be available locally, since its start phase is not a durable task. pull() @@ -131,13 +131,13 @@ class Docker implements Serializable { public void pull() { docker.node { - docker.script."${shell()}" "docker pull ${imageName()}" + docker.script."${docker.shell()}" "docker pull ${imageName()}" } } public Container run(String args = '', String command = "") { docker.node { - def container = docker.script."${shell()}"(script: "docker run -d${args != '' ? ' ' + args : ''} ${id}${command != '' ? ' ' + command : ''}", returnStdout: true).trim() + def container = docker.script."${docker.shell()}"(script: "docker run -d${args != '' ? ' ' + args : ''} ${id}${command != '' ? ' ' + command : ''}", returnStdout: true).trim() new Container(docker, container) } } @@ -156,7 +156,7 @@ class Docker implements Serializable { public void tag(String tagName = parsedId.tag, boolean force = true) { docker.node { def taggedImageName = toQualifiedImageName(parsedId.userAndRepo + ':' + tagName) - docker.script."${shell()}" "docker tag ${id} ${taggedImageName}" + docker.script."${docker.shell()}" "docker tag ${id} ${taggedImageName}" return taggedImageName; } } @@ -166,7 +166,7 @@ class Docker implements Serializable { // The image may have already been tagged, so the tagging may be a no-op. // That's ok since tagging is cheap. def taggedImageName = tag(tagName, force) - docker.script."${shell()}" "docker push ${taggedImageName}" + docker.script."${docker.shell()}" "docker push ${taggedImageName}" } } @@ -183,11 +183,11 @@ class Docker implements Serializable { } public void stop() { - docker.script."${shell()}" "docker stop ${id} && docker rm -f ${id}" + docker.script."${docker.shell()}" "docker stop ${id} && docker rm -f ${id}" } public String port(int port) { - docker.script."${shell()}"(script: "docker port ${id} ${port}", returnStdout: true).trim() + docker.script."${docker.shell()}"(script: "docker port ${id} ${port}", returnStdout: true).trim() } } From 1722a14d7b2c892982320ea1932a317efe0cf1ad Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Thu, 2 Aug 2018 11:03:41 -0400 Subject: [PATCH 05/11] Removing annotations --- .../plugins/docker/workflow/client/WindowsDockerClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java index 1c92a6972..5046fc4c5 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java @@ -103,10 +103,10 @@ public Optional getContainerIdIfContainerized() throws IOException, Inte return Optional.of(getLongIdResult.getOut()); } - private LaunchResult launch(@Nonnull EnvVars env, boolean quiet, FilePath workDir, String... args) throws IOException, InterruptedException { + private LaunchResult launch(EnvVars env, boolean quiet, FilePath workDir, String... args) throws IOException, InterruptedException { return launch(env, quiet, workDir, new ArgumentListBuilder(args)); } - private LaunchResult launch(@Nonnull EnvVars env, boolean quiet, FilePath workDir, ArgumentListBuilder argb) throws IOException, InterruptedException { + private LaunchResult launch(EnvVars env, boolean quiet, FilePath workDir, ArgumentListBuilder argb) throws IOException, InterruptedException { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Executing command \"{0}\"", argb); } From 2d19156514d537e68079139c5924ff5277924221 Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Thu, 2 Aug 2018 11:15:32 -0400 Subject: [PATCH 06/11] Fixing DockerWindowsClient test_run() --- .../docker/workflow/client/WindowsDockerClientTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java index 909f6c383..c390e0735 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClientTest.java @@ -32,18 +32,18 @@ public void test_run() throws IOException, InterruptedException { EnvVars launchEnv = DockerTestUtil.newDockerLaunchEnv(); String containerId = dockerClient.run( launchEnv, - "microsoft/nanoserver", + "learn/tutorial", null, null, Collections.emptyMap(), Collections.emptyList(), new EnvVars(), dockerClient.whoAmI(), - "cmd"); + "cat"); Assert.assertEquals(64, containerId.length()); ContainerRecord containerRecord = dockerClient.getContainerRecord(launchEnv, containerId); - Assert.assertEquals(dockerClient.inspect(launchEnv, "microsoft/nanoserver", ".Id"), containerRecord.getImageId()); + Assert.assertEquals(dockerClient.inspect(launchEnv, "learn/tutorial", ".Id"), containerRecord.getImageId()); Assert.assertTrue(containerRecord.getContainerName().length() > 0); Assert.assertTrue(containerRecord.getHost().length() > 0); Assert.assertTrue(containerRecord.getCreated() > 1000000000000L); From 5c43bbe5f55214cfa6d8a921771214f966fa26c6 Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Tue, 9 Oct 2018 11:06:24 -0400 Subject: [PATCH 07/11] Updated whoAmI to properly get user on Windows --- .../docker/workflow/client/WindowsDockerClient.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java index 5046fc4c5..fba56fe24 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java @@ -103,6 +103,14 @@ public Optional getContainerIdIfContainerized() throws IOException, Inte return Optional.of(getLongIdResult.getOut()); } + @Override + public String whoAmI() throws IOException, InterruptedException { + try (ByteArrayOutputStream userId = new ByteArrayOutputStream()) { + launcher.launch().cmds("whoami").quiet(true).stdout(userId).start().joinWithTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS, launcher.getListener()); + return userId.toString(); + } + } + private LaunchResult launch(EnvVars env, boolean quiet, FilePath workDir, String... args) throws IOException, InterruptedException { return launch(env, quiet, workDir, new ArgumentListBuilder(args)); } From b9baff6ebdd402fc59bdc8316042a7ca70759d85 Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Tue, 9 Oct 2018 12:10:38 -0400 Subject: [PATCH 08/11] Fixing findBugs issues with default charset --- .../plugins/docker/workflow/client/WindowsDockerClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java index fba56fe24..77b117e9d 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/WindowsDockerClient.java @@ -107,7 +107,7 @@ public Optional getContainerIdIfContainerized() throws IOException, Inte public String whoAmI() throws IOException, InterruptedException { try (ByteArrayOutputStream userId = new ByteArrayOutputStream()) { launcher.launch().cmds("whoami").quiet(true).stdout(userId).start().joinWithTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS, launcher.getListener()); - return userId.toString(); + return userId.toString(Charset.defaultCharset().name()).trim(); } } From 20a2a26dda048f7799bd4970188a86513f76d5fb Mon Sep 17 00:00:00 2001 From: Ryan Butcher Date: Tue, 2 Jul 2019 15:01:14 -0400 Subject: [PATCH 09/11] Fixed imports in WithContainerStep.java --- .../docker/workflow/WithContainerStep.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 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 d5d6be46b..2d3e0ce53 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -50,23 +50,7 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nonnull; - -import hudson.util.VersionNumber; -import java.util.concurrent.TimeUnit; -import javax.annotation.CheckForNull; -import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; -import org.jenkinsci.plugins.docker.commons.tools.DockerTool; -import org.jenkinsci.plugins.docker.workflow.client.WindowsDockerClient; -import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; -import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; -import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; -import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; -import org.jenkinsci.plugins.workflow.steps.BodyInvoker; -import org.jenkinsci.plugins.workflow.steps.StepContext; -import org.jenkinsci.plugins.workflow.steps.StepContextParameter; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; +import java.util.stream.Collectors; public class WithContainerStep extends AbstractStepImpl { From b0d3faee564c86c59f7943e52320be1feee20d63 Mon Sep 17 00:00:00 2001 From: Joseph Petersen Date: Fri, 23 Aug 2019 12:44:04 +0200 Subject: [PATCH 10/11] fix windows path issue and skip whoami on Windows for now --- .../docker/workflow/WithContainerStep.java | 13 ++++++++-- .../docker/workflow/client/DockerClient.java | 25 +++++++++++++------ 2 files changed, 28 insertions(+), 10 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 2d3e0ce53..156e1e354 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -114,7 +114,7 @@ public static class Execution extends AbstractStepExecutionImpl { LOGGER.log(Level.FINE, "reduced environment: {0}", envReduced); workspace.mkdirs(); // otherwise it may be owned by root when created for -v - String ws = workspace.getRemote(); + String ws = getPath(workspace); toolName = step.toolName; DockerClient dockerClient = launcher.isUnix() ? new DockerClient(launcher, node, toolName) @@ -136,7 +136,7 @@ public static class Execution extends AbstractStepExecutionImpl { FilePath tempDir = tempDir(workspace); tempDir.mkdirs(); - String tmp = tempDir.getRemote(); + String tmp = getPath(tempDir); Map volumes = new LinkedHashMap(); Collection volumesFromContainers = new LinkedHashSet(); @@ -190,6 +190,15 @@ public static class Execution extends AbstractStepExecutionImpl { return false; } + private String getPath(FilePath filePath) + throws IOException, InterruptedException { + if (launcher.isUnix()) { + return filePath.getRemote(); + } else { + return filePath.toURI().getPath().substring(1).replace("\\", "/"); + } + } + // TODO use 1.652 use WorkspaceList.tempDir private static FilePath tempDir(FilePath ws) { return ws.sibling(ws.getName() + System.getProperty(WorkspaceList.class.getName(), "@") + "tmp"); diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/DockerClient.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/DockerClient.java index 37c4a303b..6562606fb 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/client/DockerClient.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/client/DockerClient.java @@ -31,10 +31,6 @@ import hudson.model.Node; import hudson.util.ArgumentListBuilder; import hudson.util.VersionNumber; -import org.jenkinsci.plugins.docker.commons.fingerprint.ContainerRecord; - -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -44,18 +40,22 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.Map; import java.util.List; -import java.util.Arrays; +import java.util.Map; import java.util.StringTokenizer; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import org.apache.commons.lang.StringUtils; +import org.jenkinsci.plugins.docker.commons.fingerprint.ContainerRecord; import org.jenkinsci.plugins.docker.commons.tools.DockerTool; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -106,7 +106,12 @@ public DockerClient(@Nonnull Launcher launcher, @CheckForNull Node node, @CheckF public String run(@Nonnull EnvVars launchEnv, @Nonnull String image, @CheckForNull String args, @CheckForNull String workdir, @Nonnull Map volumes, @Nonnull Collection volumesFromContainers, @Nonnull EnvVars containerEnv, @Nonnull String user, @Nonnull String... command) throws IOException, InterruptedException { ArgumentListBuilder argb = new ArgumentListBuilder(); - argb.add("run", "-t", "-d", "-u", user); + argb.add("run", "-t", "-d"); + + // Username might be empty because we are running on Windows + if (StringUtils.isNotEmpty(user)) { + argb.add("-u", user); + } if (args != null) { argb.addTokenized(args); } @@ -306,6 +311,10 @@ private LaunchResult launch(@CheckForNull @Nonnull EnvVars launchEnv, boolean qu * @return a {@link String} containing the uid:gid. */ public String whoAmI() throws IOException, InterruptedException { + if (!launcher.isUnix()) { + // Windows does not support username + return ""; + } ByteArrayOutputStream userId = new ByteArrayOutputStream(); launcher.launch().cmds("id", "-u").quiet(true).stdout(userId).start().joinWithTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS, launcher.getListener()); @@ -367,6 +376,6 @@ public List getVolumes(@Nonnull EnvVars launchEnv, String containerID) t if (volumes.isEmpty()) { return Collections.emptyList(); } - return Arrays.asList(volumes.split("\\n")); + return Arrays.asList(volumes.replace("\\", "/").split("\\n")); } } From 6982db7f7860e2952af198da6599c616a45bda5f Mon Sep 17 00:00:00 2001 From: Joseph Petersen Date: Fri, 23 Aug 2019 12:52:57 +0200 Subject: [PATCH 11/11] fix imports --- .../docker/workflow/WithContainerStep.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 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 156e1e354..8c6ccfb1a 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java @@ -25,18 +25,41 @@ import com.google.common.base.Optional; import com.google.inject.Inject; -import hudson.*; +import hudson.AbortException; +import hudson.EnvVars; +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.LauncherDecorator; +import hudson.Proc; +import hudson.Util; import hudson.model.Computer; import hudson.model.Node; import hudson.model.Run; import hudson.model.TaskListener; import hudson.slaves.WorkspaceList; import hudson.util.VersionNumber; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.jenkinsci.plugins.docker.commons.tools.DockerTool; import org.jenkinsci.plugins.docker.workflow.client.DockerClient; import org.jenkinsci.plugins.docker.workflow.client.WindowsDockerClient; -import org.jenkinsci.plugins.workflow.steps.*; +import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; +import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; +import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; +import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; +import org.jenkinsci.plugins.workflow.steps.BodyInvoker; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepContextParameter; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; @@ -46,7 +69,6 @@ import java.io.IOException; import java.io.Serializable; import java.nio.charset.Charset; -import java.util.*; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -103,6 +125,9 @@ public static class Execution extends AbstractStepExecutionImpl { private String container; private String toolName; + public Execution() { + } + @Override public boolean start() throws Exception { EnvVars envReduced = new EnvVars(env); EnvVars envHost = computer.getEnvironment();