Skip to content

Commit

Permalink
Fix for JENKINS-24071
Browse files Browse the repository at this point in the history
Fix for issue that prevents Docker build step to build images on Jenkins
slaves.
  • Loading branch information
David Csakvari committed Sep 6, 2017
1 parent ff479d8 commit 30ab1b4
Show file tree
Hide file tree
Showing 24 changed files with 180 additions and 80 deletions.
@@ -1,14 +1,36 @@
package org.jenkinsci.plugins.dockerbuildstep;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isEmpty;

import java.io.Serializable;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;

import org.jenkinsci.plugins.dockerbuildstep.cmd.DockerCommand;
import org.jenkinsci.plugins.dockerbuildstep.cmd.DockerCommand.DockerCommandDescriptor;
import org.jenkinsci.plugins.dockerbuildstep.log.ConsoleLogger;
import org.jenkinsci.plugins.dockerbuildstep.util.Resolver;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.command.DockerCmdExecFactory;
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.LocalDirectorySSLConfig;
import com.github.dockerjava.core.SSLConfig;
import com.github.dockerjava.jaxrs.DockerCmdExecFactoryImpl;

import hudson.AbortException;
import hudson.DescriptorExtensionList;
import hudson.Extension;
Expand All @@ -19,26 +41,7 @@
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.jenkinsci.plugins.dockerbuildstep.cmd.DockerCommand;
import org.jenkinsci.plugins.dockerbuildstep.cmd.DockerCommand.DockerCommandDescriptor;
import org.jenkinsci.plugins.dockerbuildstep.log.ConsoleLogger;
import org.jenkinsci.plugins.dockerbuildstep.util.Resolver;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isEmpty;

