Skip to content

Commit

Permalink
Merge pull request #5 from scottanderson/master
Browse files Browse the repository at this point in the history
Multiple bug fixes
  • Loading branch information
morficus committed Nov 6, 2014
2 parents 5cb45c5 + 3e0cf93 commit a8c7aa6
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package org.jenkinsci.plugins.ParameterizedRemoteTrigger;

import hudson.EnvVars;
import hudson.model.EnvironmentContributingAction;
import hudson.model.Result;
import hudson.model.AbstractBuild;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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_";

private List<BuildReference> builds;

public BuildInfoExporterAction(AbstractBuild<?, ?> parentBuild, BuildReference buildRef) {
super();

this.builds = new ArrayList<BuildReference>();
this.builds.add(buildRef);
}

static BuildInfoExporterAction addBuildInfoExporterAction(AbstractBuild<?, ?> parentBuild, String triggeredProject, int buildNumber, Result buildResult) {
BuildReference reference = new BuildReference(triggeredProject, buildNumber, buildResult);

BuildInfoExporterAction action = parentBuild.getAction(BuildInfoExporterAction.class);
if (action == null) {
action = new BuildInfoExporterAction(parentBuild, reference);
parentBuild.getActions().add(action);
} else {
action.addBuildReference(reference);
}
return action;
}

