From 29b59bbc6bdd40acfa35845599d2c7b4149f46b4 Mon Sep 17 00:00:00 2001 From: Daniel Jeznach Date: Tue, 8 Nov 2016 08:32:11 +0100 Subject: [PATCH 1/5] Add text field for passing custom arguments to docker daemon (freetype text) --- pom.xml | 2 +- .../jenkins/plugins/docker_build_env/Docker.java | 12 ++++++++++-- .../plugins/docker_build_env/DockerBuildWrapper.java | 7 ++++++- .../docker_build_env/DockerBuildWrapper/config.jelly | 4 +++- .../DockerBuildWrapper/help-extraArgs.html | 7 +++++++ .../plugins/docker_build_env/FunctionalTests.java | 6 +++--- 6 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/help-extraArgs.html diff --git a/pom.xml b/pom.xml index d9bd377..ed21c09 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.cloudbees.jenkins.plugins docker-custom-build-environment Docker Custom Build Environment Plugin - 1.7.4-SNAPSHOT + 1.7.4-jeznach hpi https://wiki.jenkins.io/display/JENKINS/Docker+Custom+Build+Environment+Plugin diff --git a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java index dd842c3..0cf898f 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java @@ -184,7 +184,7 @@ public void kill(String container) throws IOException, InterruptedException { listener.getLogger().println("Failed to remove docker container "+container); } - public String runDetached(String image, String workdir, Map volumes, Map ports, Map links, EnvVars environment, Set sensitiveBuildVariables, String net, String memory, String cpu, String... command) throws IOException, InterruptedException { + public String runDetached(String image, String workdir, Map volumes, Map ports, Map links, EnvVars environment, Set sensitiveBuildVariables, String net, String memory, String cpu, String extraArgs, String... command) throws IOException, InterruptedException { String docker0 = getDocker0Ip(launcher, image); @@ -194,7 +194,7 @@ public String runDetached(String image, String workdir, Map volu args.add("--name", this.build.getProject().getName() + "-" + this.build.getNumber()); if (privileged) { - args.add( "--privileged"); + args.add("--privileged"); } args.add("--workdir", workdir); for (Map.Entry volume : volumes.entrySet()) { @@ -219,6 +219,14 @@ public String runDetached(String image, String workdir, Map volu args.add("--cpu-shares", cpu); } + if (StringUtils.isNotBlank(extraArgs)) { + String[] splittedArgs = extraArgs.split("\\s+"); + + for (String aSplited : splittedArgs) { + args.add(aSplited); + } + } + if (!"host".equals(net)){ //--add-host and --net=host are incompatible args.add("--add-host", "dockerhost:"+docker0); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper.java b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper.java index fec43d3..026a8ed 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper.java @@ -73,13 +73,14 @@ public class DockerBuildWrapper extends BuildWrapper { private String cpu; + private String extraArgs; private final boolean noCache; @DataBoundConstructor public DockerBuildWrapper(DockerImageSelector selector, String dockerInstallation, DockerServerEndpoint dockerHost, String dockerRegistryCredentials, boolean verbose, boolean privileged, List volumes, String group, String command, boolean forcePull, - String net, String memory, String cpu, boolean noCache) { + String net, String memory, String cpu, String extraArgs, boolean noCache) { this.selector = selector; this.dockerInstallation = dockerInstallation; this.dockerHost = dockerHost; @@ -93,6 +94,7 @@ public DockerBuildWrapper(DockerImageSelector selector, String dockerInstallatio this.net = net; this.memory = memory; this.cpu = cpu; + this.extraArgs = extraArgs; this.noCache = noCache; } @@ -141,6 +143,8 @@ public boolean isForcePull() { public String getMemory() { return memory;} public String getCpu() { return cpu;} + + public String getExtraArgs() { return extraArgs; } public boolean isNoCache() { return noCache; @@ -219,6 +223,7 @@ private String startBuildContainer(BuiltInContainer runInContainer, AbstractBuil return runInContainer.getDocker().runDetached(runInContainer.image, workdir, runInContainer.getVolumes(build), runInContainer.getPortsMap(), links, environment, build.getSensitiveBuildVariables(), net, memory, cpu, + extraArgs, command); // Command expected to hung until killed } catch (InterruptedException e) { diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/config.jelly index 42a7a78..f2f5ccf 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/config.jelly @@ -70,7 +70,9 @@ THE SOFTWARE. + + + - diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/help-extraArgs.html b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/help-extraArgs.html new file mode 100644 index 0000000..49ae222 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/DockerBuildWrapper/help-extraArgs.html @@ -0,0 +1,7 @@ +
+ Add extra arguments to docker run command. +

