package hudson.plugins.tfs.model; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.teamfoundation.sourcecontrol.webapi.model.GitPush; import hudson.model.Action; import hudson.model.BuildableItem; import hudson.model.Cause; import hudson.model.CauseAction; import hudson.model.Job; import hudson.model.ParameterDefinition; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.ParametersDefinitionProperty; import hudson.model.Queue; import hudson.model.SimpleParameterDefinition; import hudson.model.queue.ScheduleResult; import hudson.plugins.tfs.CommitParameterAction; import hudson.plugins.tfs.PullRequestParameterAction; import hudson.plugins.tfs.TeamBuildDetailsAction; import hudson.plugins.tfs.TeamBuildEndpoint; import hudson.plugins.tfs.TeamGlobalStatusAction; import hudson.plugins.tfs.TeamPullRequestMergedDetailsAction; import hudson.plugins.tfs.UnsupportedIntegrationAction; import hudson.plugins.tfs.model.servicehooks.Event; import hudson.plugins.tfs.util.ActionHelper; 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.apache.commons.lang.StringUtils; import org.kohsuke.stapler.StaplerRequest; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; public class BuildCommand extends AbstractCommand { private static final Logger LOGGER = Logger.getLogger(BuildCommand.class.getName()); private static final String BUILD_REPOSITORY_PROVIDER = "Build.Repository.Provider"; 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"; private static final String SYSTEM_TEAM_FOUNDATION_COLLECTION_URI = "System.TeamFoundationCollectionUri"; private static final String COMMIT_ID = "commitId"; private static final String PULL_REQUEST_ID = "pullRequestId"; private static final String UNSUPPORTED_TEMPLATE = "The rich integration with TFS/Team Services is not supported. Reason: %s"; public static String formatUnsupportedReason(final String reason) { return String.format(UNSUPPORTED_TEMPLATE, reason); } public static class Factory implements AbstractCommand.Factory { @Override public AbstractCommand create() { return new BuildCommand(); } @Override public String getSampleRequestPayload() { 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); } } } 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(buildableItem, delay.getTime(), actionArray); final Queue.Item item = scheduleResult.getItem(); if (item != null) { result.put("created", jenkins.getRootUrl() + item.getUrl()); } return result; } @Override 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; String pullRequestId = null; final List<Action> actions = new ArrayList<Action>(); if (teamBuildPayload.BuildVariables != null) { contributeTeamBuildParameterActions(teamBuildPayload.BuildVariables, actions); } else if (teamBuildPayload.ServiceHookEvent != null) { final Event event = teamBuildPayload.ServiceHookEvent; final String eventType = event.getEventType(); final Object resource = event.getResource(); if ("git.push".equals(eventType)) { final GitPush gitPush = mapper.convertValue(resource, GitPush.class); final GitCodePushedEventArgs args = GitPushEvent.decodeGitPush(gitPush, event); final Action action = new CommitParameterAction(args); actions.add(action); TeamGlobalStatusAction.addIfApplicable(actions); } else if ("git.pullrequest.merged".equals(eventType)) { final GitPullRequestEx gitPullRequest = mapper.convertValue(resource, GitPullRequestEx.class); final PullRequestMergeCommitCreatedEventArgs args = GitPullRequestMergedEvent.decodeGitPullRequest(gitPullRequest, event); // record the values for the special optional parameters commitId = args.commit; pullRequestId = Integer.toString(args.pullRequestId, 10); final Action action = new PullRequestParameterAction(args); actions.add(action); final String message = event.getMessage().getText(); final String detailedMessage = event.getDetailedMessage().getText(); final Action teamPullRequestMergedDetailsAction = new TeamPullRequestMergedDetailsAction(gitPullRequest, message, detailedMessage, args.collectionUri.toString()); actions.add(teamPullRequestMergedDetailsAction); TeamGlobalStatusAction.addIfApplicable(actions); } } //noinspection UnnecessaryLocalVariable 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; // commitId and pullRequestId are special and override any user-provided value // when the team-event's eventType was "git.pullrequest.merged" if (name.equals(COMMIT_ID) && commitId != null && d instanceof SimpleParameterDefinition) { final SimpleParameterDefinition spd = (SimpleParameterDefinition) d; parameterValue = spd.createValue(commitId); // erase value to avoid adding it a second time commitId = null; } else if (name.equals(PULL_REQUEST_ID) && pullRequestId != null & d instanceof SimpleParameterDefinition) { final SimpleParameterDefinition spd = (SimpleParameterDefinition) d; parameterValue = spd.createValue(pullRequestId); // erase value to avoid adding it a second time pullRequestId = null; } else { parameterValue = d.createValue(req, jo); } if (parameterValue != null) { values.add(parameterValue); } else { throw new IllegalArgumentException("Cannot retrieve the parameter value: " + name); } } // typical case: set optional "git.pullrequest.merged" parameters if (commitId != null) { final ParameterDefinition d = pp.getParameterDefinition(COMMIT_ID); if (d != null && d instanceof SimpleParameterDefinition) { final SimpleParameterDefinition spd = (SimpleParameterDefinition) d; final ParameterValue parameterValue = spd.createValue(commitId); values.add(parameterValue); } } if (pullRequestId != null) { final ParameterDefinition d = pp.getParameterDefinition(PULL_REQUEST_ID); if (d != null && d instanceof SimpleParameterDefinition) { final SimpleParameterDefinition spd = (SimpleParameterDefinition) d; final ParameterValue parameterValue = spd.createValue(pullRequestId); values.add(parameterValue); } } final ParametersAction action = new ParametersAction(values); actions.add(action); } return innerPerform(buildableItem, delay, actions); } static void contributeTeamBuildParameterActions(final Map<String, String> teamBuildParameters, final List<Action> actions) { final Action teamBuildDetails = new TeamBuildDetailsAction(teamBuildParameters); actions.add(teamBuildDetails); if (teamBuildParameters.containsKey(BUILD_REPOSITORY_PROVIDER)) { final String provider = teamBuildParameters.get(BUILD_REPOSITORY_PROVIDER); final boolean isTeamGit = "TfGit".equalsIgnoreCase(provider) || "TfsGit".equalsIgnoreCase(provider); if (isTeamGit) { // "Build.Repository.Uri" is null/whitespace/empty when the 'Jenkins Queue Job' task runs in TFS Release Management. // In this case, do not reference a CommitParameterAction in the actions reported as unsupported. final String repoUriString = teamBuildParameters.get(BUILD_REPOSITORY_URI); if (StringUtils.isNotBlank(repoUriString)) { final URI repoUri = URI.create(repoUriString); final String collectionUriString = teamBuildParameters.get(SYSTEM_TEAM_FOUNDATION_COLLECTION_URI); final URI collectionUri = URI.create(collectionUriString); final String projectId = teamBuildParameters.get(SYSTEM_TEAM_PROJECT); final String repoId = teamBuildParameters.get(BUILD_REPOSITORY_NAME); final String commit = teamBuildParameters.get(BUILD_SOURCE_VERSION); final String pushedBy = teamBuildParameters.get(BUILD_REQUESTED_FOR); 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); } UnsupportedIntegrationAction.addToBuild(actions, "Posting build status is not supported for builds triggered by the 'Jenkins Queue Job' task."); } else { final String reason = String.format( "The '%s' build variable has a value of '%s', which is not supported.", BUILD_REPOSITORY_PROVIDER, provider); UnsupportedIntegrationAction.addToBuild(actions, reason); LOGGER.warning(formatUnsupportedReason(reason)); } } else { final String reason = String.format( "There was no value provided for the '%s' build variable.", BUILD_REPOSITORY_PROVIDER); UnsupportedIntegrationAction.addToBuild(actions, reason); LOGGER.warning(formatUnsupportedReason(reason)); } } }