/**
* Build step which executes various Docker commands via Docker REST API.
Expand Down Expand Up @@ -71,13 +74,13 @@ public boolean perform(@SuppressWarnings("rawtypes") AbstractBuild build, Launch
}

try {
dockerCmd.execute(build, clog);
dockerCmd.execute(launcher, build, clog);
} catch (DockerException e) {
clog.logError("command '" + dockerCmd.getDescriptor().getDisplayName() + "' failed: " + e.getMessage());
LOGGER.severe("Failed to execute Docker command " + dockerCmd.getDescriptor().getDisplayName() + ": "
+ e.getMessage());
throw new AbortException(e.getMessage());
}
}
return true;
}

Expand All @@ -87,7 +90,9 @@ public DescriptorImpl getDescriptor() {
}

@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> implements Serializable {

private static final long serialVersionUID = -4606090261824385504L;

private String dockerUrl;
private String dockerVersion;
Expand Down Expand Up @@ -130,7 +135,6 @@ public SSLContext getSSLContext() throws KeyManagementException, UnrecoverableKe
.withRegistryPassword(authConfig.getPassword())
.withRegistryUrl(authConfig.getRegistryAddress());
}
ClassLoader classLoader = Jenkins.getInstance().getPluginManager().uberClassLoader;
// using jaxrs/jersey implementation here (netty impl is also available)
DockerCmdExecFactory dockerCmdExecFactory = new DockerCmdExecFactoryImpl()
.withConnectTimeout(1000)
Expand Down Expand Up @@ -214,10 +218,19 @@ public DockerClient getDockerClient(AuthConfig authConfig) {
}

public DockerClient getDockerClient(AbstractBuild<?, ?> build, AuthConfig authConfig) {
String dockerUrlRes = build == null ? Resolver.envVar(dockerUrl) : Resolver.buildVar(build, dockerUrl);
String dockerVersionRes = build == null ? Resolver.envVar(dockerVersion) : Resolver.buildVar(build, dockerVersion);
String dockerCertPathRes = build == null ? Resolver.envVar(dockerCertPath) : Resolver.buildVar(build, dockerCertPath);
return createDockerClient(dockerUrlRes, dockerVersionRes, dockerCertPathRes, authConfig);
return getDockerClient(Resolver.buildVar(build, dockerUrl), Resolver.buildVar(build, dockerVersion), Resolver.buildVar(build, dockerCertPath), authConfig);
}

public Config getConfig(AbstractBuild<?, ?> build) {
return new Config(Resolver.buildVar(build, dockerUrl), Resolver.buildVar(build, dockerVersion), Resolver.buildVar(build, dockerCertPath));
}

public DockerClient getDockerClient(String dockerUrlRes, String dockerVersionRes, String dockerCertPathRes, AuthConfig authConfig) {
return createDockerClient(
dockerUrlRes == null ? Resolver.envVar(dockerUrl) : dockerUrlRes,
dockerVersionRes == null ? Resolver.envVar(dockerVersion) : dockerVersionRes,
dockerCertPathRes == null ? Resolver.envVar(dockerCertPath) : dockerCertPathRes,
authConfig);
}

public DescriptorExtensionList<DockerCommand, DockerCommandDescriptor> getCmdDescriptors() {
Expand All @@ -228,4 +241,16 @@ public DescriptorExtensionList<DockerCommand, DockerCommandDescriptor> getCmdDes

private static Logger LOGGER = Logger.getLogger(DockerBuilder.class.getName());

public static class Config implements Serializable {
private static final long serialVersionUID = -2906931690456614657L;

final public String dockerUrlRes, dockerVersionRes, dockerCertPathRes;

public Config(String dockerUrlRes, String dockerVersionRes, String dockerCertPathRes) {
this.dockerUrlRes = dockerUrlRes;
this.dockerVersionRes = dockerVersionRes;
this.dockerCertPathRes = dockerCertPathRes;
}
}

}
Expand Up @@ -83,7 +83,7 @@ public boolean perform(@SuppressWarnings("rawtypes") AbstractBuild build, Launch
for (String id : ids) {
StopCommand stopCommand = new StopCommand(id);
try {
stopCommand.execute(build, clog);
stopCommand.execute(launcher, build, clog);
} catch (NotFoundException e) {
clog.logWarn("unable to stop container id " + id + ", container not found!");
} catch (NotModifiedException e) {
Expand All @@ -92,7 +92,7 @@ public boolean perform(@SuppressWarnings("rawtypes") AbstractBuild build, Launch
}

RemoveCommand removeCommand = new RemoveCommand(containerIds, true, removeVolumes, force);
removeCommand.execute(build, clog);
removeCommand.execute(launcher, build, clog);

return true;
}
Expand Down
Expand Up @@ -2,6 +2,7 @@

import hudson.AbortException;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;

import org.jenkinsci.plugins.dockerbuildstep.log.ConsoleLogger;
Expand Down Expand Up @@ -52,7 +53,7 @@ public String getRunCmd() {
}

@Override
public void execute(@SuppressWarnings("rawtypes") AbstractBuild build, ConsoleLogger console)
public void execute(Launcher launcher, @SuppressWarnings("rawtypes") AbstractBuild build, ConsoleLogger console)
throws DockerException, AbortException {
// TODO check it when submitting the form
if (containerId == null || containerId.isEmpty()) {
Expand Down
Expand Up @@ -7,6 +7,7 @@
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.*;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.util.FormValidation;
import org.jenkinsci.plugins.dockerbuildstep.action.EnvInvisibleAction;
Expand Down Expand Up @@ -136,7 +137,7 @@ public boolean isAlwaysRestart() {
}

@Override
public void execute(@SuppressWarnings("rawtypes") AbstractBuild build, ConsoleLogger console)
public void execute(Launcher launcher, @SuppressWarnings("rawtypes") AbstractBuild build, ConsoleLogger console)
throws DockerException {
// TODO check it when submitting the form
if (image == null || image.isEmpty()) {
Expand Down
Expand Up @@ -7,12 +7,20 @@
import com.github.dockerjava.core.command.BuildImageResultCallback;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.Descriptor;
import hudson.remoting.Callable;
import jenkins.model.Jenkins;

import org.jenkinsci.plugins.dockerbuildstep.DockerBuilder;
import org.jenkinsci.plugins.dockerbuildstep.DockerBuilder.Config;
import org.jenkinsci.plugins.dockerbuildstep.log.ConsoleLogger;
import org.jenkinsci.plugins.dockerbuildstep.util.Resolver;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -30,7 +38,7 @@ public class CreateImageCommand extends DockerCommand {
private final boolean noCache;
private final boolean rm;
private final String buildArgs;

@DataBoundConstructor
public CreateImageCommand(String dockerFolder, String imageTag, String dockerFile, boolean noCache, boolean rm, String buildArgs) {
this.dockerFolder = dockerFolder;
Expand Down Expand Up @@ -66,7 +74,7 @@ public String getBuildArgs() {
}

@Override
public void execute(@SuppressWarnings("rawtypes") AbstractBuild build, final ConsoleLogger console)
public void execute(Launcher launcher, @SuppressWarnings("rawtypes") AbstractBuild build, final ConsoleLogger console)
throws DockerException {

if (dockerFolder == null) {
Expand Down Expand Up @@ -98,36 +106,73 @@ public void execute(@SuppressWarnings("rawtypes") AbstractBuild build, final Con

String expandedImageTag = Resolver.buildVar(build, imageTagRes);

FilePath folder = new FilePath(new File(expandedDockerFolder));
String dockerFileRes = dockerFile == null ? "Dockerfile" : Resolver.buildVar(build, dockerFile);

if (!exist(folder))
throw new IllegalArgumentException(
"configured dockerFolder '" + expandedDockerFolder + "' does not exist.");
String imageId = null;
try {
Config cfgData = getConfig(build);
imageId = launcher.getChannel().call(new RemoteCallable(expandedDockerFolder, expandedImageTag, dockerFileRes, cfgData, buildArgsMap, noCache, rm, Jenkins.getInstance().getDescriptor(DockerBuilder.class)));
} catch (Exception e) {
console.logError("Failed to create docker image: " + e.getMessage());
e.printStackTrace();
throw new IllegalArgumentException(e);
}

console.logInfo("Build image id:" + imageId);
}

String dockerFileRes = dockerFile == null ? "Dockerfile" : Resolver.buildVar(build, dockerFile);
if (!exist(folder.child(dockerFileRes))) {
throw new IllegalArgumentException(
String.format("Configured Docker file '%s' does not exist.", dockerFileRes));
public static class RemoteCallable implements Callable<String, Exception>, Serializable {

private static final long serialVersionUID = -6593420984897195978L;

String expandedDockerFolder;
String expandedImageTag;
String dockerFileRes;
Config cfgData;
Map<String, String> buildArgsMap;
boolean noCache;
boolean rm;

Descriptor<?> descriptor;

public RemoteCallable(String expandedDockerFolder, String expandedImageTag, String dockerFileRes, Config cfgData, Map<String, String> buildArgsMap, boolean noCache, boolean rm, Descriptor descriptor) {
this.expandedDockerFolder = expandedDockerFolder;
this.expandedImageTag = expandedImageTag;
this.dockerFileRes = dockerFileRes;
this.cfgData = cfgData;
this.buildArgsMap = buildArgsMap;
this.noCache = noCache;
this.rm = rm;
this.descriptor = descriptor;
}

DockerClient client = getClient(build, null);
public String call() throws Exception {
FilePath folder = new FilePath(new File(expandedDockerFolder));

if (!exist(folder))
throw new IllegalArgumentException(
"configured dockerFolder '" + expandedDockerFolder + "' does not exist.");

if (!exist(folder.child(dockerFileRes))) {
throw new IllegalArgumentException(
String.format("Configured Docker file '%s' does not exist.", dockerFileRes));
}

try {
File docker = new File(expandedDockerFolder, dockerFileRes);
console.logInfo("Creating docker image from " + docker.getAbsolutePath());

BuildImageResultCallback callback = new BuildImageResultCallback() {
@Override
public void onNext(BuildResponseItem item) {
console.logInfo(item.toString());
super.onNext(item);
}

@Override
public void onError(Throwable throwable) {
console.logError("Failed to creating docker image:" + throwable.getMessage());
super.onError(throwable);
}
};

DockerClient client = getClient(descriptor, cfgData.dockerUrlRes, cfgData.dockerVersionRes, cfgData.dockerCertPathRes, null);
BuildImageCmd buildImageCmd = client
.buildImageCmd(docker)
.withTag(expandedImageTag)
Expand All @@ -138,10 +183,18 @@ public void onError(Throwable throwable) {
buildImageCmd = buildImageCmd.withBuildArg(entry.getKey(), entry.getValue());
}
}

BuildImageResultCallback result = buildImageCmd.exec(callback);
console.logInfo("Build image id:" + result.awaitImageId());
} catch (Exception e) {
throw new RuntimeException(e);

return result.awaitImageId();
}

private boolean exist(FilePath filePath) throws DockerException {
try {
return filePath.exists();
} catch (Exception e) {
throw new DockerException("Could not check file", 0, e);
}
}
}

Expand All @@ -152,13 +205,4 @@ public String getDisplayName() {
return "Create/build image";
}
}

private boolean exist(FilePath filePath) throws DockerException {
try {
return filePath.exists();
} catch (Exception e) {
throw new DockerException("Could not check file", 0, e);
}
}

}

0 comments on commit 30ab1b4

Please sign in to comment.