+ This is advanced option, get familiar with documentation + for more details. +

+
diff --git a/src/test/java/com/cloudbees/jenkins/plugins/docker_build_env/FunctionalTests.java b/src/test/java/com/cloudbees/jenkins/plugins/docker_build_env/FunctionalTests.java index 1bd42a7..f34cf42 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/docker_build_env/FunctionalTests.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/docker_build_env/FunctionalTests.java @@ -33,7 +33,7 @@ public void run_inside_pulled_container() throws Exception { project.getBuildWrappersList().add( new DockerBuildWrapper( new PullDockerImageSelector("ubuntu:14.04"), - "", new DockerServerEndpoint("", ""), "", true, false, Collections.emptyList(), null, "cat", false, "bridge", null, null, false) + "", new DockerServerEndpoint("", ""), "", true, false, Collections.emptyList(), null, "cat", false, "bridge", null, null, null, false) ); project.getBuildersList().add(new Shell("lsb_release -a")); @@ -52,7 +52,7 @@ public void run_inside_built_container() throws Exception { project.getBuildWrappersList().add( new DockerBuildWrapper( new DockerfileImageSelector(".", "Dockerfile"), - "", new DockerServerEndpoint("", ""), "", true, false, Collections.emptyList(), null, "cat", false, "bridge", null, null, true) + "", new DockerServerEndpoint("", ""), "", true, false, Collections.emptyList(), null, "cat", false, "bridge", null, null, null, true) ); project.getBuildersList().add(new Shell("lsb_release -a")); @@ -77,7 +77,7 @@ public void run_inside_built_python_pip_container() throws Exception { project.getBuildWrappersList().add( new DockerBuildWrapper( new DockerfileImageSelector(".", "Dockerfile"), - "", new DockerServerEndpoint("", ""), "", true, false, Collections.emptyList(), null, "cat", false, "bridge", null, null, true) + "", new DockerServerEndpoint("", ""), "", true, false, Collections.emptyList(), null, "cat", false, "bridge", null, null, null, true) ); project.getBuildersList().add(new Shell("python -V")); From 682cfa80c9795f466bb908e08990f5b1dee941ea Mon Sep 17 00:00:00 2001 From: Daniel Jeznach Date: Wed, 22 Jan 2020 13:09:02 +0100 Subject: [PATCH 2/5] Add support for BUILD_DOCKER_HOST and BUILD_DOCKER_IMAGE environment variables First one contains full host and domain name of docker host, the other stores docker image name used for build. --- pom.xml | 2 +- .../plugins/docker_build_env/BuiltInContainer.java | 8 ++++++++ .../jenkins/plugins/docker_build_env/Docker.java | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ed21c09..54fa0e1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.cloudbees.jenkins.plugins docker-custom-build-environment Docker Custom Build Environment Plugin - 1.7.4-jeznach + 1.7.4-jeznach2 hpi https://wiki.jenkins.io/display/JENKINS/Docker+Custom+Build+Environment+Plugin diff --git a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java index 7665c2a..7fe054e 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java @@ -82,6 +82,14 @@ public void buildEnvVars(AbstractBuild build, EnvVars env) { if (enable && container != null) { env.put("BUILD_CONTAINER_ID", container); } + + if (docker.getDockerHostName() != null) { + env.put("BUILD_DOCKER_HOST", docker.getDockerHostName()); + } + + if (image != null) { + env.put("BUILD_DOCKER_IMAGE", image); + } } public void bindMount(String path) { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java index 0cf898f..9a6b43a 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java @@ -42,6 +42,7 @@ public class Docker implements Closeable { private final TaskListener listener; private final String dockerExecutable; private final DockerServerEndpoint dockerHost; + private final String dockerHostName; private final DockerRegistryEndpoint registryEndpoint; private final boolean verbose; private final boolean privileged; @@ -50,6 +51,7 @@ public class Docker implements Closeable { public Docker(DockerServerEndpoint dockerHost, String dockerInstallation, String credentialsId, AbstractBuild build, Launcher launcher, TaskListener listener, boolean verbose, boolean privileged) throws IOException, InterruptedException { this.dockerHost = dockerHost; + this.dockerHostName = Computer.currentComputer().getHostName(); this.dockerExecutable = DockerTool.getExecutable(dockerInstallation, Computer.currentComputer().getNode(), listener, build.getEnvironment(listener)); this.registryEndpoint = new DockerRegistryEndpoint(null, credentialsId); this.launcher = launcher; @@ -370,6 +372,10 @@ public void executeIn(String container, String userId, Launcher.ProcStarter star starter.envs(getEnvVars()); } + public String getDockerHostName() { + return dockerHostName; + } + private ArgumentListBuilder dockerCommand() { ArgumentListBuilder args = new ArgumentListBuilder(); for (String s : dockerCommandArgs()) { From 816dd7eb223b4d7d79a1e4ba34619640ec6b40d2 Mon Sep 17 00:00:00 2001 From: Daniel Jeznach Date: Mon, 26 Oct 2020 09:20:06 +0100 Subject: [PATCH 3/5] Replace illegal charcters in container label --- .../jenkins/plugins/docker_build_env/Docker.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java index 9a6b43a..410e190 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/Docker.java @@ -125,7 +125,9 @@ public String buildImage(FilePath workspace, String dockerfile, boolean forcePul args.add("--file", dockerfile) .add(workspace.getRemote()); - args.add("--label", "jenkins-project=" + this.build.getProject().getName()); + String projectName = this.build.getProject().getName(); + projectName = projectName.replaceAll("[^a-zA-Z0-9_.-]", "__"); + args.add("--label", "jenkins-project=" + projectName); args.add("--label", "jenkins-build-number=" + this.build.getNumber()); OutputStream logOutputStream = listener.getLogger(); @@ -189,11 +191,12 @@ public void kill(String container) throws IOException, InterruptedException { public String runDetached(String image, String workdir, Map volumes, Map ports, Map links, EnvVars environment, Set sensitiveBuildVariables, String net, String memory, String cpu, String extraArgs, String... command) throws IOException, InterruptedException { String docker0 = getDocker0Ip(launcher, image); - + String projectName = this.build.getProject().getName(); + projectName = projectName.replaceAll("[^a-zA-Z0-9_.-]", "__"); ArgumentListBuilder args = dockerCommand() .add("run", "--tty", "--detach"); - args.add("--name", this.build.getProject().getName() + "-" + this.build.getNumber()); + args.add("--name", projectName + "-" + this.build.getNumber()); if (privileged) { args.add("--privileged"); From 584d0833b7f36122f283b562acb8770dc9361cd0 Mon Sep 17 00:00:00 2001 From: Daniel Jeznach Date: Wed, 4 Mar 2020 16:04:08 +0100 Subject: [PATCH 4/5] Add docker image name to build badge --- pom.xml | 2 +- .../plugins/docker_build_env/BuiltInContainer/badge.jelly | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 54fa0e1..7effe28 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.cloudbees.jenkins.plugins docker-custom-build-environment Docker Custom Build Environment Plugin - 1.7.4-jeznach2 + 1.7.5-jeznach hpi https://wiki.jenkins.io/display/JENKINS/Docker+Custom+Build+Environment+Plugin diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/badge.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/badge.jelly index df0c303..9bd570b 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/badge.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/badge.jelly @@ -24,6 +24,6 @@ THE SOFTWARE. --> - From 07a7ead0425b7fde86446a01ccb60a1421742a35 Mon Sep 17 00:00:00 2001 From: Daniel Jeznach Date: Thu, 5 Mar 2020 11:20:02 +0100 Subject: [PATCH 5/5] Add docker image name to build summary screen --- .../docker_build_env/BuiltInContainer.java | 12 +++++++++++- .../BuiltInContainer/summary.jelly | 7 +++++++ src/main/webapp/images/docker-logo.png | Bin 0 -> 8053 bytes 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/summary.jelly create mode 100644 src/main/webapp/images/docker-logo.png diff --git a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java index 7fe054e..0ebdd08 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer.java @@ -52,6 +52,16 @@ public String getImage() { return image; } + public String getImageName() { + if (image == null) { + return ""; + } + if (image.indexOf('/') < 0) { + return image; + } + return image.split("/")[1]; + } + public String getDisplayName() { return "built inside docker container"; } @@ -83,7 +93,7 @@ public void buildEnvVars(AbstractBuild build, EnvVars env) { env.put("BUILD_CONTAINER_ID", container); } - if (docker.getDockerHostName() != null) { + if (docker != null && docker.getDockerHostName() != null) { env.put("BUILD_DOCKER_HOST", docker.getDockerHostName()); } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/summary.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/summary.jelly new file mode 100644 index 0000000..5e81818 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/docker_build_env/BuiltInContainer/summary.jelly @@ -0,0 +1,7 @@ + + + + + Built in docker container using image ${it.image} + + diff --git a/src/main/webapp/images/docker-logo.png b/src/main/webapp/images/docker-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..122a0701b9396addf429c941e61c5002e238d2cd GIT binary patch literal 8053 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 UvEHhOKnIYbtlGN@Y2)Dk0nO@?$p8QV literal 0 HcmV?d00001