hudson.plugins.promoted_builds.conditions.DownstreamPassCondition Maven / Gradle / Ivy
package hudson.plugins.promoted_builds.conditions;
import hudson.CopyOnWrite;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Cause.UpstreamCause;
import hudson.model.Fingerprint;
import hudson.model.Fingerprint.BuildPtr;
import hudson.model.Hudson;
import hudson.model.InvisibleAction;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.plugins.promoted_builds.JobPropertyImpl;
import hudson.plugins.promoted_builds.PromotionBadge;
import hudson.plugins.promoted_builds.PromotionCondition;
import hudson.plugins.promoted_builds.PromotionConditionDescriptor;
import hudson.plugins.promoted_builds.PromotionProcess;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* {@link PromotionCondition} that tests if certain downstream projects have passed.
*
* @author Kohsuke Kawaguchi
*/
public class DownstreamPassCondition extends PromotionCondition {
/**
* List of downstream jobs that are used as the promotion criteria.
*
* Every job has to have at least one successful build for us to promote a build.
*/
private final String jobs;
public DownstreamPassCondition(String jobs) {
this.jobs = jobs;
}
public String getJobs() {
return jobs;
}
@Override
public PromotionBadge isMet(AbstractBuild,?> build) {
Badge badge = new Badge();
PseudoDownstreamBuilds pdb = build.getAction(PseudoDownstreamBuilds.class);
OUTER:
for (AbstractProject,?> j : getJobList()) {
for( AbstractBuild,?> b : build.getDownstreamBuilds(j) ) {
if(b.getResult()== Result.SUCCESS) {
badge.add(b);
continue OUTER;
}
}
if (pdb!=null) {// if fingerprint doesn't have any, try the pseudo-downstream
for (AbstractBuild,?> b : pdb.listBuilds(j)) {
if(b.getResult()== Result.SUCCESS) {
badge.add(b);
continue OUTER;
}
}
}
// none of the builds of this job passed.
return null;
}
return badge;
}
/**
* List of downstream jobs that we need to monitor.
*
* @return never null.
*/
public List> getJobList() {
List> r = new ArrayList>();
for (String name : Util.tokenize(jobs,",")) {
AbstractProject job = Hudson.getInstance().getItemByFullName(name.trim(),AbstractProject.class);
if(job!=null) r.add(job);
}
return r;
}
/**
* Short-cut for {@code getJobList().contains(job)}.
*/
public boolean contains(AbstractProject,?> job) {
if(!jobs.contains(job.getFullName())) return false; // quick rejection test
for (String name : Util.tokenize(jobs,",")) {
if(name.trim().equals(job.getFullName()))
return true;
}
return false;
}
public static final class Badge extends PromotionBadge {
/**
* Downstream builds that certified this build. Should be considered read-only.
*/
public final List builds = new ArrayList();
void add(AbstractBuild,?> b) {
builds.add(new Fingerprint.BuildPtr(b));
}
}
@Extension
public static final class DescriptorImpl extends PromotionConditionDescriptor {
public boolean isApplicable(AbstractProject,?> item) {
return true;
}
public String getDisplayName() {
return "When the following downstream projects build successfully";
}
public String getHelpFile() {
return "/plugin/promoted-builds/conditions/downstream.html";
}
public PromotionCondition newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new DownstreamPassCondition(formData.getString("jobs"));
}
public static final DescriptorImpl INSTANCE = new DescriptorImpl();
}
/**
* {@link RunListener} to pick up completions of downstream builds.
*
*
* This is a single instance that receives all the events everywhere in the system.
* @author Kohsuke Kawaguchi
*/
@Extension
public static final class RunListenerImpl extends RunListener> {
public RunListenerImpl() {
super((Class)AbstractBuild.class);
}
@Override
public void onCompleted(AbstractBuild,?> build, TaskListener listener) {
// this is not terribly efficient,
for(AbstractProject,?> j : Hudson.getInstance().getAllItems(AbstractProject.class)) {
boolean warned = false; // used to avoid warning for the same project more than once.
JobPropertyImpl jp = j.getProperty(JobPropertyImpl.class);
if(jp!=null) {
for (PromotionProcess p : jp.getItems()) {
boolean considerPromotion = false;
for (PromotionCondition cond : p.conditions) {
if (cond instanceof DownstreamPassCondition) {
DownstreamPassCondition dpcond = (DownstreamPassCondition) cond;
if(dpcond.contains(build.getParent())) {
considerPromotion = true;
break;
}
}
}
if(considerPromotion) {
try {
AbstractBuild,?> u = build.getUpstreamRelationshipBuild(j);
if (u==null) {
// if the fingerprint doesn't tell us, perhaps the cause would tell us?
for (UpstreamCause uc : Util.filter(build.getCauses(), UpstreamCause.class)) {
if (uc.getUpstreamProject().equals(j.getFullName())) {
u = j.getBuildByNumber(uc.getUpstreamBuild());
if (u!=null) {
// remember that this build is a pseudo-downstream of the discovered build.
PseudoDownstreamBuilds pdb = u.getAction(PseudoDownstreamBuilds.class);
if (pdb==null)
u.addAction(pdb=new PseudoDownstreamBuilds());
pdb.add(build);
u.save();
break;
}
}
}
}
if (u==null) {
// no upstream build. perhaps a configuration problem?
if(build.getResult()==Result.SUCCESS && !warned) {
listener.getLogger().println("WARNING: "+j.getFullDisplayName()+" appears to use this job as a promotion criteria, " +
"but no fingerprint is recorded. Fingerprint needs to be enabled on both this job and "+j.getFullDisplayName()+". " +
"See http://hudson.gotdns.com/wiki/display/HUDSON/Fingerprint for more details");
warned = true;
}
}
if(u!=null && p.considerPromotion(u))
listener.getLogger().println("Promoted "+u);
} catch (IOException e) {
e.printStackTrace(listener.error("Failed to promote a build"));
}
}
}
}
}
}
/**
* List of downstream jobs that we are interested in.
*/
@CopyOnWrite
private static volatile Set DOWNSTREAM_JOBS = Collections.emptySet();
/**
* Called whenever some {@link JobPropertyImpl} changes to update {@link #DOWNSTREAM_JOBS}.
*/
public static void rebuildCache() {
DOWNSTREAM_JOBS = new HashSet();
}
}
/**
* Remembers those downstream jobs that are not related by fingerprint but by the triggering relationship.
* This is a weaker form of the relationship and less reliable, but often people don't understand
* the notion of fingerprints, in which case this works.
*/
public static class PseudoDownstreamBuilds extends InvisibleAction {
final List builds = new ArrayList();
public void add(AbstractBuild,?> run) {
builds.add(new BuildPtr(run));
}
public List> listBuilds(AbstractProject, ?> job) {
List> list = new ArrayList>();
for (BuildPtr b : builds) {
if (b.is(job)) {
Run r = b.getRun();
if (r instanceof AbstractBuild)
// mainly null check, plus a defensive measure caused by a possible rename.
list.add((AbstractBuild)r);
}
}
return list;
}
}
}