diff --git a/README.md b/README.md
index 7dce4e67..d3177e01 100644
--- a/README.md
+++ b/README.md
@@ -122,9 +122,58 @@ These were retrieved from github [here][2].
* `PROMOTED_ID` - ID of the build being promoted
* ex: 2012-04-12_17-13-03
* `PROMOTED_USER_NAME` - the user who triggered the promotion
-* `PROMOTED_JOB_FULL_NAME` - the full name of the promoted
-job
-
+* `PROMOTED_JOB_FULL_NAME` - the full name of the promoted job
+
+## Job DSL support
+
+```groovy
+freeStyleJob(String jobname) {
+ properties{
+ promotions {
+ promotion {
+ name(String promotionName)
+ icon(String iconName)
+ conditions {
+ selfPromotion(boolean evenIfUnstable = true)
+ parameterizedSelfPromotion(boolean evenIfUnstable = true, String parameterName, String parameterValue)
+ releaseBuild()
+ downstream(boolean evenIfUnstable = true, String jobs)
+ upstream(String promotionNames)
+ manual(String user){
+ parameters{
+ textParam(String parameterName, String defaultValue, String description)
+ }
+ }
+ actions {
+ shell(String command)
+ }
+ }
+ }
+ }
+}
+```
+
+See [StepContext](https://jenkinsci.github.io/job-dsl-plugin/#path/job-steps) in the API Viewer for full documentation about the possible actions.
+
+### Example
+
+```groovy
+freeStyleJob('test-job') {
+ properties{
+ promotions {
+ promotion {
+ name('Development')
+ conditions {
+ manual('testuser')
+ }
+ actions {
+ shell('echo hello;')
+ }
+ }
+ }
+ }
+}
+```
## Contributing
diff --git a/pom.xml b/pom.xml
index 3f575f4f..13d75d49 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
org.jenkins-ci.plugins
plugin
- 1.554.2
+ 1.565
promoted-builds
@@ -13,8 +13,8 @@
Jenkins promoted builds plugin
http://wiki.jenkins-ci.org/display/JENKINS/Promoted+Builds+Plugin
-
-
+
+
3.0.1
true
@@ -115,6 +115,19 @@
1.5.3
true
+
+ org.jenkins-ci.plugins
+ job-dsl
+ 1.42
+ jar
+ true
+
+
+ org.jvnet.hudson
+ xstream
+ 1.4.7-jenkins-1
+ true
+
@@ -127,7 +140,7 @@
org.codehaus.mojo
findbugs-maven-plugin
${findbugs-maven-plugin.version}
-
+
${findbugs.failOnError}
true
@@ -151,4 +164,4 @@
-
+
diff --git a/src/main/java/hudson/plugins/promoted_builds/JobPropertyImpl.java b/src/main/java/hudson/plugins/promoted_builds/JobPropertyImpl.java
index 82d2a76c..a0d9443d 100644
--- a/src/main/java/hudson/plugins/promoted_builds/JobPropertyImpl.java
+++ b/src/main/java/hudson/plugins/promoted_builds/JobPropertyImpl.java
@@ -1,5 +1,23 @@
package hudson.plugins.promoted_builds;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.lang.StringUtils;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.Ancestor;
+import org.kohsuke.stapler.StaplerRequest;
+
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractBuild;
@@ -19,26 +37,10 @@
import hudson.model.listeners.ItemListener;
import hudson.remoting.Callable;
import hudson.util.IOUtils;
+import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
-import org.apache.commons.lang.StringUtils;
-import org.kohsuke.stapler.Ancestor;
-import org.kohsuke.stapler.StaplerRequest;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import jenkins.model.Jenkins;
-
/**
* Promotion processes defined for a project.
*
@@ -71,11 +73,23 @@ public JobPropertyImpl(AbstractProject,?> owner) throws Descriptor.FormExcepti
this.owner = owner;
init();
}
+ /**
+ * Programmatic construction.
+ */
public JobPropertyImpl(JobPropertyImpl other, AbstractProject,?> owner) throws Descriptor.FormException, IOException {
this.owner = owner;
this.activeProcessNames.addAll(other.activeProcessNames);
loadAllProcesses(other.getRootDir());
}
+
+ /**
+ * Programmatic construction.
+ */
+ @Restricted(NoExternalUse.class)
+ public JobPropertyImpl(Set activeProcessNames) {
+ this.activeProcessNames.addAll(activeProcessNames);
+ }
+
private JobPropertyImpl(StaplerRequest req, JSONObject json) throws Descriptor.FormException, IOException {
// a hack to get the owning AbstractProject.
// this is needed here so that we can load items
@@ -109,6 +123,7 @@ private JobPropertyImpl(StaplerRequest req, JSONObject json) throws Descriptor.F
}
init();
}
+
private void loadAllProcesses(File rootDir) throws IOException {
File[] subdirs = rootDir.listFiles(new FileFilter() {
public boolean accept(File child) {
@@ -395,4 +410,4 @@ public JobPropertyImpl newInstance(StaplerRequest req, JSONObject json) throws D
}
private static final Logger LOGGER = Logger.getLogger(JobPropertyImpl.class.getName());
-}
+}
\ No newline at end of file
diff --git a/src/main/java/hudson/plugins/promoted_builds/conditions/ManualCondition.java b/src/main/java/hudson/plugins/promoted_builds/conditions/ManualCondition.java
index af13912f..ca9b643a 100644
--- a/src/main/java/hudson/plugins/promoted_builds/conditions/ManualCondition.java
+++ b/src/main/java/hudson/plugins/promoted_builds/conditions/ManualCondition.java
@@ -55,10 +55,18 @@ public class ManualCondition extends PromotionCondition {
public ManualCondition() {
}
+ /*
+ * Restrict the Condition to specific user(s)
+ * @since 2.24
+ */
public String getUsers() {
return users;
}
+ public void setUsers(String users) {
+ this.users = users;
+ }
+
public List getParameterDefinitions() {
return parameterDefinitions;
}
@@ -151,10 +159,10 @@ public boolean isInGroupList() {
return false;
}
public Future approve(AbstractBuild,?> build, PromotionProcess promotionProcess, List paramValues) throws IOException{
- if (canApprove(promotionProcess, build)) {
+ if (canApprove(promotionProcess, build)) {
// add approval to build
- ManualApproval approval=new ManualApproval(promotionProcess.getName(), paramValues);
- build.addAction(approval);
+ ManualApproval approval=new ManualApproval(promotionProcess.getName(), paramValues);
+ build.addAction(approval);
build.save();
// check for promotion
@@ -166,9 +174,9 @@ public List createDefaultValues(){
List paramValues = new ArrayList();
if (parameterDefinitions != null && !parameterDefinitions.isEmpty()) {
- for (ParameterDefinition d:parameterDefinitions){
- paramValues.add(d.getDefaultParameterValue());
- }
+ for (ParameterDefinition d:parameterDefinitions){
+ paramValues.add(d.getDefaultParameterValue());
+ }
}
return paramValues;
}
@@ -184,7 +192,7 @@ public void doApprove(StaplerRequest req, StaplerResponse rsp,
@AncestorInPath PromotionProcess promotionProcess,
@AncestorInPath AbstractBuild,?> build) throws IOException, ServletException {
- JSONObject formData = req.getSubmittedForm();
+ JSONObject formData = req.getSubmittedForm();
if (canApprove(promotionProcess, build)) {
List paramValues = new ArrayList();
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ConditionsContext.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ConditionsContext.java
new file mode 100755
index 00000000..11ade444
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ConditionsContext.java
@@ -0,0 +1,171 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import groovy.lang.Closure;
+import groovy.util.Node;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javaposse.jobdsl.dsl.Context;
+import javaposse.jobdsl.dsl.FileJobManagement;
+import javaposse.jobdsl.dsl.helpers.BuildParametersContext;
+
+import org.apache.commons.io.FileUtils;
+
+public class ConditionsContext implements Context {
+
+ // Self Promotion Condition
+ private boolean isSelfPromotion = false;
+
+ private boolean evenIfUnstable = false;
+
+ // Parametzerized Self Promotion Condition
+ private boolean isParameterizedSelfPromotion = false;
+
+ private boolean evenIfUnstableParameterized = false;
+
+ private String parameterName = null;
+
+ private String parameterValue = null;
+
+ // Manual Promotion Condition
+ private boolean isManual = false;
+
+ private String users = null;
+
+ final Map params = new HashMap();
+
+ // Release Build Condition
+ private boolean isReleaseBuild = false;
+
+ // Downstream Build Condition
+ private boolean isDownstreamPass = false;
+
+ private boolean evenIfUnstableDownstream = false;
+
+ private String jobs = null;
+
+ // Upstream Build Condition
+ private boolean isUpstreamPromotion = false;
+
+ private String promotionNames = null;
+
+ public void selfPromotion(Boolean evenIfUnstable) {
+ isSelfPromotion = true;
+ if (evenIfUnstable) {
+ this.evenIfUnstable = evenIfUnstable;
+ }
+ }
+
+ public void parameterizedSelfPromotion(Boolean evenIfUnstable, String parameterName, String parameterValue) {
+ isParameterizedSelfPromotion = true;
+ if (evenIfUnstable) {
+ this.isParameterizedSelfPromotion = evenIfUnstable;
+ }
+ this.parameterName = parameterName;
+ this.parameterValue = parameterValue;
+ }
+
+ public void manual(String users) {
+ isManual = true;
+ this.users = users;
+ }
+
+ public void manual(String users, Closure> parametersClosure) {
+ isManual = true;
+ this.users = users;
+ parameters(parametersClosure);
+ }
+
+ public void releaseBuild() {
+ isReleaseBuild = true;
+ }
+
+ public void parameters(Closure> parametersClosure) {
+ // delegate to main BuildParametersContext
+ BuildParametersContext parametersContext = new BuildParametersContext(
+ new FileJobManagement(FileUtils.getTempDirectory()));
+ executeInContext(parametersClosure, parametersContext);
+ params.putAll(parametersContext.getBuildParameterNodes());
+ }
+
+ public void downstream(Boolean evenIfUnstable, String jobs) {
+ isDownstreamPass = true;
+ if (evenIfUnstable) {
+ this.evenIfUnstableDownstream = evenIfUnstable;
+ }
+ this.jobs = jobs;
+ }
+
+ public void upstream(String promotionNames) {
+ isUpstreamPromotion = true;
+ this.promotionNames = promotionNames;
+ }
+
+ private static void executeInContext(Closure> configClosure, Object context) {
+ configClosure.setDelegate(context);
+ configClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
+ configClosure.call();
+ }
+
+ public Map getParams() {
+ return params;
+ }
+
+ public boolean isSelfPromotion() {
+ return isSelfPromotion;
+ }
+
+ public boolean isEvenIfUnstable() {
+ return evenIfUnstable;
+ }
+
+ public boolean isParameterizedSelfPromotion() {
+ return isParameterizedSelfPromotion;
+ }
+
+ public boolean isEvenIfUnstableParameterized() {
+ return evenIfUnstableParameterized;
+ }
+
+ public String getParameterName() {
+ return parameterName;
+ }
+
+ public String getParameterValue() {
+ return parameterValue;
+ }
+
+ public boolean isManual() {
+ return isManual;
+ }
+
+ public String getUsers() {
+ return users;
+ }
+
+ public boolean isReleaseBuild() {
+ return isReleaseBuild;
+ }
+
+ public boolean isDownstreamPass() {
+ return isDownstreamPass;
+ }
+
+ public boolean isEvenIfUnstableDownstream() {
+ return evenIfUnstableDownstream;
+ }
+
+ public String getJobs() {
+ return jobs;
+ }
+
+ public boolean isUpstreamPromotion() {
+ return isUpstreamPromotion;
+ }
+
+ public String getPromotionNames() {
+ return promotionNames;
+ }
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslManualCondition.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslManualCondition.java
new file mode 100755
index 00000000..02ea43b7
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslManualCondition.java
@@ -0,0 +1,28 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import groovy.util.Node;
+import hudson.plugins.promoted_builds.conditions.ManualCondition;
+
+import java.util.Collection;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ * Special holder for the ManualCondition generated by the Job DSL Plugin
+ *
+ * @author Dennis Schulte
+ */
+@XStreamAlias("hudson.plugins.promoted_builds.conditions.ManualCondition")
+public class JobDslManualCondition extends ManualCondition {
+
+ private Collection parameterDefinitionNodes;
+
+ public Collection getParameterDefinitionNodes() {
+ return parameterDefinitionNodes;
+ }
+
+ public void setParameterDefinitionNodes(Collection parameterDefinitionNodes) {
+ this.parameterDefinitionNodes = parameterDefinitionNodes;
+ }
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcess.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcess.java
new file mode 100755
index 00000000..be54af8f
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcess.java
@@ -0,0 +1,79 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+import groovy.util.Node;
+import hudson.plugins.promoted_builds.PromotionCondition;
+
+/**
+ * Special holder for the PromotionProcess generated by the Job DSL Plugin
+ *
+ * @author Dennis Schulte
+ */
+@XStreamAlias("hudson.plugins.promoted_builds.PromotionProcess")
+public final class JobDslPromotionProcess {
+
+ private String name;
+ /**
+ * The icon that represents this promotion process. This is the name of
+ * the GIF icon that can be found in ${rootURL}/plugin/promoted-builds/icons/16x16/
+ * and ${rootURL}/plugin/promoted-builds/icons/32x32/, e.g. "star-gold"
.
+ */
+ private String icon;
+
+ /**
+ * The label that promotion process can be run on.
+ */
+ private String assignedLabel;
+
+ /**
+ * {@link PromotionCondition}s. All have to be met for a build to be promoted.
+ */
+ private List conditions = new ArrayList();
+
+ private List buildSteps = new ArrayList();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public String getAssignedLabel() {
+ return assignedLabel;
+ }
+
+ public void setAssignedLabel(String assignedLabel) {
+ this.assignedLabel = assignedLabel;
+ }
+
+ public List getConditions() {
+ return conditions;
+ }
+
+ public void setConditions(List conditions) {
+ this.conditions = conditions;
+ }
+
+ public List getBuildSteps() {
+ return buildSteps;
+ }
+
+ public void setBuildSteps(List buildSteps) {
+ this.buildSteps = buildSteps;
+ }
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcessConverter.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcessConverter.java
new file mode 100755
index 00000000..8a1bb758
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcessConverter.java
@@ -0,0 +1,126 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import java.util.Collection;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import groovy.util.Node;
+import hudson.PluginManager;
+import hudson.PluginWrapper;
+import jenkins.model.Jenkins;
+
+/**
+ * XStream Converter for the PromotionProcess for the Job DSL Plugin
+ *
+ * @author Dennis Schulte
+ */
+public class JobDslPromotionProcessConverter extends ReflectionConverter {
+
+ public JobDslPromotionProcessConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ super(mapper, reflectionProvider);
+ }
+
+ private String classOwnership;
+
+ private PluginManager pm;
+
+ @Override
+ public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+ return type.equals(JobDslPromotionProcess.class);
+ }
+
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ JobDslPromotionProcess promotionProcess = (JobDslPromotionProcess) source;
+ // attributes
+ String plugin = obtainClassOwnership();
+ if (plugin != null) {
+ writer.addAttribute("plugin", plugin);
+ }
+ // nodes
+ if (promotionProcess != null) {
+ if (promotionProcess.getName() != null) {
+ writer.startNode("name");
+ writer.setValue(promotionProcess.getName());
+ writer.endNode();
+ }
+ if (promotionProcess.getIcon() != null) {
+ writer.startNode("icon");
+ writer.setValue(promotionProcess.getIcon());
+ writer.endNode();
+ }
+ if (promotionProcess.getAssignedLabel() != null) {
+ String assignedLabel = promotionProcess.getAssignedLabel();
+ if (assignedLabel != null) {
+ writer.startNode("assignedLabel");
+ writer.setValue(assignedLabel);
+ writer.endNode();
+ }
+ }
+ if (promotionProcess.getConditions() != null) {
+ writer.startNode("conditions");
+ context.convertAnother(promotionProcess.getConditions());
+ writer.endNode();
+ }
+ if (promotionProcess.getBuildSteps() != null) {
+ writer.startNode("buildSteps");
+ for (Node node : promotionProcess.getBuildSteps()) {
+ writer.startNode(node.name().toString());
+ if (node.value() instanceof Collection) {
+ for (Object subNode : (Collection) node.value()) {
+ convertNode((Node) subNode, writer);
+ }
+ } else {
+ writer.setValue(node.value().toString());
+ }
+ writer.endNode();
+ }
+ writer.endNode();
+ }
+ }
+ }
+
+ private void convertNode(Node node, HierarchicalStreamWriter writer) {
+ writer.startNode(node.name().toString());
+ if (node.value() instanceof Collection) {
+ for (Object subNode : (Collection) node.value()) {
+ convertNode((Node) subNode, writer);
+ }
+ } else {
+ writer.setValue(node.value().toString());
+ }
+ writer.endNode();
+ }
+
+ private String obtainClassOwnership() {
+ if (this.classOwnership != null) {
+ return this.classOwnership;
+ }
+ if (pm == null) {
+ Jenkins j = Jenkins.getInstance();
+ if (j != null) {
+ pm = j.getPluginManager();
+ }
+ }
+ if (pm == null) {
+ return null;
+ }
+ // TODO: possibly recursively scan super class to discover dependencies
+ PluginWrapper p = pm.whichPlugin(hudson.plugins.promoted_builds.PromotionProcess.class);
+ this.classOwnership = p != null ? p.getShortName() + '@' + trimVersion(p.getVersion()) : null;
+ return this.classOwnership;
+ }
+
+ static String trimVersion(String version) {
+ // TODO seems like there should be some trick with VersionNumber to do
+ // this
+ return version.replaceFirst(" .+$", "");
+ }
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ManualConditionConverter.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ManualConditionConverter.java
new file mode 100755
index 00000000..d7171411
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ManualConditionConverter.java
@@ -0,0 +1,71 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import groovy.util.Node;
+
+import java.util.Collection;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * XStream Converter for the ManualCondition for the Job DSL Plugin
+ *
+ * @author Dennis Schulte
+ */
+public class ManualConditionConverter extends ReflectionConverter {
+
+ public ManualConditionConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ super(mapper, reflectionProvider);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+ return type.equals(JobDslManualCondition.class);
+ }
+
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ JobDslManualCondition mc = (JobDslManualCondition) source;
+ if (mc.getUsers() != null) {
+ writer.startNode("users");
+ writer.setValue(mc.getUsers());
+ writer.endNode();
+ }
+ writer.startNode("parameterDefinitions");
+ Collection parameterDefinitionNodes = mc.getParameterDefinitionNodes();
+ if(parameterDefinitionNodes != null && !parameterDefinitionNodes.isEmpty()){
+ for (Node node : mc.getParameterDefinitionNodes()) {
+ writer.startNode(node.name().toString());
+ if (node.value() instanceof Collection) {
+ for (Object subNode : (Collection) node.value()) {
+ convertNode((Node) subNode, writer);
+ }
+ } else {
+ writer.setValue(node.value().toString());
+ }
+ writer.endNode();
+ }
+ }
+ writer.endNode();
+ }
+
+ private void convertNode(Node node, HierarchicalStreamWriter writer) {
+ writer.startNode(node.name().toString());
+ if (node.value() instanceof Collection) {
+ for (Object subNode : (Collection) node.value()) {
+ convertNode((Node) subNode, writer);
+ }
+ } else {
+ writer.setValue(node.value().toString());
+ }
+ writer.endNode();
+ }
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionContext.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionContext.java
new file mode 100755
index 00000000..b1327819
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionContext.java
@@ -0,0 +1,108 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import groovy.lang.Closure;
+import groovy.util.Node;
+import hudson.plugins.promoted_builds.PromotionCondition;
+import hudson.plugins.promoted_builds.conditions.DownstreamPassCondition;
+import hudson.plugins.promoted_builds.conditions.ParameterizedSelfPromotionCondition;
+import hudson.plugins.promoted_builds.conditions.SelfPromotionCondition;
+import hudson.plugins.promoted_builds.conditions.UpstreamPromotionCondition;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javaposse.jobdsl.dsl.Context;
+import javaposse.jobdsl.dsl.FileJobManagement;
+import javaposse.jobdsl.dsl.helpers.step.StepContext;
+
+import org.apache.commons.io.FileUtils;
+
+class PromotionContext implements Context {
+
+ private List conditions = new ArrayList();
+
+ private List actions = new ArrayList();
+
+ private String icon;
+
+ private String restrict;
+
+ private String name;
+
+ public void name(String name) {
+ this.name = name;
+ }
+
+ public void icon(String icon) {
+ this.icon = icon;
+ }
+
+ public void restrict(String restrict) {
+ this.restrict = restrict;
+ }
+
+ public void conditions(Closure> conditionClosure) {
+ // delegate to ConditionsContext
+ ConditionsContext conditionContext = new ConditionsContext();
+ executeInContext(conditionClosure, conditionContext);
+ if (conditionContext.isSelfPromotion()) {
+ conditions.add(new SelfPromotionCondition(conditionContext.isEvenIfUnstable()));
+ }
+ if (conditionContext.isParameterizedSelfPromotion()) {
+ conditions.add(new ParameterizedSelfPromotionCondition(conditionContext.isEvenIfUnstableParameterized(), conditionContext
+ .getParameterName(), conditionContext.getParameterValue()));
+ }
+ if (conditionContext.isManual()) {
+ JobDslManualCondition condition = new JobDslManualCondition();
+ condition.setUsers(conditionContext.getUsers());
+ if (conditionContext.getParams() != null) {
+ condition.setParameterDefinitionNodes(conditionContext.getParams().values());
+ }
+ conditions.add(condition);
+ }
+ if (conditionContext.isReleaseBuild()) {
+ conditions.add(new ReleasePromotionCondition());
+ }
+ if (conditionContext.isDownstreamPass()) {
+ conditions.add(new DownstreamPassCondition(conditionContext.getJobs(), conditionContext.isEvenIfUnstableDownstream()));
+ }
+ if (conditionContext.isUpstreamPromotion()) {
+ conditions.add(new UpstreamPromotionCondition(conditionContext.getPromotionNames()));
+ }
+ }
+
+ public void actions(Closure> actionsClosure) {
+ // delegate to StepContext
+ StepContext stepContext = new StepContext(new FileJobManagement(FileUtils.getTempDirectory()), null);
+ executeInContext(actionsClosure, stepContext);
+ actions.addAll(stepContext.getStepNodes());
+ }
+
+ private static void executeInContext(Closure> configClosure, Object context) {
+ configClosure.setDelegate(context);
+ configClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
+ configClosure.call();
+ }
+
+ public List getConditions() {
+ return conditions;
+ }
+
+ public List getActions() {
+ return actions;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public String getRestrict() {
+ return restrict;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsContext.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsContext.java
new file mode 100755
index 00000000..24a54ed4
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsContext.java
@@ -0,0 +1,59 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Preconditions;
+
+import groovy.lang.Closure;
+import javaposse.jobdsl.dsl.Context;
+
+public class PromotionsContext implements Context {
+
+ Set names = new HashSet();
+
+ Map promotionContexts = new HashMap();
+
+ /**
+ * PromotionNodes:
+ * 1. dev
+ * 2. test
+ *
+ * AND
+ *
+ * Sub PromotionNode for every promotion
+ * 1.
+ * dev
+ * .
+ * .
+ * .
+ *
+ * 2.
+ * test
+ * .
+ * .
+ * .
+ *
+ *
+ * @param promotionClosure
+ * @return
+ */
+ public void promotion(Closure> promotionClosure) {
+ PromotionContext promotionContext = new PromotionContext();
+ executeInContext(promotionClosure, promotionContext);
+ Preconditions.checkNotNull(promotionContext.getName(), "promotion name cannot be null");
+ Preconditions.checkArgument(promotionContext.getName().length() > 0);
+ names.add(promotionContext.getName());
+ promotionContexts.put(promotionContext.getName(),promotionContext);
+
+ }
+
+ private static void executeInContext(Closure> configClosure, Object context) {
+ configClosure.setDelegate(context);
+ configClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
+ configClosure.call();
+ }
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsExtensionPoint.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsExtensionPoint.java
new file mode 100755
index 00000000..fe9a7d56
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsExtensionPoint.java
@@ -0,0 +1,138 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.thoughtworks.xstream.XStream;
+
+import hudson.Extension;
+import hudson.model.AbstractItem;
+import hudson.model.Item;
+import hudson.model.Items;
+import hudson.model.Descriptor.FormException;
+import hudson.plugins.promoted_builds.JobPropertyImpl;
+import hudson.util.IOUtils;
+import hudson.util.XStream2;
+import javaposse.jobdsl.dsl.helpers.properties.PropertiesContext;
+import javaposse.jobdsl.plugin.ContextExtensionPoint;
+import javaposse.jobdsl.plugin.DslEnvironment;
+import javaposse.jobdsl.plugin.DslExtensionMethod;
+
+/**
+ * The Job DSL Extension Point for the Promotions. See also {@linktourl https://github.com/jenkinsci/job-dsl-plugin/wiki/Extending-the-DSL}
+ *
+ * @author Dennis Schulte
+ */
+@Extension
+public class PromotionsExtensionPoint extends ContextExtensionPoint {
+
+ private static final Logger LOGGER = Logger.getLogger(PromotionsExtensionPoint.class.getName());
+
+ private static final XStream XSTREAM = new XStream2();
+
+ @DslExtensionMethod(context = PropertiesContext.class)
+ public Object promotions(Runnable closure, DslEnvironment dslEnvironment) throws FormException, IOException {
+ PromotionsContext context = new PromotionsContext();
+ executeInContext(closure, context);
+ dslEnvironment.put("processNames", context.names);
+ JobPropertyImpl jobProperty = new JobPropertyImpl(context.names);
+ Map promotionProcesses = new HashMap();
+ for (String processName : context.names) {
+ PromotionContext promotionContext = context.promotionContexts.get(processName);
+ JobDslPromotionProcess jobDslPromotionProcess = new JobDslPromotionProcess();
+ jobDslPromotionProcess.setName(processName);
+ jobDslPromotionProcess.setIcon(promotionContext.getIcon());
+ jobDslPromotionProcess.setAssignedLabel(promotionContext.getRestrict());
+ jobDslPromotionProcess.setBuildSteps(promotionContext.getActions());
+ jobDslPromotionProcess.setConditions(promotionContext.getConditions());
+ promotionProcesses.put(processName,jobDslPromotionProcess);
+ }
+ dslEnvironment.put("promotionProcesses", promotionProcesses);
+ return jobProperty;
+ }
+
+ @Override
+ public void notifyItemCreated(Item item, DslEnvironment dslEnvironment) {
+ notifyItemCreated(item, dslEnvironment, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void notifyItemCreated(Item item, DslEnvironment dslEnvironment, boolean update) {
+ LOGGER.log(Level.INFO, String.format("Creating promotions for %s", item.getName()));
+ Map promotionProcesses = (Map) dslEnvironment.get("promotionProcesses");
+ Set names = (Set) dslEnvironment.get("processNames");
+ if (names != null && names.size() > 0) {
+ for (String name : names) {
+ JobDslPromotionProcess promotionProcess = promotionProcesses.get(name);
+ XSTREAM.registerConverter(new JobDslPromotionProcessConverter(XSTREAM.getMapper(), XSTREAM.getReflectionProvider()));
+ XSTREAM.registerConverter(new ManualConditionConverter(XSTREAM.getMapper(), XSTREAM.getReflectionProvider()));
+ String xml = XSTREAM.toXML(promotionProcess);
+ File dir = new File(item.getRootDir(), "promotions/" + name);
+ File configXml = Items.getConfigFile(dir).getFile();
+ boolean created = configXml.getParentFile().mkdirs();
+ String createUpdate;
+ if(created){
+ createUpdate = "Added";
+ }else{
+ createUpdate = "Updated";
+ }
+ try {
+ InputStream in = new ByteArrayInputStream(xml.getBytes("UTF-8"));
+ IOUtils.copy(in, configXml);
+ LOGGER.log(Level.INFO, String.format(createUpdate + " promotion with name %s for %s", name, item.getName()));
+ update = true;
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Error handling extension code", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error handling extension code", e);
+ }
+ }
+ }
+
+ // Only update if a promotion was actually added, updated, or removed.
+ if (update) {
+ try {
+ LOGGER.log(Level.INFO, String.format("Reloading config for %s", item.getName()));
+ ((AbstractItem) item).doReload();
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable to cast item to AbstractItem and reload config", e);
+ }
+ }
+ }
+
+ @Override
+ public void notifyItemUpdated(Item item, DslEnvironment dslEnvironment) {
+ LOGGER.log(Level.INFO, String.format("Updating promotions for %s", item.getName()));
+ @SuppressWarnings("unchecked")
+ Set newPromotions = (Set) dslEnvironment.get("processNames");
+ File dir = new File(item.getRootDir(), "promotions/");
+ boolean update = false;
+ // Delete removed promotions
+ if (newPromotions != null) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File promotion : files) {
+ if (!newPromotions.contains(promotion.getName())) {
+ boolean deleted = promotion.delete();
+ if(deleted){
+ LOGGER.log(Level.INFO, String.format("Deleted promotion with name %s for %s", promotion.getName(), item.getName()));
+ update = true;
+ }
+ }
+ }
+ }
+ }
+
+ // Delegate to create-method
+ this.notifyItemCreated(item, dslEnvironment, update);
+ }
+
+}
diff --git a/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ReleasePromotionCondition.java b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ReleasePromotionCondition.java
new file mode 100755
index 00000000..5be0b0ff
--- /dev/null
+++ b/src/main/java/hudson/plugins/promoted_builds/integrations/jobdsl/ReleasePromotionCondition.java
@@ -0,0 +1,15 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import hudson.plugins.promoted_builds.PromotionCondition;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ * Special holder for the ReleasePromotionCondition generated by the Job DSL Plugin
+ *
+ * @author Dennis Schulte
+ */
+@XStreamAlias("hudson.plugins.release.promotion.ReleasePromotionCondition")
+public class ReleasePromotionCondition extends PromotionCondition {
+
+}
diff --git a/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslManualConditionConverterTest.java b/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslManualConditionConverterTest.java
new file mode 100644
index 00000000..f0951f9f
--- /dev/null
+++ b/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslManualConditionConverterTest.java
@@ -0,0 +1,30 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.Test;
+
+import com.thoughtworks.xstream.XStream;
+
+import hudson.util.XStream2;
+
+public class JobDslManualConditionConverterTest {
+
+ private static final XStream XSTREAM = new XStream2();
+
+ @Test
+ public void testShouldGenerateValidXml() throws Exception {
+ //Given
+ JobDslManualCondition mc = new JobDslManualCondition();
+ mc.setUsers("testusers");
+ //When
+ XSTREAM.registerConverter(new ManualConditionConverter(XSTREAM.getMapper(), XSTREAM.getReflectionProvider()));
+ String xml = XSTREAM.toXML(mc);
+ //Then
+ assertNotNull(xml);
+ System.out.println(xml);
+ assertTrue(StringUtils.contains(xml, "hudson.plugins.promoted__builds.conditions.ManualCondition"));
+ }
+}
diff --git a/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcessConverterTest.java b/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcessConverterTest.java
new file mode 100644
index 00000000..35a0ef8f
--- /dev/null
+++ b/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/JobDslPromotionProcessConverterTest.java
@@ -0,0 +1,54 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.Test;
+
+import com.thoughtworks.xstream.XStream;
+
+import groovy.util.Node;
+import hudson.plugins.promoted_builds.PromotionCondition;
+import hudson.plugins.promoted_builds.conditions.SelfPromotionCondition;
+import hudson.util.XStream2;
+
+public class JobDslPromotionProcessConverterTest {
+
+ private static final XStream XSTREAM = new XStream2();
+
+ @Test
+ public void testShouldGenerateValidXml() throws Exception {
+ // Given
+ JobDslPromotionProcess pp = new JobDslPromotionProcess();
+ //Conditions
+ List conditions = new ArrayList();
+ conditions.add(new SelfPromotionCondition(true));
+ //BuildSteps
+ List buildSteps = new ArrayList();
+ Node node = new Node(null, "hudson.tasks.Shell");
+ Node subNode = new Node(node, "command");
+ buildSteps.add(node);
+ subNode.setValue("echo hello;");
+ Node node2 = new Node(null, "hudson.plugins.parameterizedtrigger.TriggerBuilder");
+ Node subNode2 = new Node(node2, "configs");
+ Node subsubNode2 = new Node(subNode2, "hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig");
+ Node subsubsubNode2a = new Node(subsubNode2, "projects");
+ subsubsubNode2a.setValue("anoter-project");
+ Node subsubsubNode2b = new Node(subsubNode2, "condition");
+ subsubsubNode2b.setValue("ALWAYS");
+ buildSteps.add(node2);
+ pp.setBuildSteps(buildSteps);
+ pp.setConditions(conditions);
+ // When
+ XSTREAM.registerConverter(new JobDslPromotionProcessConverter(XSTREAM.getMapper(), XSTREAM.getReflectionProvider()));
+ String xml = XSTREAM.toXML(pp);
+ // Then
+ assertNotNull(xml);
+ System.out.println(xml);
+ assertTrue(StringUtils.contains(xml, "hudson.plugins.promoted__builds.PromotionProcess"));
+ }
+}
diff --git a/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsDslContextExtensionTest.java b/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsDslContextExtensionTest.java
new file mode 100644
index 00000000..c7629d0f
--- /dev/null
+++ b/src/test/java/hudson/plugins/promoted_builds/integrations/jobdsl/PromotionsDslContextExtensionTest.java
@@ -0,0 +1,44 @@
+package hudson.plugins.promoted_builds.integrations.jobdsl;
+
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import hudson.model.queue.QueueTaskFuture;
+
+import java.io.File;
+
+import javaposse.jobdsl.plugin.RemovedJobAction;
+import javaposse.jobdsl.plugin.ExecuteDslScripts;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Test;
+import org.jvnet.hudson.test.HudsonTestCase;
+
+public class PromotionsDslContextExtensionTest extends HudsonTestCase {
+
+ @Test
+ public void testShouldGenerateTheDefindedJob() throws Exception {
+ // Given
+ String dsl = FileUtils.readFileToString(new File("src/test/resources/example-dsl.groovy"));
+ FreeStyleProject seedJob = createFreeStyleProject();
+ seedJob.getBuildersList().add(
+ new ExecuteDslScripts(new ExecuteDslScripts.ScriptLocation(Boolean.TRUE.toString(), null, dsl), false, RemovedJobAction.DELETE));
+ // When
+ QueueTaskFuture scheduleBuild2 = seedJob.scheduleBuild2(0);
+ // Then
+ assertBuildStatusSuccess(scheduleBuild2);
+ }
+
+
+ @Test
+ public void testShouldGenerateTheDefindedComplexJob() throws Exception {
+ // Given
+ String dsl = FileUtils.readFileToString(new File("src/test/resources/complex-example-dsl.groovy"));
+ FreeStyleProject seedJob = createFreeStyleProject();
+ seedJob.getBuildersList().add(
+ new ExecuteDslScripts(new ExecuteDslScripts.ScriptLocation(Boolean.TRUE.toString(), null, dsl), false, RemovedJobAction.DELETE));
+ // When
+ QueueTaskFuture scheduleBuild2 = seedJob.scheduleBuild2(0);
+ // Then
+ assertBuildStatusSuccess(scheduleBuild2);
+ }
+}
diff --git a/src/test/resources/complex-example-dsl.groovy b/src/test/resources/complex-example-dsl.groovy
new file mode 100644
index 00000000..70011276
--- /dev/null
+++ b/src/test/resources/complex-example-dsl.groovy
@@ -0,0 +1,45 @@
+freeStyleJob('test-job-complex') {
+ properties{
+ promotions {
+ promotion {
+ name("Development")
+ icon("star-red")
+ restrict('slave1')
+ conditions {
+ manual('testuser'){
+ parameters{
+ textParam("parameterName","defaultValue","description")
+ }
+ }
+ }
+ actions {
+ downstreamParameterized {
+ trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
+ predefinedProp("ENVIRONMENT","dev-server")
+ predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
+ predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
+ }
+ }
+ }
+ }
+ promotion {
+ name("Test")
+ icon("star-yellow")
+ restrict('slave2')
+ conditions {
+ manual('testuser')
+ upstream("Development")
+ }
+ actions {
+ downstreamParameterized {
+ trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
+ predefinedProp("ENVIRONMENT","test-server")
+ predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
+ predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/resources/example-dsl.groovy b/src/test/resources/example-dsl.groovy
new file mode 100644
index 00000000..2178b231
--- /dev/null
+++ b/src/test/resources/example-dsl.groovy
@@ -0,0 +1,15 @@
+freeStyleJob('test-job') {
+ properties{
+ promotions {
+ promotion {
+ name('Development')
+ conditions {
+ manual('tester')
+ }
+ actions {
+ shell('echo hello;')
+ }
+ }
+ }
+ }
+}