Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/main/java/com/cloudbees/jenkins/GitHubPushTrigger.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.Stapler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -67,10 +68,56 @@
*/
public class GitHubPushTrigger extends Trigger<Job<?, ?>> implements GitHubTrigger {

private String ignoredUsers;

@DataBoundConstructor
public GitHubPushTrigger() {
}

/**
* Gets the newline-separated list of usernames to ignore.
*
* @return the ignored users list, or null if not configured
* @since FIXME
*/
public String getIgnoredUsers() {
return ignoredUsers;
}

/**
* Sets the newline-separated list of usernames whose push events should be ignored.
* The list will be trimmed of leading/trailing whitespace.
*
* @param ignoredUsers the newline-separated list of usernames to ignore
* @since FIXME
*/
@DataBoundSetter
public void setIgnoredUsers(String ignoredUsers) {
this.ignoredUsers = Util.fixEmptyAndTrim(ignoredUsers);
}

/**
* Checks if the given username should be ignored.
* Username comparison is case-insensitive.
*
* @param username the username to check
* @return true if the user should be ignored, false otherwise
* @since FIXME
*/
public boolean isUserIgnored(String username) {
if (isEmpty(ignoredUsers) || isEmpty(username)) {
return false;
}
String[] ignoredUserArray = ignoredUsers.split("[\\r\\n]+");
for (String ignoredUser : ignoredUserArray) {
String trimmedUser = ignoredUser.trim();
if (!trimmedUser.isEmpty() && trimmedUser.equalsIgnoreCase(username)) {
return true;
}
}
return false;
}

/**
* Called when a POST is made.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,18 @@ public void run() {
LOGGER.debug("Considering to poke {}", fullDisplayName);
if (GitHubRepositoryNameContributor.parseAssociatedNames(job)
.contains(changedRepository)) {
LOGGER.info("Poked {}", fullDisplayName);
trigger.onPost(GitHubTriggerEvent.create()
.withTimestamp(event.getTimestamp())
.withOrigin(event.getOrigin())
.withTriggeredByUser(pusherName)
.build()
);
if (trigger.isUserIgnored(pusherName)) {
LOGGER.debug("Skipped {} because pusher '{}' is in the ignored users list",
fullDisplayName, pusherName);
} else {
LOGGER.info("Poked {}", fullDisplayName);
trigger.onPost(GitHubTriggerEvent.create()
.withTimestamp(event.getTimestamp())
.withOrigin(event.getOrigin())
.withTriggeredByUser(pusherName)
.build()
);
}
} else {
LOGGER.debug("Skipped {} because it doesn't have a matching repository.",
fullDisplayName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ package com.cloudbees.jenkins.GitHubPushTrigger

import com.cloudbees.jenkins.GitHubPushTrigger

def f = namespace(lib.FormTagLib)

f.advanced() {
f.entry(title: _('Ignored Users'), field: 'ignoredUsers') {
f.textarea()
}
}

tr {
td(colspan: 4) {
def url = descriptor.getCheckMethod('hookRegistered').toCheckUrl()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
A newline-separated list of (case-insensitive) GitHub usernames whose push events should be ignored.
<br/>
When a push event is received from a user in this list, the trigger will not poll for changes.
<br/>
<br/>
Example:
<pre>renovate-bot
dependabot[bot]
some-automation-user</pre>
</div>
37 changes: 37 additions & 0 deletions src/test/java/com/cloudbees/jenkins/GitHubPushTriggerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.jenkinsci.plugins.github.webhook.subscriber.DefaultPushGHEventListenerTest.TRIGGERED_BY_USER_FROM_RESOURCE;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* @author lanwen (Merkushev Kirill)
Expand Down Expand Up @@ -97,4 +99,39 @@ void shouldReturnOkOnNoAnyProblem() throws Exception {
FormValidation validation = descriptor.doCheckHookRegistered(job);
assertThat("all ok", validation.kind, is(FormValidation.Kind.OK));
}

@Test
public void shouldIgnoreSingleUser() {
GitHubPushTrigger trigger = new GitHubPushTrigger();
trigger.setIgnoredUsers("ignored-user");

assertTrue("user should be ignored", trigger.isUserIgnored("ignored-user"));
assertTrue("user should be ignored", trigger.isUserIgnored("IGNORED-user"));
assertFalse("user should not be ignored", trigger.isUserIgnored("another-user"));
assertFalse("user should not be ignored", trigger.isUserIgnored(""));
assertFalse("user should not be ignored", trigger.isUserIgnored(null));
}

@Test
public void shouldIgnoreMultipleUsers() {
GitHubPushTrigger trigger = new GitHubPushTrigger();
trigger.setIgnoredUsers(" user1 \nUsEr2\nuser3");

assertTrue("user should be ignored", trigger.isUserIgnored("user1"));
assertTrue("user should be ignored", trigger.isUserIgnored("user2"));
assertTrue("user should be ignored", trigger.isUserIgnored("USER3"));
assertFalse("user should not be ignored", trigger.isUserIgnored("user4"));
assertFalse("user should not be ignored", trigger.isUserIgnored(""));
assertFalse("user should not be ignored", trigger.isUserIgnored(null));
}

@Test
public void shouldHandleEmptyIgnoredUsers() {
GitHubPushTrigger trigger = new GitHubPushTrigger();
trigger.setIgnoredUsers("");

assertFalse("user should not be ignored", trigger.isUserIgnored("user4"));
assertFalse("user should not be ignored", trigger.isUserIgnored(""));
assertFalse("user should not be ignored", trigger.isUserIgnored(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,73 @@ void shouldNotReceivePushHookOnWorkflowWithNoBuilds() throws Exception {

verify(trigger, never()).onPost(Mockito.any(GitHubTriggerEvent.class));
}

@Test
@WithoutJenkins
public void shouldNotTriggerWhenUserIsIgnored() {
GitHubPushTrigger trigger = mock(GitHubPushTrigger.class);
when(trigger.isUserIgnored(eq(TRIGGERED_BY_USER_FROM_RESOURCE))).thenReturn(true);

FreeStyleProject prj = mock(FreeStyleProject.class);
when(prj.getTriggers()).thenReturn(
Collections.singletonMap(new GitHubPushTrigger.DescriptorImpl(), trigger));
when(prj.getSCMs()).thenAnswer(unused -> Collections.singletonList(GIT_SCM_FROM_RESOURCE));
when(prj.getFullDisplayName()).thenReturn("test-job");

GHSubscriberEvent subscriberEvent =
new GHSubscriberEvent("shouldNotTriggerWhenUserIsIgnored", GHEvent.PUSH, classpath("payloads/push.json"));

Jenkins jenkins = mock(Jenkins.class);
when(jenkins.getAllItems(Item.class)).thenReturn(Collections.singletonList(prj));

ExtensionList<GitHubRepositoryNameContributor> extensionList = mock(ExtensionList.class);
List<GitHubRepositoryNameContributor> gitHubRepositoryNameContributorList =
Collections.singletonList(new GitHubRepositoryNameContributor.FromSCM());
when(extensionList.iterator()).thenReturn(gitHubRepositoryNameContributorList.iterator());
when(jenkins.getExtensionList(GitHubRepositoryNameContributor.class)).thenReturn(extensionList);

try (MockedStatic<Jenkins> mockedJenkins = mockStatic(Jenkins.class)) {
mockedJenkins.when(Jenkins::getInstance).thenReturn(jenkins);
new DefaultPushGHEventSubscriber().onEvent(subscriberEvent);
}

verify(trigger, never()).onPost(Mockito.any(GitHubTriggerEvent.class));
}

@Test
@WithoutJenkins
public void shouldTriggerWhenUserIsNotIgnored() {
GitHubPushTrigger trigger = mock(GitHubPushTrigger.class);
when(trigger.isUserIgnored(eq(TRIGGERED_BY_USER_FROM_RESOURCE))).thenReturn(false);

FreeStyleProject prj = mock(FreeStyleProject.class);
when(prj.getTriggers()).thenReturn(
Collections.singletonMap(new GitHubPushTrigger.DescriptorImpl(), trigger));
when(prj.getSCMs()).thenAnswer(unused -> Collections.singletonList(GIT_SCM_FROM_RESOURCE));
when(prj.getFullDisplayName()).thenReturn("test-job");

GHSubscriberEvent subscriberEvent =
new GHSubscriberEvent("shouldTriggerWhenUserIsNotIgnored", GHEvent.PUSH, classpath("payloads/push.json"));

Jenkins jenkins = mock(Jenkins.class);
when(jenkins.getAllItems(Item.class)).thenReturn(Collections.singletonList(prj));

ExtensionList<GitHubRepositoryNameContributor> extensionList = mock(ExtensionList.class);
List<GitHubRepositoryNameContributor> gitHubRepositoryNameContributorList =
Collections.singletonList(new GitHubRepositoryNameContributor.FromSCM());
when(extensionList.iterator()).thenReturn(gitHubRepositoryNameContributorList.iterator());
when(jenkins.getExtensionList(GitHubRepositoryNameContributor.class)).thenReturn(extensionList);

try (MockedStatic<Jenkins> mockedJenkins = mockStatic(Jenkins.class)) {
mockedJenkins.when(Jenkins::getInstance).thenReturn(jenkins);
new DefaultPushGHEventSubscriber().onEvent(subscriberEvent);
}

verify(trigger).onPost(eq(GitHubTriggerEvent.create()
.withTimestamp(subscriberEvent.getTimestamp())
.withOrigin("shouldTriggerWhenUserIsNotIgnored")
.withTriggeredByUser(TRIGGERED_BY_USER_FROM_RESOURCE)
.build()
));
}
}