Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-36082] [FIXED JENKINS-32790] DockerToolInstaller was b…
…roken.
  • Loading branch information
jglick committed Sep 2, 2016
1 parent 004cc34 commit dc2fd75
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 53 deletions.
9 changes: 2 additions & 7 deletions pom.xml
Expand Up @@ -5,8 +5,8 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.9</version>
<relativePath />
<version>2.14</version>
<relativePath/>
</parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>docker-commons</artifactId>
Expand Down Expand Up @@ -71,11 +71,6 @@
<artifactId>icon-shim</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>annotations</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>

</project>
Expand Up @@ -24,6 +24,7 @@

package org.jenkinsci.plugins.docker.commons.tools;

import hudson.AbortException;
import hudson.Extension;
import hudson.FilePath;
import hudson.ProxyConfiguration;
Expand All @@ -33,23 +34,16 @@
import hudson.tools.ToolInstallation;
import hudson.tools.ToolInstaller;
import hudson.tools.ToolInstallerDescriptor;
import hudson.util.IOUtils;
import jenkins.MasterToSlaveFileCallable;
import jenkins.security.MasterToSlaveCallable;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import javax.annotation.Nonnull;
import jenkins.security.MasterToSlaveCallable;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Download and install Docker CLI binary, see https://docs.docker.com/installation/binaries/
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
* Download and install Docker CLI binary, see https://docs.docker.com/engine/installation/binaries/
*/
public class DockerToolInstaller extends ToolInstaller {

Expand All @@ -72,16 +66,7 @@ public FilePath performInstallation(ToolInstallation toolInstallation, @Nonnull
if (nodeChannel == null) {
throw new IllegalStateException("Node is offline");
}
String os = nodeChannel.call(new MasterToSlaveCallable<String, IOException>() {
public String call() throws IOException {
String os = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch").contains("64") ? "x86_64" : "i386";
if (os.contains("linux")) return "Linux/" + arch;
if (os.contains("windows")) return "Windows/" + arch;
if (os.contains("mac")) return "Darwin/" + arch;
throw new IOException("Failed to determine OS architecture " + os + ":" + arch);
}
});
String os = nodeChannel.call(new FindArch());

final URL url = new URL("https://get.docker.com/builds/"+os+"/docker-"+version);
FilePath install = preferredLocation(tool, node);
Expand Down Expand Up @@ -123,40 +108,35 @@ public String call() throws IOException {
install.deleteContents();
}

listener.getLogger().println("Downloading Docker client " + version);
listener.getLogger().println(Messages.DockerToolInstaller_downloading_docker_client_(version));
FilePath bin = install.child("bin");
bin.mkdirs();
FilePath docker = bin.child("docker");

if (install.isRemote()) {
// First try to download from the slave machine.
// First try to download raw Docker binary from the agent machine, as this is fastest, but only available up to 1.10.x.
bin.mkdirs();
try {
install.act(new MasterToSlaveFileCallable<Object>() {
public Object invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
InputStream in = url.openStream();
IOUtils.copy(in, f);
in.close();
return null;
}
});
docker.copyFrom(url);
} catch (IOException x) {
x.printStackTrace(listener.error("Failed to download " + url + " from slave; will retry from master"));
InputStream in = ProxyConfiguration.getInputStream(url);
docker.copyFrom(in);
in.close();
listener.error("Failed to download pre-1.11.x URL " + url + " from agent: " + x);
}
} else {
InputStream in = ProxyConfiguration.getInputStream(url);
docker.copyFrom(in);
in.close();
}

docker.act(new MasterToSlaveFileCallable<Object>() {
public Object invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
f.setExecutable(true);
return null;
if (!docker.exists()) {
// That did not work, or we did not even try.
// Fall back to downloading the tarball, which is available for all versions.
URL tgz = new URL(url + ".tgz");
// The core utility automatically tries a direct download first, followed by a download via master.
install.installIfNecessaryFrom(tgz, listener, "Unpacking " + tgz + " to " + install + " on " + node.getDisplayName());
// But it is in the wrong directory structure, and contains various server binaries we do not need.
bin.mkdirs(); // installIfNecessaryFrom will wipe it out
install.child("docker/docker").renameTo(docker);
if (!docker.exists()) { // TODO FilePath.renameTo does not check its return value
throw new AbortException(tgz + " did not contain a docker/docker entry as expected");
}
});
install.child("docker").deleteRecursive();
}

docker.chmod(0777);

timestamp.touch(sourceTimestamp);
return install;
Expand All @@ -176,4 +156,18 @@ public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
}
}

