From 4fa16ce4a787839e1142752989f83b1db98dabf1 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 28 Nov 2016 19:38:31 +0200 Subject: [PATCH] Allow nesting of templates for inheritance. --- README.md | 48 +++++++++++ .../pipeline/PodTemplateAction.java | 84 +++++++++++++++++++ .../kubernetes/pipeline/PodTemplateStep.java | 9 ++ .../pipeline/PodTemplateStepExecution.java | 11 ++- 4 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateAction.java diff --git a/README.md b/README.md index 7e5a5e6c9b..95f680510b 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,54 @@ Field `inheritFrom` may refer a single podTemplate or multiple separated by spac In any case if the referenced template is not found it will be ignored. +#### Nesting Pod templates + +Field `inheritFrom` provides an easy way to compose podTemplates that have been pre-configured. In many cases it would be useful to define and compose podTemplates directly in the pipeline using groovy. +This is made possible via nesting. You can nest multiple pod templates together in order to compose a single one. + +The example below composes two different podTemplates in order to create one with maven and docker capabilities. + + podTemplate(label: 'docker', containers: [containerTemplate(image: 'docker)]) { + podTemplate(label: 'maven', containers: [containerTemplate(image: 'maven)]) { + // do stuff + } + } + +This feature is extra useful, pipeline library developers as it allows you to wrap podTemplates into functions and let users, nest those functions according to their needs. + +For example one could create a function for a maven template, say `mavenTemplate.groovy`: + + #!/usr/bin/groovy + def call() { + podTemplate(label: label, + containers: [containerTemplate(name: 'maven', image: 'maven', command: 'cat', ttyEnabled: true)], + volumes: [secretVolume(secretName: 'maven-settings', mountPath: '/root/.m2'), + persistentVolumeClaim(claimName: 'maven-local-repo', mountPath: '/root/.m2nrepo')]) { + body() + } + +and also a function for a docker template, say `dockerTemplate.groovy`: + + #!/usr/bin/groovy + def call() { + podTemplate(label: label, + containers: [containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true)], + volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')]) { + body() + } + +Then consumers of the library could just express the need for a maven pod with docker capabilities by combining the two: + + dockerTemplate { + mavenTemplate { + ssh """ + mvn clean install + docker build -t myimage ./target/docker/ + """ + } + } + + ## Container Configuration When configuring a container in a pipeline podTemplate the following options are available: diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateAction.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateAction.java new file mode 100644 index 0000000000..8ca9438a3b --- /dev/null +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateAction.java @@ -0,0 +1,84 @@ +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import hudson.BulkChange; +import hudson.model.InvisibleAction; +import hudson.model.Run; + +public class PodTemplateAction extends InvisibleAction { + + private final Stack names = new Stack<>(); + private final Run run; + + + PodTemplateAction(Run run) { + this.run = run; + } + + public void push(String template) throws IOException { + synchronized (run) { + BulkChange bc = new BulkChange(run); + try { + PodTemplateAction action = run.getAction(PodTemplateAction.class); + if (action == null) { + action = new PodTemplateAction(run); + run.addAction(action); + } + action.names.push(template); + bc.commit(); + } finally { + bc.abort(); + } + } + } + + public String pop() throws IOException { + synchronized (run) { + BulkChange bc = new BulkChange(run); + try { + PodTemplateAction action = run.getAction(PodTemplateAction.class); + if (action == null) { + action = new PodTemplateAction(run); + run.addAction(action); + } + String template = action.names.pop(); + bc.commit(); + return template; + } finally { + bc.abort(); + return null; + } + } + } + + public List getParentTemplateList() { + synchronized (run) { + PodTemplateAction action = run.getAction(PodTemplateAction.class); + if (action == null) { + action = new PodTemplateAction(run); + run.addAction(action); + } + return new ArrayList<>(action.names); + } + } + + public String getParentTemplates() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String template : getParentTemplateList()) { + if (first) { + first = false; + } else { + sb.append(" "); + } + sb.append(template); + + } + return sb.toString(); + } + +} diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java index c163ae010c..4ade6644d8 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java @@ -8,11 +8,13 @@ import org.csanchez.jenkins.plugins.kubernetes.volumes.PodVolume; import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; +import org.jenkinsci.plugins.workflow.steps.StepContextParameter; import org.jenkinsci.plugins.workflow.steps.StepExecution; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import hudson.Extension; +import hudson.model.Run; public class PodTemplateStep extends AbstractStepImpl implements Serializable { @@ -33,6 +35,13 @@ public class PodTemplateStep extends AbstractStepImpl implements Serializable { private String nodeSelector; private String workingDir = ContainerTemplate.DEFAULT_WORKING_DIR; + @StepContextParameter + private transient Run run; + + public Run getRun() { + return run; + } + @DataBoundConstructor public PodTemplateStep(String label, String name) { this.label = label; diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java index 26991c771b..9ac634281e 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java @@ -1,5 +1,7 @@ package org.csanchez.jenkins.plugins.kubernetes.pipeline; +import com.google.common.base.Strings; + import hudson.slaves.Cloud; import jenkins.model.Jenkins; import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; @@ -26,12 +28,13 @@ public boolean start() throws Exception { Cloud cloud = Jenkins.getActiveInstance().getCloud(step.getCloud()); if (cloud instanceof KubernetesCloud) { KubernetesCloud kubernetesCloud = (KubernetesCloud) cloud; - String name = String.format(NAME_FORMAT, UUID.randomUUID().toString().replaceAll("-", "")); + PodTemplateAction action = new PodTemplateAction(step.getRun()); + PodTemplate newTemplate = new PodTemplate(); newTemplate.setName(name); - newTemplate.setInheritFrom(step.getInheritFrom()); + newTemplate.setInheritFrom(!Strings.isNullOrEmpty( action.getParentTemplates()) ? action.getParentTemplates() : step.getInheritFrom()); newTemplate.setLabel(step.getLabel()); newTemplate.setVolumes(step.getVolumes()); newTemplate.setContainers(step.getContainers()); @@ -42,6 +45,9 @@ public boolean start() throws Exception { getContext().newBodyInvoker() .withCallback(new PodTemplateCallback(newTemplate)) .start(); + + + action.push(step.getLabel()); return false; } else { getContext().onFailure(new IllegalStateException("Could not find cloud with name:[" + step.getCloud() + "].")); @@ -51,6 +57,7 @@ public boolean start() throws Exception { @Override public void stop(Throwable cause) throws Exception { + new PodTemplateAction(step.getRun()).pop(); } private class PodTemplateCallback extends BodyExecutionCallback.TailCall {