/*
* The MIT License
*
* Copyright (c) 2011-2, Jørgen P. Tjernø <jorgenpt@gmail.com>
* Chris Johnson
* Geoff Cummings
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.plugins.parameterizedtrigger;
import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.EnvironmentContributingAction;
import hudson.model.Result;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
@ExportedBean
public class BuildInfoExporterAction implements EnvironmentContributingAction {
public static final String JOB_NAME_VARIABLE = "LAST_TRIGGERED_JOB_NAME";
public static final String ALL_JOBS_NAME_VARIABLE = "TRIGGERED_JOB_NAMES";
public static final String BUILD_NUMBER_VARIABLE_PREFIX = "TRIGGERED_BUILD_NUMBER_";
public static final String ALL_BUILD_NUMBER_VARIABLE_PREFIX = "TRIGGERED_BUILD_NUMBERS_";
public static final String BUILD_RESULT_VARIABLE_PREFIX = "TRIGGERED_BUILD_RESULT_";
public static final String BUILD_RUN_COUNT_PREFIX = "TRIGGERED_BUILD_RUN_COUNT_";
public static final String RUN = "_RUN_";
//now unused as part of map
private transient String buildName;
private transient int buildNumber;
// used in version =< 2.21.
// this is now migrated to this.builds.
private transient Map<String, List<BuildReference>> buildRefs;
private List<BuildReference> builds;
private BuildReference lastReference;
public BuildInfoExporterAction(BuildReference buildRef) {
super();
this.builds = new ArrayList<BuildReference>();
addBuild(buildRef);
lastReference = buildRef;
}
public BuildInfoExporterAction(String buildName, int buildNumber, AbstractBuild<?, ?> parentBuild, Result buildResult) {
this(new BuildReference(buildName, buildNumber, buildResult));
}
static BuildInfoExporterAction addBuildInfoExporterAction(AbstractBuild<?, ?> parentBuild, String triggeredProject, int buildNumber, Result buildResult) {
BuildInfoExporterAction action = parentBuild.getAction(BuildInfoExporterAction.class);
if (action == null) {
action = new BuildInfoExporterAction(triggeredProject, buildNumber, parentBuild, buildResult);
parentBuild.getActions().add(action);
} else {
action.addBuildReference(triggeredProject, buildNumber, buildResult);
}
return action;
}
static BuildInfoExporterAction addBuildInfoExporterAction(AbstractBuild<?, ?> parentBuild, String triggeredProject) {
BuildInfoExporterAction action = parentBuild.getAction(BuildInfoExporterAction.class);
if (action == null) {
action = new BuildInfoExporterAction(new BuildReference(triggeredProject));
parentBuild.getActions().add(action);
} else {
action.addBuildReference(new BuildReference(triggeredProject));
}
return action;
}
private void addBuild(BuildReference br) {
this.builds.add(br);
if (br.buildNumber != 0) {
this.lastReference = br;
}
}
public void addBuildReference(String triggeredProject, int buildNumber, Result buildResult) {
BuildReference buildRef = new BuildReference(triggeredProject, buildNumber, buildResult);
addBuild(buildRef);
}
public void addBuildReference(BuildReference buildRef) {
addBuild(buildRef);
}
public static class BuildReference {
public final String projectName;
public final int buildNumber;
public final Result buildResult;
public BuildReference(String projectName, int buildNumber, Result buildResult) {
this.projectName = projectName;
this.buildNumber = buildNumber;
this.buildResult = buildResult;
}
public BuildReference(final String projectName) {
this.projectName = projectName;
this.buildNumber = 0;
this.buildResult = Result.NOT_BUILT;
}
}
@Override
public String getIconFileName() {
return null;
}
@Override
public String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return null;
}
@Override
public void buildEnvVars(AbstractBuild<?, ?> build, EnvVars env) {
// Note: this will only indicate the last project in the list that is ran
env.put(JOB_NAME_VARIABLE, lastReference.projectName.replaceAll("[^a-zA-Z0-9]+", "_"));
//all projects triggered.
// this should not include projects that donot have a build item.
String sanatizedProjectList = getProjectListString(",");
env.put(ALL_JOBS_NAME_VARIABLE, sanatizedProjectList);
for (String project : getProjectsWithBuilds()) {
// for each project add the following variables once
// all buildnumbers, lastbuildnumber
// all Run results, last build result
String sanatizedBuildName = project.replaceAll("[^a-zA-Z0-9]+", "_");
List<BuildReference> refs = getBuildRefs(project);
env.put(ALL_BUILD_NUMBER_VARIABLE_PREFIX + sanatizedBuildName, getBuildNumbersString(refs, ","));
env.put(BUILD_RUN_COUNT_PREFIX + sanatizedBuildName, Integer.toString(refs.size()));
for (BuildReference br : refs) {
if (br.buildNumber != 0) {
String tiggeredBuildRunResultKey = BUILD_RESULT_VARIABLE_PREFIX + sanatizedBuildName + RUN + Integer.toString(br.buildNumber);
env.put(tiggeredBuildRunResultKey, br.buildResult.toString());
}
}
BuildReference lastBuild = null;
for (int i = (refs.size()); i > 0; i--) {
if (refs.get(i - 1).buildNumber != 0) {
lastBuild = refs.get(i - 1);
}
break;
}
if (lastBuild != null) {
env.put(BUILD_NUMBER_VARIABLE_PREFIX + sanatizedBuildName, Integer.toString(lastBuild.buildNumber));
env.put(BUILD_RESULT_VARIABLE_PREFIX + sanatizedBuildName, lastBuild.buildResult.toString());
}
}
}
private List<BuildReference> getBuildRefs(String project) {
List<BuildReference> refs = new ArrayList<BuildReference>();
for (BuildReference br : builds) {
if (br.projectName.equals(project)) refs.add(br);
}
return refs;
}
/**
* Gets all the builds triggered from this one, filters out the items that
* were non blocking, which we don't have a builds for. Used in the UI for see
* Summary.groovy
*
* @return a list of builds that are triggered by this build. May contains null if a project or a build is deleted.
*/
@Exported(visibility = 1)
public List<AbstractBuild<?, ?>> getTriggeredBuilds() {
List<AbstractBuild<?, ?>> builds = new ArrayList<AbstractBuild<?, ?>>();
for (BuildReference br : this.builds) {
AbstractProject<?, ? extends AbstractBuild<?, ?>> project =
Jenkins.getInstance().getItemByFullName(br.projectName, AbstractProject.class);
if (br.buildNumber != 0) {
builds.add((project != null)?project.getBuildByNumber(br.buildNumber):null);
}
}
return builds;
}
/**
* Gets all the projects that triggered from this one which were non blocking,
* which we don't have a builds for. Does not include builds that are returned
* in #link{getTriggeredBuilds} Used in the UI for see Summary.groovy
*
* @return List of Projects that are triggered by this build. May contains null if a project is deleted.
*/
@Exported(visibility = 1)
public List<AbstractProject<?, ?>> getTriggeredProjects() {
List<AbstractProject<?, ?>> projects = new ArrayList<AbstractProject<?, ?>>();
for (BuildReference br : this.builds) {
if (br.buildNumber == 0) {
AbstractProject<?, ? extends AbstractBuild<?, ?>> project =
Jenkins.getInstance().getItemByFullName(br.projectName, AbstractProject.class);
projects.add(project);
}
}
return projects;
}
/**
* Handle cases from older builds so that they still add old variables if
* needed to. Should not show any UI as there will be no data added.
*
* @return
*/
public Object readResolve() {
if (this.lastReference == null) {
this.lastReference = new BuildReference(this.buildName, this.buildNumber, Result.NOT_BUILT);
}
if (this.builds == null) {
this.builds = new ArrayList<BuildReference>();
}
if (this.buildRefs != null) {
for (List<BuildReference> buildReferences : buildRefs.values()) {
this.builds.addAll(buildReferences);
}
}
return this;
}
/**
* Gets a string for all of the build numbers
*
* @param refs List of build references to process.
* @param separator
* @return String containing all the build numbers from refs, never null but
* can be empty
*/
private String getBuildNumbersString(List<BuildReference> refs, String separator) {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (BuildReference s : refs) {
if (s.buildNumber != 0) {
if (first) {
first = false;
} else {
buf.append(separator);
}
buf.append(s.buildNumber);
}
}
return buf.toString();
}
/**
* Get a list of projects as a string using the separator
*
* @param separator
* @return list of projects separated by separator
*/
protected String getProjectListString(String separator) {
Set<String> refs = getProjectsWithBuilds();
StringBuilder buf = new StringBuilder();
boolean first = true;
for (String s : refs) {
if (first) {
first = false;
} else {
buf.append(separator);
}
buf.append(s.replaceAll("[^a-zA-Z0-9]+", "_"));
}
return buf.toString();
}
/**
* Gets the unique set of project names that have a linked build.
*
* @return Set of project names that have at least one build linked.
*/
private Set<String> getProjectsWithBuilds() {
Set<String> projects = new HashSet<String>();
for (BuildReference br : this.builds) {
if (br.buildNumber != 0) {
projects.add(br.projectName);
}
}
return projects;
}
}