Skip to content

Commit

Permalink
[FIXED JENKINS-41795] Add field to track event origins
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenc committed Feb 7, 2017
1 parent eda4686 commit 694ec6b
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 9 deletions.
10 changes: 5 additions & 5 deletions docs/implementation.adoc
Expand Up @@ -1163,13 +1163,13 @@ public class MySCMWebHook implements UnprotectedRootAction {
for (event : payload) {
switch (eventType) {
case HEAD:
SCMHeadEvent.fireNow(new MySCMHeadEvent(eventType, payload);
SCMHeadEvent.fireNow(new MySCMHeadEvent(eventType, payload, SCMEvent.originOf(req));
break;
case SOURCE:
SCMHeadEvent.fireNow(new MySCMSourceEvent(eventType, payload);
SCMHeadEvent.fireNow(new MySCMSourceEvent(eventType, payload, SCMEvent.originOf(req));
break;
case NAVIGATOR:
SCMHeadEvent.fireNow(new MySCMNavigatorEvent(eventType, payload);
SCMHeadEvent.fireNow(new MySCMNavigatorEvent(eventType, payload, SCMEvent.originOf(req));
break;
}
}
Expand Down Expand Up @@ -1291,8 +1291,8 @@ Because each event from the source control system has a 1:1 correspondance with
----
public class MyBrachSCMHeadEvent extends SCMHeadEvent<JsonNode> {
public MyBrachSCMHeadEvent(@NonNull Type type, JsonNode payload) {
super(type, payload);
public MyBrachSCMHeadEvent(@NonNull Type type, JsonNode payload, String origin) {
super(type, payload, origin);
}
@Override
Expand Down
129 changes: 125 additions & 4 deletions src/main/java/jenkins/scm/api/SCMEvent.java
Expand Up @@ -25,6 +25,7 @@

package jenkins.scm.api;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
import hudson.model.Cause;
Expand All @@ -33,16 +34,17 @@
import hudson.security.ACL;
import java.util.Date;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import jenkins.util.Timer;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest;

/**
* Base class for all events from a SCM system.
Expand Down Expand Up @@ -92,6 +94,10 @@ public abstract class SCMEvent<P> {
* An empty array of {@linkplain Cause}s.
*/
private static final Cause[] EMPTY_CAUSES = new Cause[0];
/**
* An unknown origin.
*/
private static final String ORIGIN_UNKNOWN = "?";
/**
* The event type.
*/
Expand All @@ -109,17 +115,41 @@ public abstract class SCMEvent<P> {
@NonNull
private final P payload;

/**
* The optional (provider specific) origin of the event.
*
* @since 2.0.3
*/
@CheckForNull
private final String origin;

/**
* Constructor to use when the timestamp is available from the external SCM.
*
* @param type the type of event.
* @param timestamp the timestamp from the external SCM (see {@link System#currentTimeMillis()} for start and units)
* @param payload the original provider specific payload.
* @deprecated use {@link #SCMEvent(Type, long, Object, String)}
*/
@Deprecated
public SCMEvent(@NonNull Type type, long timestamp, @NonNull P payload) {
this(type,timestamp,payload,null);
}

/**
* Constructor to use when the timestamp is available from the external SCM.
*
* @param type the type of event.
* @param timestamp the timestamp from the external SCM (see {@link System#currentTimeMillis()} for start and units)
* @param payload the original provider specific payload.
* @param origin the (optional) origin of the event, e.g. a hostname, etc
* @since 2.0.3
*/
public SCMEvent(@NonNull Type type, long timestamp, @NonNull P payload, @CheckForNull String origin) {
this.type = type;
this.timestamp = timestamp;
this.payload = payload;
this.origin = ORIGIN_UNKNOWN.equals(origin) ? null : origin;
}

/**
Expand All @@ -128,18 +158,33 @@ public SCMEvent(@NonNull Type type, long timestamp, @NonNull P payload) {
*
* @param type the type of event.
* @param payload the original provider specific payload.
* @deprecated use {@link #SCMEvent(Type, Object, String)}
*/
@Deprecated
public SCMEvent(@NonNull Type type, @NonNull P payload) {
this(type, System.currentTimeMillis(), payload);
}

/**
* Constructor to use when the timestamp is not available from the external SCM. The timestamp will be set
* using {@link System#currentTimeMillis()}
*
* @param type the type of event.
* @param payload the original provider specific payload.
* @param origin the (optional) origin of the event, e.g. a hostname, etc
* @since 2.0.3
*/
public SCMEvent(@NonNull Type type, @NonNull P payload, @CheckForNull String origin) {
this(type, System.currentTimeMillis(), payload, origin);
}

/**
* Copy constructor which may be required in cases where sub-classes need to implement {@code readResolve}
*
* @param copy the event to clone.
*/
protected SCMEvent(SCMEvent<P> copy) {
this(copy.getType(), copy.getTimestamp(), copy.getPayload());
this(copy.getType(), copy.getTimestamp(), copy.getPayload(), copy.origin);
}

/**
Expand Down Expand Up @@ -193,6 +238,17 @@ public P getPayload() {
return payload;
}

/**
* Gets the origin of the event, e.g. a hostname, etc.
*
* @return the origin of the event (or {@code "?"} if the origin is unknown)
* @since 2.0.3
*/
@NonNull
public String getOrigin() {
return StringUtils.defaultIfBlank(origin, ORIGIN_UNKNOWN);
}

/**
* If this event is being used to trigger a build, what - if any - {@linkplain Cause}s should be added to the
* triggered build.
Expand Down Expand Up @@ -247,13 +303,78 @@ public int hashCode() {
@Override
public String toString() {
return String.format(
"SCMEvent{type=%s, timestamp=%tc, payload=%s}",
"SCMEvent{type=%s, timestamp=%tc, payload=%s, origin=%s}",
type,
timestamp,
payload
payload,
StringUtils.defaultIfBlank(origin, ORIGIN_UNKNOWN)
);
}

/**
* Helper method to get the origin of an event from a {@link StaplerRequest}.
*
* @param req the {@link StaplerRequest}.
* @return the origin of the event.
*/
@CheckForNull
public static String originOf(@CheckForNull StaplerRequest req) {
if (req == null) {
return null;
}
String last = null;
StringBuilder result = new StringBuilder();
// TODO RFC 7239 support once standard is approved
String header = req.getHeader("X-Forwarded-For");
if (StringUtils.isNotBlank(header)) {
for (String remote : header.split("(,\\s*)")) {
if (StringUtils.isBlank(remote)) {
continue;
}
if (last != null) {
result.append(" => ");
}
last = StringUtils.trim(remote);
result.append(last);
}
}
String remoteHost = req.getRemoteHost();
String remoteAddr = req.getRemoteAddr();
if (last == null || (!(StringUtils.equals(last, remoteHost) || StringUtils.equals(last, remoteAddr)))) {
if (last != null) {
result.append(" => ");
}
if (!StringUtils.isBlank(remoteHost) && !remoteHost.equals(remoteAddr)) {
result.append(remoteHost);
result.append('/');
}
result.append(remoteAddr);
}
result.append(" => ");
String scheme = StringUtils.defaultIfBlank(req.getHeader("X-Forwarded-Proto"), req.getScheme());
result.append(scheme);
result.append("://");
result.append(req.getServerName());
String portStr = req.getHeader("X-Forwarded-Port");
int port;
if (portStr != null) {
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
port = req.getRemotePort();
}
} else {
port = req.getRemotePort();
}
if (!("http".equals(scheme) && port == 80 || "https".equals(scheme) && port == 443)) {
result.append(':');
result.append(port);
}
result.append(req.getRequestURI());
// omit query as may contain "secrets"
return result.toString();
}

/**
* The type of event.
*/
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/jenkins/scm/api/SCMHeadEvent.java
Expand Up @@ -25,6 +25,7 @@

package jenkins.scm.api;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Item;
import hudson.scm.SCM;
Expand Down Expand Up @@ -55,17 +56,33 @@ public abstract class SCMHeadEvent<P> extends SCMEvent<P> {
/**
* {@inheritDoc}
*/
@Deprecated
public SCMHeadEvent(@NonNull Type type, long timestamp, @NonNull P payload) {
super(type, timestamp, payload);
}

/**
* {@inheritDoc}
*/
public SCMHeadEvent(@NonNull Type type, long timestamp, @NonNull P payload, @CheckForNull String origin) {
super(type, timestamp, payload, origin);
}

/**
* {@inheritDoc}
*/
@Deprecated
public SCMHeadEvent(@NonNull Type type, @NonNull P payload) {
super(type, payload);
}

/**
* {@inheritDoc}
*/
public SCMHeadEvent(@NonNull Type type, @NonNull P payload, @CheckForNull String origin) {
super(type, payload, origin);
}

/**
* {@inheritDoc}
*/
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/jenkins/scm/api/SCMNavigatorEvent.java
Expand Up @@ -25,6 +25,7 @@

package jenkins.scm.api;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
import java.util.concurrent.TimeUnit;
Expand All @@ -48,17 +49,33 @@ public abstract class SCMNavigatorEvent<P> extends SCMEvent<P> {
/**
* {@inheritDoc}
*/
@Deprecated
public SCMNavigatorEvent(@NonNull Type type, long timestamp, @NonNull P payload) {
super(type, timestamp, payload);
}

/**
* {@inheritDoc}
*/
public SCMNavigatorEvent(@NonNull Type type, long timestamp, @NonNull P payload, @CheckForNull String origin) {
super(type, timestamp, payload, origin);
}

/**
* {@inheritDoc}
*/
@Deprecated
public SCMNavigatorEvent(@NonNull Type type, @NonNull P payload) {
super(type, payload);
}

/**
* {@inheritDoc}
*/
public SCMNavigatorEvent(@NonNull Type type, @NonNull P payload, @CheckForNull String origin) {
super(type, payload, origin);
}

/**
* {@inheritDoc}
*/
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/jenkins/scm/api/SCMSourceEvent.java
Expand Up @@ -25,6 +25,7 @@

package jenkins.scm.api;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
import hudson.model.Item;
Expand All @@ -49,17 +50,33 @@ public abstract class SCMSourceEvent<P> extends SCMEvent<P> {
/**
* {@inheritDoc}
*/
@Deprecated
public SCMSourceEvent(@NonNull Type type, long timestamp, @NonNull P payload) {
super(type, timestamp, payload);
}

/**
* {@inheritDoc}
*/
public SCMSourceEvent(@NonNull Type type, long timestamp, @NonNull P payload, @CheckForNull String origin) {
super(type, timestamp, payload, origin);
}

/**
* {@inheritDoc}
*/
@Deprecated
public SCMSourceEvent(@NonNull Type type, @NonNull P payload) {
super(type, payload);
}

/**
* {@inheritDoc}
*/
public SCMSourceEvent(@NonNull Type type, @NonNull P payload, @CheckForNull String origin) {
super(type, payload, origin);
}

/**
* {@inheritDoc}
*/
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/jenkins/scm/impl/mock/MockSCMHeadEvent.java
Expand Up @@ -25,6 +25,7 @@

package jenkins.scm.impl.mock;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.scm.SCM;
import java.util.Collections;
Expand All @@ -45,6 +46,7 @@ public class MockSCMHeadEvent extends SCMHeadEvent<String> {

private final String revision;

@Deprecated
public MockSCMHeadEvent(@NonNull Type type, MockSCMController controller,
String repository, String head, String revision) {
super(type, head);
Expand All @@ -54,6 +56,15 @@ public MockSCMHeadEvent(@NonNull Type type, MockSCMController controller,
this.revision = revision;
}

public MockSCMHeadEvent(@CheckForNull String origin, @NonNull Type type, MockSCMController controller,
String repository, String head, String revision) {
super(type, head, origin);
this.controller = controller;
this.repository = repository;
this.head = head;
this.revision = revision;
}

@Override
public boolean isMatch(@NonNull SCMNavigator navigator) {
return navigator instanceof MockSCMNavigator
Expand Down

0 comments on commit 694ec6b

Please sign in to comment.