package hudson.plugins.parameterizedtrigger;
import com.google.common.collect.ImmutableList;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.console.ModelHyperlinkNote;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.DependecyDeclarer;
import hudson.model.DependencyGraph;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class BuildTrigger extends Notifier implements DependecyDeclarer {
private final ArrayList<BuildTriggerConfig> configs;
@DataBoundConstructor
public BuildTrigger(List<BuildTriggerConfig> configs) {
this.configs = new ArrayList<BuildTriggerConfig>(Util.fixNull(configs));
}
public BuildTrigger(BuildTriggerConfig... configs) {
this(Arrays.asList(configs));
}
public List<BuildTriggerConfig> getConfigs() {
return configs;
}
@Override
public boolean needsToRunAfterFinalized() {
return true;
}
@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
@Override
public Collection<? extends Action> getProjectActions(AbstractProject<?, ?> project) {
return ImmutableList.of(new DynamicProjectAction(configs));
}
@Override @SuppressWarnings("deprecation")
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
Map<String, AbstractBuild> downstreamMap = new HashMap<String, AbstractBuild>();
Map<String, Integer> buildMap = new HashMap<String, Integer>();
boolean hasEnvVariables = false;
HashSet<BuildTriggerConfig> alreadyFired = new HashSet<BuildTriggerConfig>();
// If this project has non-abstract projects, we need to fire them
for (BuildTriggerConfig config : configs) {
boolean hasNonAbstractProject = false;
hasEnvVariables = hasEnvVariables || hasEnvVariables(config, build.getEnvironment(listener));
List<Job> jobs = config.getJobs(build.getRootBuild().getProject().getParent(), build.getEnvironment(listener));
for (Job j : jobs) {
if (!(j instanceof AbstractProject)) {
hasNonAbstractProject = true;
break;
}
}
// Fire this config's projects if not already fired
if (hasNonAbstractProject) {
config.perform(build, launcher, listener);
alreadyFired.add(config);
}
}
if (canDeclare(build.getProject()) && !hasEnvVariables) {
// job will get triggered by dependency graph, so we have to capture buildEnvironment NOW before
// hudson.model.AbstractBuild.AbstractBuildExecution#cleanUp is called and reset
EnvVars env = build.getEnvironment(listener);
build.addAction(new CapturedEnvironmentAction(env));
}
else { // Not using dependency graph
for (BuildTriggerConfig config : configs) {
if (!alreadyFired.contains(config)) {
//config.perform(build, launcher, listener);
List<Future<AbstractBuild>> futures = config.perform(build, launcher, listener);
for (Future future : futures) {
AbstractBuild abstractBuild = null;
try {
abstractBuild = (AbstractBuild) future.get();
if (null != abstractBuild) {
downstreamMap.put(abstractBuild.getProject().getFullName(), abstractBuild);
}
} catch (ExecutionException e) {
listener.getLogger().println("Failed to execute downstream build");
}
}
String[] projects = config.getProjects(build.getEnvironment(listener)).split(",");
String[] vars = config.getProjects().split(",");
for (int i = 0; i < projects.length; i++) {
if (vars[i].trim().contains("$")) {
AbstractBuild abstractBuild = downstreamMap.get(projects[i]);
if (null != abstractBuild) {
listener.getLogger().println(makeLogEntry(projects[i].trim()));
buildMap.put(abstractBuild.getProject().getFullName(), abstractBuild.getNumber());
}
}
}
}
DynamicBuildAction action = new DynamicBuildAction(buildMap);
build.addAction(action);
}
}
return true;
}
private String makeLogEntry(String name) {
String url = name;
url = Jenkins.getInstance().getRootUrl() + "job/" + url.replaceAll("/", "/job/");
name = name.replaceAll("/", " ยป ");
String link = ModelHyperlinkNote.encodeTo(url, name);
StringBuilder sb = new StringBuilder();
sb.append("Triggering a new build of ");
sb.append(link);
return sb.toString();
}
private boolean hasEnvVariables(BuildTriggerConfig config, EnvVars env) {
return !config.getProjects().equalsIgnoreCase(config.getProjects(env));
}
@Override
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
// Can only add dependencies in Hudson 1.341 or higher
if (!canDeclare(owner)) return;
for (BuildTriggerConfig config : configs) {
List<AbstractProject> projectList = config.getProjectList(owner.getParent(), null);
for (AbstractProject project : projectList) {
if (config.isTriggerFromChildProjects() && owner instanceof ItemGroup) {
ItemGroup<Item> parent = (ItemGroup) owner;
for (Item item : parent.getItems()) {
if(item instanceof AbstractProject){
AbstractProject child = (AbstractProject) item;
ParameterizedDependency.add(child, project, config, graph);
}
}
}
else{
ParameterizedDependency.add(owner, project, config, graph);
}
}
}
}
private boolean canDeclare(AbstractProject owner) {
// In Hudson 1.341+ builds will be triggered via DependencyGraph
// Inner class added in Hudson 1.341
String ownerClassName = owner.getClass().getName();
return DependencyGraph.class.getClasses().length > 0
// See HUDSON-5679 -- dependency graph is also not used when triggered from a promotion
&& !ownerClassName.equals("hudson.plugins.promoted_builds.PromotionProcess");
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public String getDisplayName() {
return "Trigger parameterized build on other projects";
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
}
}