提交 5736ee19 编写于 作者: S Stephen Connolly 提交者: GitHub

Merge pull request #2418 from stephenc/jenkins-36123

[FIXED JENKINS-36123] Add an extension point that allows plugins to veto polling
......@@ -25,7 +25,6 @@ package hudson.cli;
import hudson.Util;
import hudson.console.ModelHyperlinkNote;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Cause.UserIdCause;
import hudson.model.CauseAction;
......@@ -42,10 +41,10 @@ import hudson.model.Item;
import hudson.model.TaskListener;
import hudson.model.User;
import hudson.model.queue.QueueTaskFuture;
import hudson.scm.PollingResult.Change;
import hudson.util.EditDistance;
import hudson.util.StreamTaskListener;
import jenkins.scm.SCMDecisionHandler;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Option;
......@@ -148,6 +147,10 @@ public class BuildCommand extends CLICommand {
SCMTriggerItem item = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(job);
if (item == null)
throw new AbortException(job.getFullDisplayName()+" has no SCM trigger, but checkSCM was specified");
// pre-emtively check for a polling veto
if (SCMDecisionHandler.firstShouldPollVeto(job) != null) {
return 0;
}
if (!item.poll(new StreamTaskListener(stdout, getClientCharset())).hasChanges())
return 0;
}
......
......@@ -114,6 +114,7 @@ import jenkins.model.lazy.LazyBuildMixIn;
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
import jenkins.scm.SCMDecisionHandler;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
......@@ -1322,6 +1323,11 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
listener.getLogger().println(Messages.AbstractProject_Disabled());
return NO_CHANGES;
}
SCMDecisionHandler veto = SCMDecisionHandler.firstShouldPollVeto(this);
if (veto != null) {
listener.getLogger().println(Messages.AbstractProject_PollingVetoed(veto));
return NO_CHANGES;
}
R lb = getLastBuild();
if (lb==null) {
......
......@@ -46,14 +46,6 @@ import hudson.util.NamingThreadFactory;
import hudson.util.SequentialExecutionQueue;
import hudson.util.StreamTaskListener;
import hudson.util.TimeUnit2;
import org.apache.commons.io.FileUtils;
import org.apache.commons.jelly.XMLOutput;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
......@@ -72,15 +64,23 @@ import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.model.RunAction2;
import jenkins.scm.SCMDecisionHandler;
import jenkins.triggers.SCMTriggerItem;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.jelly.XMLOutput;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import static java.util.logging.Level.*;
import jenkins.model.RunAction2;
import jenkins.util.SystemProperties;
import static java.util.logging.Level.WARNING;
/**
......@@ -549,6 +549,23 @@ public class SCMTrigger extends Trigger<Item> {
if (job == null) {
return;
}
// we can pre-emtively check the SCMDecisionHandler instances here
// note that job().poll(listener) should also check this
SCMDecisionHandler veto = SCMDecisionHandler.firstShouldPollVeto(job);
if (veto != null) {
try (StreamTaskListener listener = new StreamTaskListener(getLogFile())) {
listener.getLogger().println(
"Skipping polling on " + DateFormat.getDateTimeInstance().format(new Date())
+ " due to veto from " + veto);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to record SCM polling for " + job, e);
}
LOGGER.log(Level.FINE, "Skipping polling for {0} due to veto from {1}",
new Object[]{job.getFullDisplayName(), veto}
);
return;
}
String threadName = Thread.currentThread().getName();
Thread.currentThread().setName("SCM polling for "+job);
......@@ -616,7 +633,7 @@ public class SCMTrigger extends Trigger<Item> {
/**
* @deprecated
* Use {@link #SCMTrigger.SCMTriggerCause(String)}.
* Use {@link SCMTrigger.SCMTriggerCause#SCMTriggerCause(String)}.
*/
@Deprecated
public SCMTriggerCause() {
......
/*
* The MIT License
*
* Copyright (c) 2016, Stephen Connolly.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.scm;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Item;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Extension point for various decisions about SCM operations for {@link Item} instances.
*
* @since TODO
*/
public abstract class SCMDecisionHandler implements ExtensionPoint {
/**
* This handler is consulted every time someone tries to run a polling of an {@link Item}.
* If any of the registered handlers returns false, the {@link Item} will not be polled.
*
* @param item The item.
*/
public abstract boolean shouldPoll(@Nonnull Item item);
/**
* All registered {@link SCMDecisionHandler}s
*/
@Nonnull
public static ExtensionList<SCMDecisionHandler> all() {
return ExtensionList.lookup(SCMDecisionHandler.class);
}
/**
* Returns the first {@link SCMDecisionHandler} that returns {@code false} from {@link #shouldPoll(Item)}
* @param item the item
* @return the first veto or {@code null} if there are no vetos
*/
@CheckForNull
public static SCMDecisionHandler firstShouldPollVeto(@Nonnull Item item) {
for (SCMDecisionHandler handler : all()) {
if (!handler.shouldPoll(item)) {
return handler;
}
}
return null;
}
/**
* Returns the {@link SCMDecisionHandler} instances that return {@code false} from {@link #shouldPoll(Item)}
* @param item the item
* @return the {@link SCMDecisionHandler} instances vetoing the polling of the specified item.
*/
@Nonnull
public static List<SCMDecisionHandler> listShouldPollVetos(@Nonnull Item item) {
List<SCMDecisionHandler> result = new ArrayList<>();
for (SCMDecisionHandler handler : all()) {
if (!handler.shouldPoll(item)) {
result.add(handler);
}
}
return result;
}
}
......@@ -40,6 +40,7 @@ import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.ParameterizedJobMixIn;
import jenkins.scm.SCMDecisionHandler;
/**
* The item type accepted by {@link SCMTrigger}.
......@@ -65,6 +66,9 @@ public interface SCMTriggerItem {
* <p>
* The implementation is responsible for ensuring mutual exclusion between polling and builds
* if necessary.
* <p>
* The implementation is responsible for checking the {@link SCMDecisionHandler} before proceeding
* with the actual polling.
*/
@Nonnull PollingResult poll(@Nonnull TaskListener listener);
......@@ -116,6 +120,11 @@ public interface SCMTriggerItem {
return delegate.asProject().scheduleBuild2(quietPeriod, null, actions);
}
@Override public PollingResult poll(TaskListener listener) {
SCMDecisionHandler veto = SCMDecisionHandler.firstShouldPollVeto(asItem());
if (!veto.shouldPoll(asItem())) {
listener.getLogger().println(Messages.SCMTriggerItem_PollingVetoed(veto));
return PollingResult.NO_CHANGES;
}
return delegate.poll(listener);
}
@Override public SCMTrigger getSCMTrigger() {
......
......@@ -46,6 +46,7 @@ AbstractProject.NoWorkspace=No workspace is available, so can\u2019t check for u
AbstractProject.WorkspaceTitle=Workspace of {0}
AbstractProject.WorkspaceTitleOnComputer=Workspace of {0} on {1}
AbstractProject.PollingABorted=SCM polling aborted
AbstractProject.PollingVetoed=SCM polling vetoed by {0}
AbstractProject.ScmAborted=SCM check out aborted
AbstractProject.WorkspaceOffline=Workspace is offline.
AbstractProject.BuildPermission.Description=\
......
......@@ -22,3 +22,4 @@
ReverseBuildTrigger.build_after_other_projects_are_built=Build after other projects are built
ReverseBuildTrigger.running_as_cannot_even_see_for_trigger_f=Running as {0} cannot even see {1} for trigger from {2}
SCMTriggerItem.PollingVetoed=SCM polling vetoed by {0}
......@@ -27,8 +27,10 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import hudson.ExtensionList;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Item;
import hudson.util.OneShotEvent;
import hudson.util.StreamTaskListener;
import hudson.model.AbstractBuild;
......@@ -42,6 +44,9 @@ import hudson.model.Cause.UserCause;
import hudson.scm.NullSCM;
import hudson.triggers.SCMTrigger.SCMTriggerCause;
import hudson.triggers.SCMTrigger.BuildAction;
import java.util.HashSet;
import java.util.Set;
import jenkins.scm.SCMDecisionHandler;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
......@@ -52,6 +57,7 @@ import java.io.File;
import java.io.IOException;
import java.util.concurrent.Future;
import java.util.List;
import org.jvnet.hudson.test.TestExtension;
/**
* @author Alan Harder
......@@ -81,6 +87,28 @@ public class SCMTriggerTest {
build.get(); // let mock build finish
}
/**
* Make sure that SCMTrigger doesn't poll when there is a polling veto in place.
*/
@Test
@Issue("JENKINS-36123")
public void pollingExcludedByExtensionPoint() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
PollDecisionHandlerImpl handler =
ExtensionList.lookup(SCMDecisionHandler.class).get(PollDecisionHandlerImpl.class);
handler.blacklist.add(p);
// used to coordinate polling and check out
final OneShotEvent checkoutStarted = new OneShotEvent();
p.setScm(new TestSCM(checkoutStarted));
assertFalse("SCM-poll with blacklist should report no changes", p.pollSCMChanges(StreamTaskListener.fromStdout()));
handler.blacklist.remove(p);
assertTrue("SCM-poll with blacklist removed should report changes", p.pollSCMChanges(StreamTaskListener.fromStdout()));
}
private static class TestSCM extends NullSCM {
private volatile int myRev = 1;
private final OneShotEvent checkoutStarted;
......@@ -144,4 +172,15 @@ public class SCMTriggerTest {
assertFalse("There should only be one BuildAction.", ba.size()!=1);
}
@TestExtension
public static class PollDecisionHandlerImpl extends SCMDecisionHandler {
Set<Item> blacklist = new HashSet<>();
@Override
public boolean shouldPoll(Item item) {
return !blacklist.contains(item);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册