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

Support WorkflowJob (Pipeline) jobs #149

Merged
merged 3 commits into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 92 additions & 13 deletions tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.BuildAuthorizationToken;
import hudson.model.BuildableItem;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.UnprotectedRootAction;
import hudson.plugins.tfs.model.AbstractCommand;
Expand All @@ -14,6 +15,7 @@
import hudson.plugins.tfs.util.EndpointHelper;
import hudson.plugins.tfs.util.MediaType;
import jenkins.model.Jenkins;
import jenkins.model.ParameterizedJobMixIn;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
Expand All @@ -32,6 +34,8 @@
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
Expand All @@ -53,6 +57,7 @@ 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 PARAMETER = "parameter";
public static final String BUILD_SOURCE_BRANCH = "Build.SourceBranch";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if Build.SourceBranch is available in VSTS Release Management scenarios? If not, can we handle its absence gracefully?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call. Made changes to get the branch from:

  1. jobName input.
  2. Build.SourceBranch variable

Throw if none of them are available.

static final String URL_PREFIX = "/" + URL_NAME + "/";

static {
Expand Down Expand Up @@ -129,6 +134,7 @@ public HttpResponse doIndex(final HttpServletRequest request) throws IOException
IOUtils.closeQuietly(stream);
}
}

static String describeCommands(final Map<String, AbstractCommand.Factory> commandMap, final String urlName) {
final String newLine = System.getProperty("line.separator");
final StringBuilder sb = new StringBuilder();
Expand All @@ -146,11 +152,11 @@ static String describeCommands(final Map<String, AbstractCommand.Factory> comman
return sb.toString();
}


@SuppressWarnings("deprecation" /* We want to do exactly what Jenkins does */)
void checkPermission(final AbstractProject project, final StaplerRequest req, final StaplerResponse rsp) throws IOException {
Job<?, ?> job = project;
final BuildAuthorizationToken authToken = project.getAuthToken();
void checkPermission(final Job job, final ParameterizedJobMixIn.ParameterizedJob jobMixin,
final StaplerRequest req, final StaplerResponse rsp) throws IOException {

final BuildAuthorizationToken authToken = jobMixin.getAuthToken();
hudson.model.BuildAuthorizationToken.checkPermission(job, authToken, req, rsp);
}

Expand Down Expand Up @@ -185,6 +191,78 @@ void dispatch(final StaplerRequest req, final StaplerResponse rsp, final TimeDur
}
}

/**
* If we are calling this method, it means we didn't find any job or project with jobName. Assuming we are building
* multibranch pipeline projects in this case.
*
* We will try to determine the branch name in the following sequence:
* 1. Check if the jobName is composed from ${multibranch_pipeline}/${branch_name}
* 2. Check if the payload has BuildSource variable defined (for PR builds)
*
* If we can't determine the branch name, throw.
*/
private String getBranch(final String jobName, final StaplerRequest req) {
String sourceBranch = null;

if (jobName.indexOf('/') > 0) {
sourceBranch = jobName.substring(jobName.indexOf('/') + 1);
} else {
final String json = req.getParameter("json");
final JSONObject formData = JSONObject.fromObject(json);
final TeamBuildPayload payload = EndpointHelper.MAPPER.convertValue(formData, TeamBuildPayload.class);

sourceBranch = payload.BuildVariables.get(BUILD_SOURCE_BRANCH);
}

if (sourceBranch == null || sourceBranch.trim().isEmpty()) {
throw new IllegalArgumentException("Could not find branch from job name. If building a multibranch"
+ "pipeline job, the job name should be in the format of '${multibranch pipeline name}/${branch}.'");
}

try {
return URLEncoder.encode(sourceBranch.replace("refs/heads/", ""), "UTF-8");
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException("Failed to encode branch: " + sourceBranch, e);
}
}

private String getJobNameFromNestedFolder(final String jobName) {
final int idx = jobName.indexOf('/');
if (idx > 0) {
return jobName.substring(0, idx);
}

return jobName;
}

private Job getJob(final String jobName, final StaplerRequest req) {
final Jenkins jenkins = Jenkins.getInstance();

Job job = jenkins.getItemByFullName(jobName, Job.class);

if (job == null) {
final Item item = jenkins.getItemByFullName(getJobNameFromNestedFolder(jobName));

if (item != null) {
final Collection<? extends Job> allJobs = item.getAllJobs();
final String sourceBranch = getBranch(jobName, req);

for (final Job j : allJobs) {
if (j.getName().equals(sourceBranch)) {
job = j;
break;
}
}
}
}

if (job == null) {
throw new IllegalArgumentException("Job: " + jobName + " not found");
}

return job;
}

private JSONObject innerDispatch(final StaplerRequest req, final StaplerResponse rsp, final TimeDuration delay) throws IOException, ServletException {
commandName = null;
jobName = null;
Expand All @@ -202,22 +280,23 @@ private JSONObject innerDispatch(final StaplerRequest req, final StaplerResponse
throw new IllegalArgumentException("Command not implemented");
}

final Jenkins jenkins = Jenkins.getInstance();
final AbstractProject project = jenkins.getItemByFullName(jobName, AbstractProject.class);
if (project == null) {
throw new IllegalArgumentException("Project not found");
}
checkPermission(project, req, rsp);
final Job job = getJob(jobName, req);

final ParameterizedJobMixIn.ParameterizedJob jobMixin = (ParameterizedJobMixIn.ParameterizedJob) job;

checkPermission(job, jobMixin, req, rsp);
final TimeDuration actualDelay =
delay == null ? new TimeDuration(project.getQuietPeriod()) : delay;
delay == null ? new TimeDuration(jobMixin.getQuietPeriod()) : delay;

final AbstractCommand.Factory factory = COMMAND_FACTORIES_BY_NAME.get(commandName);
final AbstractCommand command = factory.create();
final JSONObject response;
final JSONObject formData = req.getSubmittedForm();
final ObjectMapper mapper = EndpointHelper.MAPPER;
final TeamBuildPayload teamBuildPayload = mapper.convertValue(formData, TeamBuildPayload.class);
response = command.perform(project, req, formData, mapper, teamBuildPayload, actualDelay);

final BuildableItem buildable = (BuildableItem) job;
response = command.perform(job, buildable, req, formData, mapper, teamBuildPayload, actualDelay);
return response;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package hudson.plugins.tfs.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import hudson.model.AbstractProject;
import hudson.model.BuildableItem;
import hudson.model.Job;
import hudson.plugins.tfs.model.servicehooks.Event;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
Expand All @@ -21,7 +22,8 @@ public interface Factory {
* Actually do the work of the command, using the supplied {@code requestPayload} and
* {@code teamBuildPayload}, then returning the output as a {@link JSONObject}.
*
* @param project an {@link AbstractProject to operate on}
* @param job an {@link Job to operate on}
* @param buildableItem an {@link BuildableItem to operate on}
* @param request a {@link StaplerRequest} to help build parameter values
* @param requestPayload a {@link JSONObject} representing the command's input
* @param mapper an {@link ObjectMapper} instance to use to convert the {@link Event#resource}
Expand All @@ -30,6 +32,6 @@ public interface Factory {
*
* @return a {@link JSONObject} representing the hook event's output
*/
public abstract JSONObject perform(final AbstractProject project, final StaplerRequest request, final JSONObject requestPayload, final ObjectMapper mapper, final TeamBuildPayload teamBuildPayload, final TimeDuration delay);
public abstract JSONObject perform(final Job<?, ?> job, final BuildableItem buildableItem, final StaplerRequest request, final JSONObject requestPayload, final ObjectMapper mapper, final TeamBuildPayload teamBuildPayload, final TimeDuration delay);

}
13 changes: 7 additions & 6 deletions tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.teamfoundation.sourcecontrol.webapi.model.GitPush;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildableItem;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Job;
Expand Down Expand Up @@ -82,15 +82,15 @@ public String getSampleRequestPayload() {
}
}

protected JSONObject innerPerform(final AbstractProject project, final TimeDuration delay, final List<Action> extraActions) {
protected JSONObject innerPerform(final BuildableItem buildableItem, final TimeDuration delay, final List<Action> extraActions) {
final JSONObject result = new JSONObject();

final Jenkins jenkins = Jenkins.getInstance();
final Queue queue = jenkins.getQueue();
final Cause cause = new Cause.UserIdCause();
final CauseAction causeAction = new CauseAction(cause);
final Action[] actionArray = ActionHelper.create(extraActions, causeAction);
final ScheduleResult scheduleResult = queue.schedule2(project, delay.getTime(), actionArray);
final ScheduleResult scheduleResult = queue.schedule2(buildableItem, delay.getTime(), actionArray);
final Queue.Item item = scheduleResult.getItem();
if (item != null) {
result.put("created", jenkins.getRootUrl() + item.getUrl());
Expand All @@ -99,7 +99,9 @@ protected JSONObject innerPerform(final AbstractProject project, final TimeDurat
}

@Override
public JSONObject perform(final AbstractProject project, final StaplerRequest req, final JSONObject requestPayload, final ObjectMapper mapper, final TeamBuildPayload teamBuildPayload, final TimeDuration delay) {
public JSONObject perform(final Job<?, ?> job, final BuildableItem buildableItem, final StaplerRequest req,
final JSONObject requestPayload, final ObjectMapper mapper,
final TeamBuildPayload teamBuildPayload, final TimeDuration delay) {

// These values are for optional parameters of the same name, for the git.pullrequest.merged event
String commitId = null;
Expand Down Expand Up @@ -138,7 +140,6 @@ else if ("git.pullrequest.merged".equals(eventType)) {
}

//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>();
Expand Down Expand Up @@ -199,7 +200,7 @@ else if (name.equals(PULL_REQUEST_ID) && pullRequestId != null & d instanceof Si
actions.add(action);
}

return innerPerform(project, delay, actions);
return innerPerform(buildableItem, delay, actions);
}

static void contributeTeamBuildParameterActions(final Map<String, String> teamBuildParameters, final List<Action> actions) {
Expand Down
7 changes: 5 additions & 2 deletions tfs/src/main/java/hudson/plugins/tfs/model/PingCommand.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package hudson.plugins.tfs.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import hudson.model.AbstractProject;
import hudson.model.BuildableItem;
import hudson.model.Job;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
Expand All @@ -27,7 +28,9 @@ public String getSampleRequestPayload() {
}

@Override
public JSONObject perform(final AbstractProject project, final StaplerRequest request, final JSONObject requestPayload, final ObjectMapper mapper, final TeamBuildPayload teamBuildPayload, final TimeDuration delay) {
public JSONObject perform(final Job project, final BuildableItem buildableItem, final StaplerRequest request,
final JSONObject requestPayload, final ObjectMapper mapper,
final TeamBuildPayload teamBuildPayload, final TimeDuration delay) {
return requestPayload;
}

Expand Down