diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java b/tfs/src/main/java/hudson/plugins/tfs/TeamBuildEndpoint.java index d0ca02cd9..431c47acf 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.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; @@ -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; @@ -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; @@ -53,6 +57,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 { @@ -129,6 +134,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 +152,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); } @@ -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 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; @@ -202,14 +280,13 @@ 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(); @@ -217,7 +294,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; }