package hudson.plugins.tfs.model;
import com.fasterxml.jackson.databind.ObjectMapper;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Item;
import hudson.model.Job;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.GitStatus;
import hudson.plugins.git.extensions.impl.IgnoreNotifyCommit;
import hudson.plugins.tfs.TeamEventsEndpoint;
import hudson.plugins.tfs.TeamGlobalStatusAction;
import hudson.plugins.tfs.TeamHookCause;
import hudson.plugins.tfs.TeamPluginGlobalConfig;
import hudson.plugins.tfs.TeamPushTrigger;
import hudson.plugins.tfs.model.servicehooks.Event;
import hudson.plugins.tfs.util.ActionHelper;
import hudson.plugins.tfs.util.UriHelper;
import hudson.scm.SCM;
import hudson.security.ACL;
import hudson.triggers.SCMTrigger;
import jenkins.model.Jenkins;
import jenkins.triggers.SCMTriggerItem;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public abstract class AbstractHookEvent {
private static final Logger LOGGER = Logger.getLogger(AbstractHookEvent.class.getName());
public interface Factory {
AbstractHookEvent create();
String getSampleRequestPayload();
}
/**
* Actually do the work of the hook event, using the supplied
* {@code mapper} to convert the event's data from the supplied {@code serviceHookEvent}
* and returning the output as a {@link JSONObject}.
*
* @param mapper an {@link ObjectMapper} instance to use to convert the {@link Event#resource}
* @param serviceHookEvent an {@link Event} that represents the request payload
* and from which the {@link Event#resource} can be obtained
* @param message a simple description of the event
* @param detailedMessage a longer description of the event, with some details
*
* @return a {@link JSONObject} representing the hook event's output
*/
public abstract JSONObject perform(final ObjectMapper mapper, final Event serviceHookEvent, final String message, final String detailedMessage);
static JSONObject fromResponseContributors(final List<GitStatus.ResponseContributor> contributors) {
final JSONObject result = new JSONObject();
final JSONArray messages = new JSONArray();
for (final GitStatus.ResponseContributor contributor : contributors) {
final StringWriter stringWriter = new StringWriter();
final PrintWriter printWriter = new PrintWriter(stringWriter);
try {
contributor.writeBody(printWriter);
printWriter.flush();
}
finally {
IOUtils.closeQuietly(printWriter);
}
final String contributorMessage = stringWriter.toString();
messages.add(contributorMessage);
}
result.put("messages", messages);
return result;
}
// TODO: it would be easiest if pollOrQueueFromEvent built a JSONObject directly
List<GitStatus.ResponseContributor> pollOrQueueFromEvent(final GitCodePushedEventArgs gitCodePushedEventArgs, final List<Action> actions, final boolean bypassPolling) {
List<GitStatus.ResponseContributor> result = new ArrayList<GitStatus.ResponseContributor>();
final String commit = gitCodePushedEventArgs.commit;
if (commit == null) {
result.add(new GitStatus.MessageResponseContributor("No commits were pushed, skipping further event processing."));
return result;
}
final URIish uri = gitCodePushedEventArgs.getRepoURIish();
TeamGlobalStatusAction.addIfApplicable(actions);
// run in high privilege to see all the projects anonymous users don't see.
// this is safe because when we actually schedule a build, it's a build that can
// happen at some random time anyway.
SecurityContext old = ACL.impersonate(ACL.SYSTEM);
try {
boolean scmFound = false;
final Jenkins jenkins = Jenkins.getInstance();
if (jenkins == null) {
LOGGER.severe("Jenkins.getInstance() is null");
return result;
}
int totalRepositoryMatches = 0;
for (final Item project : Jenkins.getInstance().getAllItems()) {
final SCMTriggerItem scmTriggerItem = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(project);
if (scmTriggerItem == null) {
continue;
}
for (final SCM scm : scmTriggerItem.getSCMs()) {
if (!(scm instanceof GitSCM)) {
continue;
}
final GitSCM git = (GitSCM) scm;
scmFound = true;
for (final RemoteConfig repository : git.getRepositories()) {
boolean repositoryMatches = false;
for (final URIish remoteURL : repository.getURIs()) {
if (UriHelper.areSameGitRepo(uri, remoteURL)) {
repositoryMatches = true;
totalRepositoryMatches++;
break;
}
}
if (!repositoryMatches || git.getExtensions().get(IgnoreNotifyCommit.class)!=null) {
continue;
}
if (!(project instanceof AbstractProject && ((AbstractProject) project).isDisabled())) {
if (project instanceof Job) {
// TODO: Add default parameters defined in the job
final Job job = (Job) project;
final int quietPeriod = scmTriggerItem.getQuietPeriod();
boolean triggered = false;
if (!triggered) {
final TeamPluginGlobalConfig config = TeamPluginGlobalConfig.get();
if (config.isEnableTeamPushTriggerForAllJobs()) {
triggered = true;
final SCMTrigger scmTrigger = TeamEventsEndpoint.findTrigger(job, SCMTrigger.class);
if (scmTrigger != null && scmTrigger.isIgnorePostCommitHooks()) {
// job has explicitly opted out of hooks
triggered = false;
}
}
if (triggered) {
final TeamPushTrigger trigger = new TeamPushTrigger(job);
trigger.execute(gitCodePushedEventArgs, actions, bypassPolling);
final GitStatus.ResponseContributor response;
if (bypassPolling) {
response = new TeamEventsEndpoint.ScheduledResponseContributor(project);
}
else {
response = new TeamEventsEndpoint.PollingScheduledResponseContributor(project);
}
result.add(response);
}
}
if (!triggered) {
final SCMTrigger scmTrigger = TeamEventsEndpoint.findTrigger(job, SCMTrigger.class);
if (scmTrigger != null && !scmTrigger.isIgnorePostCommitHooks()) {
// queue build without first polling
final Cause cause = new TeamHookCause(commit);
final CauseAction causeAction = new CauseAction(cause);
final Action[] actionArray = ActionHelper.create(actions, causeAction);
scmTriggerItem.scheduleBuild2(quietPeriod, actionArray);
result.add(new TeamEventsEndpoint.ScheduledResponseContributor(project));
triggered = true;
}
}
if (!triggered) {
final TeamPushTrigger pushTrigger = TeamEventsEndpoint.findTrigger(job, TeamPushTrigger.class);
if (pushTrigger != null) {
pushTrigger.execute(gitCodePushedEventArgs, actions, bypassPolling);
final GitStatus.ResponseContributor response;
if (bypassPolling) {
response = new TeamEventsEndpoint.ScheduledResponseContributor(project);
}
else {
response = new TeamEventsEndpoint.PollingScheduledResponseContributor(project);
}
result.add(response);
triggered = true;
}
}
if (triggered) {
break;
}
}
}
}
}
}
if (!scmFound) {
result.add(new GitStatus.MessageResponseContributor("No Git jobs found"));
}
else if (totalRepositoryMatches == 0) {
final String template = "No Git jobs matched the remote URL '%s' requested by an event.";
final String message = String.format(template, uri);
LOGGER.warning(message);
}
return result;
}
finally {
SecurityContextHolder.setContext(old);
}
}
}