public void addBuildReference(BuildReference buildRef) {
this.builds.add(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 String getIconFileName() {
// TODO Auto-generated method stub
return null;
}

public String getDisplayName() {
// TODO Auto-generated method stub
return null;
}

public String getUrlName() {
// TODO Auto-generated method stub
return null;
}

public void buildEnvVars(AbstractBuild<?, ?> build, EnvVars env) {
for (String project : getProjectsWithBuilds()) {
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 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();
}

/**
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import hudson.util.ListBoxModel;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.AbstractProject;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
Expand Down Expand Up @@ -533,7 +534,6 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis
}

int nextBuildNumber = queryResponseObject.getInt("nextBuildNumber");
listener.getLogger().println("This job is build #[" + Integer.toString(nextBuildNumber) + "] on the remote server.");

if (this.getOverrideAuth()) {
listener.getLogger().println(
Expand All @@ -543,6 +543,44 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis

listener.getLogger().println("Triggering remote job now.");
sendHTTPCall(triggerUrlString, "POST", build, listener);
// Validate the build number via parameters
foundIt: for (int tries = 3; tries > 0; tries--) {
for (int buildNumber : new SearchPattern(nextBuildNumber, 2)) {
listener.getLogger().println("Checking parameters of #" + buildNumber);
String validateUrlString = this.buildGetUrl(jobName, securityToken) + "/" + buildNumber + "/api/json/";
JSONObject validateResponse = sendHTTPCall(validateUrlString, "GET", build, listener);
if (validateResponse == null) {
listener.getLogger().println("Query failed.");
continue;
}
JSONArray actions = validateResponse.getJSONArray("actions");
for (int i = 0; i < actions.size(); i++) {
JSONObject action = actions.getJSONObject(i);
if (!action.has("parameters")) continue;
JSONArray parameters = action.getJSONArray("parameters");
// Check if the parameters match
if (compareParameters(listener, parameters, cleanedParams)) {
// We now have a very high degree of confidence that this is the correct build.
// It is still possible that this is a false positive if there are no parameters,
// or multiple jobs use the same parameters.
nextBuildNumber = buildNumber;
break foundIt;
}
// This is the wrong build
break;
}

// Sleep for 'pollInterval' seconds.
// Sleep takes miliseconds so need to convert this.pollInterval to milisecopnds (x 1000)
try {
Thread.sleep(this.pollInterval * 1000);
} catch (InterruptedException e) {
this.failBuild(e, listener);
}
}
}
listener.getLogger().println("This job is build #[" + Integer.toString(nextBuildNumber) + "] on the remote server.");
BuildInfoExporterAction.addBuildInfoExporterAction(build, jobName, nextBuildNumber, Result.NOT_BUILT);

//Have to form the string ourselves, as we might not get a response from non-parameterized builds
String jobURL = remoteServerURL + "/job/" + this.encodeValue(job) + "/";
Expand Down Expand Up @@ -602,6 +640,7 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis
}
}
listener.getLogger().println("Remote build finished with status " + buildStatusStr + ".");
BuildInfoExporterAction.addBuildInfoExporterAction(build, jobName, nextBuildNumber, Result.fromString(buildStatusStr));

// If build did not finish with 'success' then fail build step.
if (!buildStatusStr.equals("SUCCESS")) {
Expand All @@ -616,6 +655,39 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis
return true;
}

private String findParameter(String parameter, List<String> parameters) {
for (String search : parameters) {
if (search.startsWith(parameter + "=")) {
return search.substring(parameter.length() + 1);
}
}
return null;
}

private boolean compareParameters(BuildListener listener, JSONArray parameters, List<String> expectedParams) {
for (int j = 0; j < parameters.size(); j++) {
JSONObject parameter = parameters.getJSONObject(j);
String name = parameter.getString("name");
String value = parameter.getString("value");
String expected = findParameter(name, expectedParams);

if (expected == null) {
// If we didn't specify all of the parameters, this will happen, so we can not infer that this it he wrong build
listener.getLogger().println("Unable to find expected value for " + name);
continue;
}

// If we got the expected value, skip to the next parameter
if (expected.equals(value)) continue;

// We didn't get the expected value
listener.getLogger().println("Param " + name + " doesn't match!");
return false;
}
// All found parameters matched. This if there are no uniquely identifying parameters, this could still be a false positive.
return true;
}

public String getBuildStatus(String buildUrlString, AbstractBuild build, BuildListener listener) throws IOException {
String buildStatus = "UNKNOWN";

Expand Down Expand Up @@ -909,11 +981,11 @@ private boolean isRemoteJobParameterized(String jobName, AbstractBuild build, Bu
//build the proper URL to inspect the remote job
RemoteJenkinsServer remoteServer = this.findRemoteHost(this.getRemoteJenkinsName());
String remoteServerUrl = remoteServer.getAddress().toString();
remoteServerUrl += "/job/" + jobName;
remoteServerUrl += "/job/" + encodeValue(jobName);
remoteServerUrl += "/api/json";

try {
JSONObject response = sendHTTPCall(encodeValue(remoteServerUrl), "GET", build, listener);
JSONObject response = sendHTTPCall(remoteServerUrl, "GET", build, listener);

if(response.getJSONArray("actions").size() >= 1){
isParameterized = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.jenkinsci.plugins.ParameterizedRemoteTrigger;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
* Search around a specified {@link startingValue} by a magnitude of {@link maxDrift}.
*/
public class SearchPattern implements Iterable<Integer> {
private final int startingValue;
private final int maxDrift;

public SearchPattern(int startingValue, int maxDrift) {
this.startingValue = startingValue;
this.maxDrift = maxDrift;
}

public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
private int drift = 0;

public boolean hasNext() {
return (drift != -maxDrift - 1);
}

public Integer next() {
if (! hasNext()) throw new NoSuchElementException();
int ret = startingValue + drift;
if (drift < 0) {
drift = -drift;
} else {
drift = -drift - 1;
}
return ret;
}

public void remove() {
next();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.jenkinsci.plugins.ParameterizedRemoteTrigger;

import junit.framework.TestCase;

import java.util.Iterator;

public class SearchPatternTest extends TestCase {

public void testSearchPattern() {
SearchPattern sp = new SearchPattern(5, 2);
// Test iterator() twice
for (int x = 0; x < 2; x++) {
Iterator<Integer> it = sp.iterator();
assertEquals(5, it.next().intValue());
assertEquals(4, it.next().intValue());
assertEquals(6, it.next().intValue());
assertEquals(3, it.next().intValue());
assertEquals(7, it.next().intValue());
assertEquals(false, it.hasNext());
}
}

}

0 comments on commit a8c7aa6

Please sign in to comment.