Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to build with parameters #101

Merged
merged 11 commits into from
Aug 3, 2016
26 changes: 21 additions & 5 deletions src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import hudson.model.UnprotectedRootAction;
import hudson.plugins.tfs.model.AbstractCommand;
import hudson.plugins.tfs.model.BuildCommand;
import hudson.plugins.tfs.model.BuildWithParametersCommand;
import hudson.plugins.tfs.model.PingCommand;
import hudson.plugins.tfs.util.MediaType;
import jenkins.model.Jenkins;
Expand Down Expand Up @@ -33,6 +34,7 @@
import java.util.logging.Logger;

import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
Expand All @@ -47,14 +49,15 @@ public class TeamBuildEndpoint implements UnprotectedRootAction {
private static final Map<String, AbstractCommand.Factory> COMMAND_FACTORIES_BY_NAME;
public static final String URL_NAME = "team-build";
public static final String TEAM_PARAMETERS = "team-parameters";
public static final String PARAMETER = "parameter";
static final String URL_PREFIX = "/" + URL_NAME + "/";
private static final String JSON = "json";
private static final String PARAMETER = "parameter";

static {
final Map<String, AbstractCommand.Factory> map = new TreeMap<String, AbstractCommand.Factory>(String.CASE_INSENSITIVE_ORDER);
map.put("ping", new PingCommand.Factory());
map.put("build", new BuildCommand.Factory());
map.put("buildWithParameters", new BuildWithParametersCommand.Factory());
COMMAND_FACTORIES_BY_NAME = Collections.unmodifiableMap(map);
}

Expand Down Expand Up @@ -187,13 +190,18 @@ void dispatch(final StaplerRequest req, final StaplerResponse rsp, final TimeDur
final JSONObject response;
if (isStructuredForm(req.getParameter(JSON))) {
final JSONObject formData = req.getSubmittedForm();
response = command.perform(project, formData, actualDelay);
response = command.perform(project, req, formData, actualDelay);
}
else {
response = command.perform(project, req, delay);
response = command.perform(project, req, actualDelay);
}

rsp.setStatus(SC_OK);
if (response.containsKey("created")) {
rsp.setStatus(SC_CREATED);
}
else {
rsp.setStatus(SC_OK);
}
rsp.setContentType(MediaType.APPLICATION_JSON_UTF_8);
final PrintWriter w = rsp.getWriter();
final String responseJsonString = response.toString();
Expand All @@ -218,7 +226,7 @@ static boolean isStructuredForm(final String jsonParameter) {
if (jsonParameter != null) {
try {
final JSONObject jsonObject = JSONObject.fromObject(jsonParameter);
if (jsonObject.containsKey(PARAMETER) && jsonObject.containsKey(TEAM_PARAMETERS)) {
if (jsonObject.containsKey(TEAM_PARAMETERS)) {
return true;
}
}
Expand All @@ -245,4 +253,12 @@ public void doBuild(
dispatch(request, response, delay);
}

public void doBuildWithParameters(
final StaplerRequest request,
final StaplerResponse response,
@QueryParameter final TimeDuration delay
) {
dispatch(request, response, delay);
}

}
3 changes: 2 additions & 1 deletion src/main/java/hudson/plugins/tfs/model/AbstractCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ public interface Factory {
* {@code requestPayload} and returning the output as a {@link JSONObject}.
*
* @param project an {@link AbstractProject to operate on}
* @param request a {@link StaplerRequest} to help build parameter values
* @param requestPayload a {@link JSONObject} representing the command's input
* @param delay how long to wait before the project starts executing
*
* @return a {@link JSONObject} representing the hook event's output
*/
public abstract JSONObject perform(final AbstractProject project, final JSONObject requestPayload, final TimeDuration delay);
public abstract JSONObject perform(final AbstractProject project, final StaplerRequest request, final JSONObject requestPayload, final TimeDuration delay);

/**
* Actually do the work of the command, using the supplied
Expand Down
126 changes: 74 additions & 52 deletions src/main/java/hudson/plugins/tfs/model/BuildCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,28 @@
import hudson.model.Action;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Queue;
import hudson.model.queue.ScheduleResult;
import hudson.plugins.tfs.CommitParameterAction;
import hudson.plugins.tfs.PullRequestParameterAction;
import hudson.plugins.tfs.TeamBuildEndpoint;
import hudson.plugins.tfs.util.MediaType;
import jenkins.model.Jenkins;
import jenkins.util.TimeDuration;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.jfree.data.Values;
import org.kohsuke.stapler.StaplerRequest;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
Expand All @@ -30,6 +42,7 @@ public class BuildCommand extends AbstractCommand {
private static final String REFS_PULL_SLASH = "refs/pull/";
private static final int REFS_PULL_SLASH_LENGTH = REFS_PULL_SLASH.length();
private static final String BUILD_REPOSITORY_URI = "Build.Repository.Uri";
private static final String BUILD_REPOSITORY_NAME = "Build.Repository.Name";
private static final String SYSTEM_TEAM_PROJECT = "System.TeamProject";
private static final String BUILD_SOURCE_VERSION = "Build.SourceVersion";
private static final String BUILD_REQUESTED_FOR = "Build.RequestedFor";
Expand All @@ -43,17 +56,17 @@ public AbstractCommand create() {

@Override
public String getSampleRequestPayload() {
return "{\n" +
" \"team-parameters\":\n" +
" {\n" +
" \"collectionUri\":\"https://fabrikam-fiber-inc.visualstudio.com\",\n" +
" \"repoUri\":\"https://fabrikam-fiber-inc.visualstudio.com/Personal/_git/olivida.tfs-plugin\",\n" +
" \"projectId\":\"Personal\",\n" +
" \"repoId\":\"olivida.tfs-plugin\",\n" +
" \"commit\":\"6a23fc7afec31f0a14bade6544bed4f16492e6d2\",\n" +
" \"pushedBy\":\"olivida\"\n" +
" }\n" +
"}";
final Class<? extends Factory> me = this.getClass();
final InputStream stream = me.getResourceAsStream("BuildCommand.json");
try {
return IOUtils.toString(stream, MediaType.UTF_8);
}
catch (final IOException e) {
throw new Error(e);
}
finally {
IOUtils.closeQuietly(stream);
}
}
}

Expand All @@ -79,7 +92,7 @@ protected JSONObject innerPerform(final AbstractProject project, final TimeDurat
}

@Override
public JSONObject perform(final AbstractProject project, final JSONObject requestPayload, final TimeDuration delay) {
public JSONObject perform(final AbstractProject project, final StaplerRequest req, final JSONObject requestPayload, final TimeDuration delay) {

final List<Action> actions = new ArrayList<Action>();
if (requestPayload.containsKey(TeamBuildEndpoint.TEAM_PARAMETERS)) {
Expand All @@ -96,7 +109,32 @@ public JSONObject perform(final AbstractProject project, final JSONObject reques
}
actions.add(action);
}
// TODO: detect if a job is parameterized and react appropriately

//noinspection UnnecessaryLocalVariable
final Job<?, ?> job = project;
final ParametersDefinitionProperty pp = job.getProperty(ParametersDefinitionProperty.class);
if (pp != null && requestPayload.containsKey(TeamBuildEndpoint.PARAMETER)) {
final List<ParameterValue> values = new ArrayList<ParameterValue>();
final JSONArray a = requestPayload.getJSONArray(TeamBuildEndpoint.PARAMETER);

for (final Object o : a) {
final JSONObject jo = (JSONObject) o;
final String name = jo.getString("name");

final ParameterDefinition d = pp.getParameterDefinition(name);
if (d == null)
throw new IllegalArgumentException("No such parameter definition: " + name);
final ParameterValue parameterValue = d.createValue(req, jo);
if (parameterValue != null) {
values.add(parameterValue);
}
else {
throw new IllegalArgumentException("Cannot retrieve the parameter value: " + name);
}
}
final ParametersAction action = new ParametersAction(values);
actions.add(action);
}

return innerPerform(project, delay, actions);
}
Expand All @@ -119,9 +157,6 @@ public JSONObject perform(final AbstractProject project, final StaplerRequest re
}
teamParameters.put(teamParamName, valueArray[0]);
}
else {
// TODO: implement when we add support for parameterized builds
}
}

if (teamParameters.containsKey(BUILD_REPOSITORY_PROVIDER) && "TfGit".equalsIgnoreCase(teamParameters.get(BUILD_REPOSITORY_PROVIDER))) {
Expand All @@ -130,49 +165,36 @@ public JSONObject perform(final AbstractProject project, final StaplerRequest re
final String repoUriString = teamParameters.get(BUILD_REPOSITORY_URI);
final URI repoUri = URI.create(repoUriString);
final String projectId = teamParameters.get(SYSTEM_TEAM_PROJECT);
final String repoId = teamParameters.get(BUILD_REPOSITORY_NAME);
final String commit = teamParameters.get(BUILD_SOURCE_VERSION);
final String pushedBy = teamParameters.get(BUILD_REQUESTED_FOR);
final Integer pullRequestId = determinePullRequestId(teamParameters);
final CommitParameterAction action;
if (pullRequestId != null) {
final PullRequestMergeCommitCreatedEventArgs args = new PullRequestMergeCommitCreatedEventArgs();
args.collectionUri = collectionUri;
args.repoUri = repoUri;
args.projectId = projectId;
args.commit = commit;
args.pushedBy = pushedBy;
args.pullRequestId = pullRequestId;
args.iterationId = -1 /* TODO: the pull request iteration ID is missing! */;
action = new PullRequestParameterAction(args);
}
else {
final GitCodePushedEventArgs args = new GitCodePushedEventArgs();
args.collectionUri = collectionUri;
args.repoUri = repoUri;
args.projectId = projectId;
args.commit = commit;
args.pushedBy = pushedBy;
action = new CommitParameterAction(args);
}
final GitCodePushedEventArgs args = new GitCodePushedEventArgs();
args.collectionUri = collectionUri;
args.repoUri = repoUri;
args.projectId = projectId;
args.repoId = repoId;
args.commit = commit;
args.pushedBy = pushedBy;
final CommitParameterAction action = new CommitParameterAction(args);
actions.add(action);
}

return innerPerform(project, delay, actions);
}

static Integer determinePullRequestId(final HashMap<String, String> teamParameters) {
Integer pullRequestId = null;
if (teamParameters.containsKey(BUILD_SOURCE_BRANCH)) {
final String sourceBranch = teamParameters.get(BUILD_SOURCE_BRANCH);
if (sourceBranch.startsWith(REFS_PULL_SLASH)) {
final String idSlashMerge = sourceBranch.substring(REFS_PULL_SLASH_LENGTH);
final int nextSlash = idSlashMerge.indexOf('/');
if (nextSlash > 0) {
final String pullRequestIdString = idSlashMerge.substring(0, nextSlash);
pullRequestId = Integer.valueOf(pullRequestIdString, 10);
//noinspection UnnecessaryLocalVariable
final Job<?, ?> job = project;
final ParametersDefinitionProperty pp = job.getProperty(ParametersDefinitionProperty.class);
if (pp != null) {
final List<ParameterDefinition> parameterDefinitions = pp.getParameterDefinitions();
final List<ParameterValue> values = new ArrayList<ParameterValue>();
for (final ParameterDefinition d : parameterDefinitions) {
final ParameterValue value = d.createValue(request);
if (value != null) {
values.add(value);
}
}
final ParametersAction action = new ParametersAction(values);
actions.add(action);
}
return pullRequestId;

return innerPerform(project, delay, actions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package hudson.plugins.tfs.model;

import hudson.plugins.tfs.util.MediaType;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;

public class BuildWithParametersCommand extends BuildCommand {

public static class Factory implements AbstractCommand.Factory {

@Override
public AbstractCommand create() {
return new BuildWithParametersCommand();
}

@Override
public String getSampleRequestPayload() {
final Class<? extends Factory> me = this.getClass();
final InputStream stream = me.getResourceAsStream("BuildWithParametersCommand.json");
try {
return IOUtils.toString(stream, MediaType.UTF_8);
}
catch (final IOException e) {
throw new Error(e);
}
finally {
IOUtils.closeQuietly(stream);
}
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/hudson/plugins/tfs/model/PingCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public String getSampleRequestPayload() {
}

@Override
public JSONObject perform(final AbstractProject project, final JSONObject requestPayload, final TimeDuration delay) {
public JSONObject perform(final AbstractProject project, final StaplerRequest request, final JSONObject requestPayload, final TimeDuration delay) {
return requestPayload;
}

Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/hudson/plugins/tfs/model/BuildCommand.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"team-parameters":
{
"collectionUri":"https://fabrikam-fiber-inc.visualstudio.com",
"repoUri":"https://fabrikam-fiber-inc.visualstudio.com/Personal/_git/olivida.tfs-plugin",
"projectId":"Personal",
"repoId":"olivida.tfs-plugin",
"commit":"6a23fc7afec31f0a14bade6544bed4f16492e6d2",
"pushedBy":"olivida"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"parameter":
[
{"name":"id","value":"123"},
{"name":"verbosity","value":"high"}
],
"team-parameters":
{
"collectionUri":"https://fabrikam-fiber-inc.visualstudio.com",
"repoUri":"https://fabrikam-fiber-inc.visualstudio.com/Personal/_git/olivida.tfs-plugin",
"projectId":"Personal",
"repoId":"olivida.tfs-plugin",
"commit":"6a23fc7afec31f0a14bade6544bed4f16492e6d2",
"pushedBy":"olivida"
}
}