From aa6512f361c0a541d5928f8287a5cf7ebfde8448 Mon Sep 17 00:00:00 2001 From: "Yang Cao (VSNC)" Date: Tue, 14 Feb 2017 12:58:17 -0500 Subject: [PATCH 1/3] Pass project reference down with sub interfaces This change is to support WorkflowJob (Pipeline) job types. The WorkflowJob implements BuildableItem and ParameterizedJobMixin.ParameterizedJob interface, but is not an AbstractProject type. --- .../hudson/plugins/tfs/TeamBuildEndpoint.java | 30 ++++++++++++------- .../plugins/tfs/model/AbstractCommand.java | 8 +++-- .../plugins/tfs/model/BuildCommand.java | 13 ++++---- .../hudson/plugins/tfs/model/PingCommand.java | 7 +++-- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java index d0ca02cd9..27dc333d8 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import hudson.Extension; -import hudson.model.AbstractProject; +import hudson.model.BuildableItem; import hudson.model.BuildAuthorizationToken; import hudson.model.Job; import hudson.model.UnprotectedRootAction; @@ -14,6 +14,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; @@ -129,6 +130,7 @@ public HttpResponse doIndex(final HttpServletRequest request) throws IOException IOUtils.closeQuietly(stream); } } + static String describeCommands(final Map commandMap, final String urlName) { final String newLine = System.getProperty("line.separator"); final StringBuilder sb = new StringBuilder(); @@ -146,11 +148,11 @@ static String describeCommands(final Map 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); } @@ -203,13 +205,17 @@ private JSONObject innerDispatch(final StaplerRequest req, final StaplerResponse } final Jenkins jenkins = Jenkins.getInstance(); - final AbstractProject project = jenkins.getItemByFullName(jobName, AbstractProject.class); - if (project == null) { - throw new IllegalArgumentException("Project not found"); + + final Job job = jenkins.getItemByFullName(jobName, Job.class); + if (job == null) { + throw new IllegalArgumentException("Job not found"); } - checkPermission(project, req, rsp); + + 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(); @@ -217,7 +223,9 @@ private JSONObject innerDispatch(final StaplerRequest req, final StaplerResponse 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; } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/AbstractCommand.java b/tfs/src/main/java/hudson/plugins/tfs/model/AbstractCommand.java index e2e4c5a20..7aaa60426 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/AbstractCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/AbstractCommand.java @@ -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; @@ -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} @@ -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); } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java b/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java index 8d21a41e5..56d76ec03 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java @@ -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; @@ -82,7 +82,7 @@ public String getSampleRequestPayload() { } } - protected JSONObject innerPerform(final AbstractProject project, final TimeDuration delay, final List extraActions) { + protected JSONObject innerPerform(final BuildableItem buildableItem, final TimeDuration delay, final List extraActions) { final JSONObject result = new JSONObject(); final Jenkins jenkins = Jenkins.getInstance(); @@ -90,7 +90,7 @@ protected JSONObject innerPerform(final AbstractProject project, final TimeDurat 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()); @@ -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; @@ -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 values = new ArrayList(); @@ -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 teamBuildParameters, final List actions) { diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/PingCommand.java b/tfs/src/main/java/hudson/plugins/tfs/model/PingCommand.java index 561a1a4be..75f7b6c8f 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/PingCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/PingCommand.java @@ -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; @@ -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; } From a77b9576a8f8ae7e36f165f44374deac2a758263 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 16 Feb 2017 13:29:03 -0500 Subject: [PATCH 2/3] Support multibranch Pipeline project --- .../hudson/plugins/tfs/TeamBuildEndpoint.java | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java index 27dc333d8..9dd9b3065 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java @@ -2,8 +2,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import hudson.Extension; -import hudson.model.BuildableItem; 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; @@ -33,6 +34,7 @@ import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.TreeMap; @@ -54,6 +56,7 @@ public class TeamBuildEndpoint implements UnprotectedRootAction { private static final Map 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"; static final String URL_PREFIX = "/" + URL_NAME + "/"; static { @@ -187,6 +190,41 @@ void dispatch(final StaplerRequest req, final StaplerResponse rsp, final TimeDur } } + private String getBranch(final StaplerRequest req) { + final String json = req.getParameter("json"); + final JSONObject formData = JSONObject.fromObject(json); + final TeamBuildPayload payload = EndpointHelper.MAPPER.convertValue(formData, TeamBuildPayload.class); + + final String sourceBranch = payload.BuildVariables.get(BUILD_SOURCE_BRANCH); + + return sourceBranch.replace("refs/heads/", ""); + } + + 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(jobName); + final Collection allJobs = item.getAllJobs(); + final String sourceBranch = getBranch(req); + + for (final Job j : allJobs) { + if (j.getName().equals(sourceBranch)) { + job = j; + break; + } + } + } + + if (job == null) { + throw new IllegalArgumentException("Job not found"); + } + + return job; + } + private JSONObject innerDispatch(final StaplerRequest req, final StaplerResponse rsp, final TimeDuration delay) throws IOException, ServletException { commandName = null; jobName = null; @@ -204,12 +242,7 @@ private JSONObject innerDispatch(final StaplerRequest req, final StaplerResponse throw new IllegalArgumentException("Command not implemented"); } - final Jenkins jenkins = Jenkins.getInstance(); - - final Job job = jenkins.getItemByFullName(jobName, Job.class); - if (job == null) { - throw new IllegalArgumentException("Job not found"); - } + final Job job = getJob(jobName, req); final ParameterizedJobMixIn.ParameterizedJob jobMixin = (ParameterizedJobMixIn.ParameterizedJob) job; From 9fe3c7ca8820daaae151e51e0e6d202a1052f2eb Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 16 Feb 2017 14:59:36 -0500 Subject: [PATCH 3/3] Get branch name for multibranch pipeline job We will try parse the branch name in this order: 1. Parse the branch name if job name is in format of ${multibranch pipeline}/${branch} 2. Read Build.SourceBranch variable --- .../hudson/plugins/tfs/TeamBuildEndpoint.java | 68 +++++++++++++++---- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java index 9dd9b3065..431c47acf 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java @@ -34,6 +34,7 @@ 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; @@ -190,14 +191,48 @@ void dispatch(final StaplerRequest req, final StaplerResponse rsp, final TimeDur } } - private String getBranch(final StaplerRequest req) { - final String json = req.getParameter("json"); - final JSONObject formData = JSONObject.fromObject(json); - final TeamBuildPayload payload = EndpointHelper.MAPPER.convertValue(formData, TeamBuildPayload.class); + /** + * 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); + } + } - final String sourceBranch = payload.BuildVariables.get(BUILD_SOURCE_BRANCH); + private String getJobNameFromNestedFolder(final String jobName) { + final int idx = jobName.indexOf('/'); + if (idx > 0) { + return jobName.substring(0, idx); + } - return sourceBranch.replace("refs/heads/", ""); + return jobName; } private Job getJob(final String jobName, final StaplerRequest req) { @@ -206,20 +241,23 @@ private Job getJob(final String jobName, final StaplerRequest req) { Job job = jenkins.getItemByFullName(jobName, Job.class); if (job == null) { - final Item item = jenkins.getItemByFullName(jobName); - final Collection allJobs = item.getAllJobs(); - final String sourceBranch = getBranch(req); - - for (final Job j : allJobs) { - if (j.getName().equals(sourceBranch)) { - job = j; - break; + final Item item = jenkins.getItemByFullName(getJobNameFromNestedFolder(jobName)); + + if (item != null) { + final Collection 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 not found"); + throw new IllegalArgumentException("Job: " + jobName + " not found"); } return job;