private static class FindArch extends MasterToSlaveCallable<String,IOException> {

@Override
public String call() throws IOException {
String os = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch").contains("64") ? "x86_64" : "i386";
if (os.contains("linux")) return "Linux/" + arch;
if (os.contains("windows")) return "Windows/" + arch;
if (os.contains("mac")) return "Darwin/" + arch;
throw new IOException("Failed to determine OS architecture " + os + ":" + arch);
}

}

}
@@ -0,0 +1,23 @@
# The MIT License
#
# Copyright 2016 CloudBees, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

DockerToolInstaller.downloading_docker_client_=Downloading Docker client {0}
@@ -0,0 +1,88 @@
/*
* The MIT License
*
* Copyright 2016 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.docker.commons.tools;

import hudson.FilePath;
import hudson.model.TaskListener;
import hudson.slaves.DumbSlave;
import hudson.tools.InstallSourceProperty;
import hudson.util.StreamTaskListener;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import org.apache.commons.io.output.TeeOutputStream;
import static org.hamcrest.Matchers.*;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.Rule;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

public class DockerToolInstallerTest {

@Rule
public JenkinsRule r = new JenkinsRule();

@Issue({"JENKINS-36082", "JENKINS-32790"})
@Test
public void smokes() throws Exception {
try {
new URL("https://get.docker.com/").openStream().close();
} catch (IOException x) {
Assume.assumeNoException("Cannot contact get.docker.com, perhaps test machine is not online", x);
}
r.jenkins.getDescriptorByType(DockerTool.DescriptorImpl.class).setInstallations(
new DockerTool("latest", "", Collections.singletonList(new InstallSourceProperty(Collections.singletonList(new DockerToolInstaller("", "latest"))))),
new DockerTool("1.10.0", "", Collections.singletonList(new InstallSourceProperty(Collections.singletonList(new DockerToolInstaller("", "1.10.0"))))));
DumbSlave slave = r.createOnlineSlave();
FilePath toolDir = slave.getRootPath().child("tools/org.jenkinsci.plugins.docker.commons.tools.DockerTool");
FilePath exe10 = toolDir.child("1.10.0/bin/docker");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
TaskListener l = new StreamTaskListener(new TeeOutputStream(baos, System.err));
// Download 1.10.0 for first time:
assertEquals(exe10.getRemote(), DockerTool.getExecutable("1.10.0", slave, l, null));
assertTrue(exe10.exists());
assertThat(baos.toString(), containsString(Messages.DockerToolInstaller_downloading_docker_client_("1.10.0")));
// Next time we do not need to download:
baos.reset();
assertEquals(exe10.getRemote(), DockerTool.getExecutable("1.10.0", slave, l, null));
assertTrue(exe10.exists());
assertThat(baos.toString(), not(containsString(Messages.DockerToolInstaller_downloading_docker_client_("1.10.0"))));
// Download latest for the first time:
baos.reset();
FilePath exeLatest = toolDir.child("latest/bin/docker");
assertEquals(exeLatest.getRemote(), DockerTool.getExecutable("latest", slave, l, null));
assertTrue(exeLatest.exists());
assertThat(baos.toString(), containsString(Messages.DockerToolInstaller_downloading_docker_client_("latest")));
// Next time we do not need to download:
baos.reset();
assertEquals(exeLatest.getRemote(), DockerTool.getExecutable("latest", slave, l, null));
assertTrue(exeLatest.exists());
assertThat(baos.toString(), not(containsString(Messages.DockerToolInstaller_downloading_docker_client_("latest"))));
assertThat("we do not have any extra files in here", toolDir.list("**"), arrayContainingInAnyOrder(exe10, toolDir.child("1.10.0/.timestamp"), exeLatest, toolDir.child("latest/.timestamp")));
}

}

0 comments on commit dc2fd75

Please sign in to comment.