From ab26fdbebcb2fed07c144cd1ae918d516ffee941 Mon Sep 17 00:00:00 2001 From: godfath3r Date: Sun, 3 Jul 2016 16:51:22 +0300 Subject: [PATCH 01/89] [JENKINS-36193] Raise exception when trying to build a job that is disabled. --- .../src/main/java/jenkins/model/ParameterizedJobMixIn.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java index 40e904eecd..40fa6503a8 100644 --- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java +++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java @@ -52,7 +52,7 @@ import java.util.Map; import javax.annotation.CheckForNull; import javax.servlet.ServletException; import static javax.servlet.http.HttpServletResponse.SC_CREATED; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; import jenkins.util.TimeDuration; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -178,7 +178,7 @@ public abstract class ParameterizedJobMixIn & Param } if (!asJob().isBuildable()) { - throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR, new IOException(asJob().getFullName() + " is not buildable")); + throw HttpResponses.error(SC_CONFLICT, new IOException(asJob().getFullName() + " is not buildable")); } // if a build is parameterized, let that take over @@ -215,6 +215,9 @@ public abstract class ParameterizedJobMixIn & Param hudson.model.BuildAuthorizationToken.checkPermission(asJob(), asJob().getAuthToken(), req, rsp); ParametersDefinitionProperty pp = asJob().getProperty(ParametersDefinitionProperty.class); + if (!asJob().isBuildable()) { + throw HttpResponses.error(SC_CONFLICT, new IOException(asJob().getFullName() + " is not buildable!")); + } if (pp != null) { pp.buildWithParameters(req, rsp, delay); } else { From fefd61c87d9d19ba786178410473a60315707923 Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Mon, 11 Jul 2016 15:08:17 +0200 Subject: [PATCH 02/89] [JENKINS-35493] Introduce a UserDetails cache to fix performance regression after SECURITY-243 --- core/src/main/java/hudson/model/User.java | 31 ++-- .../jenkins/security/UserDetailsCache.java | 152 ++++++++++++++++++ .../security/UserDetailsCacheTest.java | 94 +++++++++++ 3 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/jenkins/security/UserDetailsCache.java create mode 100644 test/src/test/java/jenkins/security/UserDetailsCacheTest.java diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 9dfe633f04..e811b9bfdd 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -24,6 +24,7 @@ */ package hudson.model; +import jenkins.security.UserDetailsCache; import jenkins.util.SystemProperties; import com.google.common.base.Predicate; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; @@ -81,6 +82,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; @@ -1049,24 +1051,17 @@ public class User extends AbstractModelObject implements AccessControlled, Descr return existing.getId(); } if (SECURITY_243_FULL_DEFENSE) { - Jenkins j = Jenkins.getInstance(); - if (j != null) { - if (!resolving.get()) { - resolving.set(true); - try { - UserDetails userDetails = j.getSecurityRealm().loadUserByUsername(idOrFullName); - if (userDetails == null) { - throw new NullPointerException("hudson.security.SecurityRealm should never return null. " - + j.getSecurityRealm() + " returned null for idOrFullName='" + idOrFullName + "'"); - } - return userDetails.getUsername(); - } catch (UsernameNotFoundException x) { - LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x); - } catch (DataAccessException x) { - LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x); - } finally { - resolving.set(false); - } + if (!resolving.get()) { + resolving.set(true); + try { + UserDetails userDetails = UserDetailsCache.get().loadUserByUsername(idOrFullName); + return userDetails.getUsername(); + } catch (UsernameNotFoundException x) { + LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x); + } catch (DataAccessException | ExecutionException x) { + LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x); + } finally { + resolving.set(false); } } } diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java new file mode 100644 index 0000000000..7987afbb46 --- /dev/null +++ b/core/src/main/java/jenkins/security/UserDetailsCache.java @@ -0,0 +1,152 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * 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.security; + +import com.google.common.cache.Cache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import hudson.Extension; +import hudson.ExtensionList; +import jenkins.model.Jenkins; +import jenkins.util.SystemProperties; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.dao.DataAccessException; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static com.google.common.cache.CacheBuilder.newBuilder; + +/** + * Cache layer for {@link org.acegisecurity.userdetails.UserDetails} lookup. + * + * @since TODO + */ +@Extension +@Restricted(NoExternalUse.class) //TODO Keep for LTS, Remove when in weekly +public final class UserDetailsCache { + + private static final String SYS_PROP_NAME = UserDetailsCache.class.getName() + ".EXPIRE_AFTER_WRITE_SEC"; + /** + * Nr of seconds before a value expires after being cached, note full GC will also clear the cache. + * Should be able to set this value in script and then reload from disk to change in runtime. + */ + private static /*not final*/ Integer EXPIRE_AFTER_WRITE_SEC = SystemProperties.getInteger(SYS_PROP_NAME, (int)TimeUnit.MINUTES.toSeconds(2)); + private final Cache detailsCache; + private final Cache existanceCache; + + public UserDetailsCache() { + if (EXPIRE_AFTER_WRITE_SEC == null || EXPIRE_AFTER_WRITE_SEC <= 0) { + //just in case someone is trying to trick us + EXPIRE_AFTER_WRITE_SEC = SystemProperties.getInteger(SYS_PROP_NAME, (int)TimeUnit.MINUTES.toSeconds(2)); + } + detailsCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build(); + existanceCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build(); + } + + public static UserDetailsCache get() { + return ExtensionList.lookup(UserDetailsCache.class).get(UserDetailsCache.class); + } + + @CheckForNull + public UserDetails getCached(String idOrFullName) throws UsernameNotFoundException { + Boolean exists = existanceCache.getIfPresent(idOrFullName); + if (exists != null && !exists) { + throw new UsernameNotFoundException(String.format("\"%s\" does not exist", idOrFullName)); + } else { + return detailsCache.getIfPresent(idOrFullName); + } + } + + @Nonnull + public UserDetails loadUserByUsername(String idOrFullName) throws UsernameNotFoundException, DataAccessException, ExecutionException { + Boolean exists = existanceCache.getIfPresent(idOrFullName); + if(exists != null && !exists) { + throw new UsernameNotFoundException(String.format("\"%s\" does not exist", idOrFullName)); + } else { + try { + return detailsCache.get(idOrFullName, new Retriever(idOrFullName)); + } catch (ExecutionException | UncheckedExecutionException e) { + if (e.getCause() instanceof UsernameNotFoundException) { + throw ((UsernameNotFoundException)e.getCause()); + } else if (e.getCause() instanceof DataAccessException) { + throw ((DataAccessException)e.getCause()); + } else { + throw e; + } + } + } + } + + /** + * Discards all entries in the cache. + */ + public void invalidateAll() { + existanceCache.invalidateAll(); + detailsCache.invalidateAll(); + } + + /** + * Discards any cached value for key. + * @param idOrFullName the key + */ + public void invalidate(final String idOrFullName) { + existanceCache.invalidate(idOrFullName); + detailsCache.invalidate(idOrFullName); + } + + private class Retriever implements Callable { + private final String idOrFullName; + + private Retriever(final String idOrFullName) { + this.idOrFullName = idOrFullName; + } + + @Override + public UserDetails call() throws Exception { + try { + Jenkins jenkins = Jenkins.getInstance(); + UserDetails userDetails = jenkins.getSecurityRealm().loadUserByUsername(idOrFullName); + if (userDetails == null) { + existanceCache.put(this.idOrFullName, false); + throw new NullPointerException("hudson.security.SecurityRealm should never return null. " + + jenkins.getSecurityRealm() + " returned null for idOrFullName='" + idOrFullName + "'"); + } + existanceCache.put(this.idOrFullName, true); + return userDetails; + } catch (UsernameNotFoundException e) { + existanceCache.put(this.idOrFullName, false); + throw e; + } catch (DataAccessException e) { + existanceCache.put(this.idOrFullName, null); //TODO verify this is the correct way + throw e; + } + } + } +} diff --git a/test/src/test/java/jenkins/security/UserDetailsCacheTest.java b/test/src/test/java/jenkins/security/UserDetailsCacheTest.java new file mode 100644 index 0000000000..3365d341f8 --- /dev/null +++ b/test/src/test/java/jenkins/security/UserDetailsCacheTest.java @@ -0,0 +1,94 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * 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.security; + +import hudson.security.HudsonPrivateSecurityRealm; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.springframework.dao.DataAccessException; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.*; + +/** + * Tests for {@link UserDetailsCache}. + */ +public class UserDetailsCacheTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Before + public void before() throws IOException { + HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false, false, null); + j.jenkins.setSecurityRealm(realm); + realm.createAccount("alice", "veeerysecret"); + } + + @Test + public void getCachedTrue() throws Exception { + UserDetailsCache cache = UserDetailsCache.get(); + assertNotNull(cache); + UserDetails alice = cache.loadUserByUsername("alice"); + assertNotNull(alice); + UserDetails alice1 = cache.getCached("alice"); + assertNotNull(alice1); + } + + @Test + public void getCachedFalse() throws Exception { + UserDetailsCache cache = UserDetailsCache.get(); + assertNotNull(cache); + UserDetails alice1 = cache.getCached("alice"); + assertNull(alice1); + } + + @Test(expected = UsernameNotFoundException.class) + public void getCachedTrueNotFound() throws Exception { + UserDetailsCache cache = UserDetailsCache.get(); + assertNotNull(cache); + try { + cache.loadUserByUsername("bob"); + fail("Bob should not be found"); + } catch (UsernameNotFoundException e) { + //as expected + } + cache.getCached("bob"); + } + + @Test + public void getCachedFalseNotFound() throws Exception { + UserDetailsCache cache = UserDetailsCache.get(); + assertNotNull(cache); + UserDetails bob = cache.getCached("bob"); + assertNull(bob); + } + +} \ No newline at end of file From c50b61dd745e3f4547850f59abce5cd295e90d6e Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Mon, 11 Jul 2016 18:18:08 +0200 Subject: [PATCH 03/89] [JENKINS-35493] Forgot the JavaDoc --- .../jenkins/security/UserDetailsCache.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java index 7987afbb46..1c4169fb00 100644 --- a/core/src/main/java/jenkins/security/UserDetailsCache.java +++ b/core/src/main/java/jenkins/security/UserDetailsCache.java @@ -27,6 +27,7 @@ import com.google.common.cache.Cache; import com.google.common.util.concurrent.UncheckedExecutionException; import hudson.Extension; import hudson.ExtensionList; +import hudson.security.UserMayOrMayNotExistException; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; import org.acegisecurity.userdetails.UserDetails; @@ -61,6 +62,10 @@ public final class UserDetailsCache { private final Cache detailsCache; private final Cache existanceCache; + /** + * Constructor intended to be instantiated by Jenkins only. + */ + @Restricted(NoExternalUse.class) public UserDetailsCache() { if (EXPIRE_AFTER_WRITE_SEC == null || EXPIRE_AFTER_WRITE_SEC <= 0) { //just in case someone is trying to trick us @@ -70,20 +75,45 @@ public final class UserDetailsCache { existanceCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build(); } + /** + * The singleton instance registered in Jenkins. + * @return the cache + */ public static UserDetailsCache get() { return ExtensionList.lookup(UserDetailsCache.class).get(UserDetailsCache.class); } + /** + * Gets the cached UserDetails for the given username. + * Similar to {@link #loadUserByUsername(String)} except it doesn't perform the actual lookup if there is a cache miss. + * + * @param idOrFullName the username + * + * @return {@code null} if the cache doesn't contain any data for the key or the user details cached for the key. + * @throws UsernameNotFoundException if a previous lookup resulted in the same + */ @CheckForNull public UserDetails getCached(String idOrFullName) throws UsernameNotFoundException { Boolean exists = existanceCache.getIfPresent(idOrFullName); if (exists != null && !exists) { - throw new UsernameNotFoundException(String.format("\"%s\" does not exist", idOrFullName)); + throw new UserMayOrMayNotExistException(String.format("\"%s\" does not exist", idOrFullName)); } else { return detailsCache.getIfPresent(idOrFullName); } } + /** + * Locates the user based on the username, by first looking in the cache and then delegate to + * {@link hudson.security.SecurityRealm#loadUserByUsername(String)}. + * + * @param idOrFullName the username + * @return the details + * + * @throws UsernameNotFoundException (normally a {@link hudson.security.UserMayOrMayNotExistException}) + * if the user could not be found or the user has no GrantedAuthority + * @throws DataAccessException if user could not be found for a repository-specific reason + * @throws ExecutionException if anything else went wrong in the cache lookup/retrieval + */ @Nonnull public UserDetails loadUserByUsername(String idOrFullName) throws UsernameNotFoundException, DataAccessException, ExecutionException { Boolean exists = existanceCache.getIfPresent(idOrFullName); @@ -121,6 +151,10 @@ public final class UserDetailsCache { detailsCache.invalidate(idOrFullName); } + /** + * Callable that performs the actual lookup if there is a cache miss. + * @see #loadUserByUsername(String) + */ private class Retriever implements Callable { private final String idOrFullName; From 97369edb7d70c9652813f4acc5073b3017867e18 Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Mon, 11 Jul 2016 18:24:31 +0200 Subject: [PATCH 04/89] [JENKINS-35493] Fixed nits --- .../main/java/jenkins/security/UserDetailsCache.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java index 1c4169fb00..71a3db8402 100644 --- a/core/src/main/java/jenkins/security/UserDetailsCache.java +++ b/core/src/main/java/jenkins/security/UserDetailsCache.java @@ -70,6 +70,10 @@ public final class UserDetailsCache { if (EXPIRE_AFTER_WRITE_SEC == null || EXPIRE_AFTER_WRITE_SEC <= 0) { //just in case someone is trying to trick us EXPIRE_AFTER_WRITE_SEC = SystemProperties.getInteger(SYS_PROP_NAME, (int)TimeUnit.MINUTES.toSeconds(2)); + if (EXPIRE_AFTER_WRITE_SEC <= 0) { + //The property could also be set to a negative value + EXPIRE_AFTER_WRITE_SEC = (int)TimeUnit.MINUTES.toSeconds(2); + } } detailsCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build(); existanceCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build(); @@ -168,14 +172,14 @@ public final class UserDetailsCache { Jenkins jenkins = Jenkins.getInstance(); UserDetails userDetails = jenkins.getSecurityRealm().loadUserByUsername(idOrFullName); if (userDetails == null) { - existanceCache.put(this.idOrFullName, false); + existanceCache.put(this.idOrFullName, Boolean.FALSE); throw new NullPointerException("hudson.security.SecurityRealm should never return null. " + jenkins.getSecurityRealm() + " returned null for idOrFullName='" + idOrFullName + "'"); } - existanceCache.put(this.idOrFullName, true); + existanceCache.put(this.idOrFullName, Boolean.TRUE); return userDetails; } catch (UsernameNotFoundException e) { - existanceCache.put(this.idOrFullName, false); + existanceCache.put(this.idOrFullName, Boolean.FALSE); throw e; } catch (DataAccessException e) { existanceCache.put(this.idOrFullName, null); //TODO verify this is the correct way From 88c2d8ed043011ed7d3889776725477df5b58156 Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Thu, 14 Jul 2016 15:17:26 +0200 Subject: [PATCH 05/89] [JENKINS-18435] Tell browsers not to cache or try to autocomplete forms Setting autocomplete to "off" will tell browsers not to try to autofill forms during history navigation. Since this seems to be a prominent problem in Jenkins I feel it is best to have it default to "off" and let individual inputs or pages opt-in to enable it instead to indicate they can handle it. The standard login page does not seem to use the taglib form tag so it is not affected, even so, all modern browsers ignores the autocomplete attribute on username and password fields anyway. --- core/src/main/resources/lib/form/form.jelly | 6 +- test/src/test/java/lib/form/FormTest.java | 111 ++++++++++++++++++ .../AutocompleteOffByDefault/index.jelly | 37 ++++++ .../AutocompleteOnWhenTrue/index.jelly | 37 ++++++ .../InputsCanSetAutocomplete/index.jelly | 37 ++++++ 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/lib/form/FormTest.java create mode 100644 test/src/test/resources/lib/form/FormTest/AutocompleteOffByDefault/index.jelly create mode 100644 test/src/test/resources/lib/form/FormTest/AutocompleteOnWhenTrue/index.jelly create mode 100644 test/src/test/resources/lib/form/FormTest/InputsCanSetAutocomplete/index.jelly diff --git a/core/src/main/resources/lib/form/form.jelly b/core/src/main/resources/lib/form/form.jelly index 567b44e91b..b55e82700c 100644 --- a/core/src/main/resources/lib/form/form.jelly +++ b/core/src/main/resources/lib/form/form.jelly @@ -48,8 +48,12 @@ THE SOFTWARE. Optional class attribute for <table> that is created in the form. + + Optional attribute for allowing browsers to perform auto complete or pre-fill the form from history. + Default: false + -
+
diff --git a/test/src/test/java/lib/form/FormTest.java b/test/src/test/java/lib/form/FormTest.java new file mode 100644 index 0000000000..701efae3aa --- /dev/null +++ b/test/src/test/java/lib/form/FormTest.java @@ -0,0 +1,111 @@ +package lib.form; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlInput; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.RootAction; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.xml.sax.SAXException; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests for lib/form.jelly. + */ +public class FormTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @Issue("JENKINS-18435") + public void autocompleteOffByDefault() throws IOException, SAXException { + HtmlPage page = j.createWebClient().goTo("autocompleteOffByDefault"); + HtmlForm form = page.getFormByName("config"); + String autocomplete = form.getAttribute("autocomplete"); + assertNotNull(autocomplete); + assertEquals("off", autocomplete); + } + + @Test + @Issue("JENKINS-18435") + public void autocompleteOnWhenTrue() throws IOException, SAXException { + HtmlPage page = j.createWebClient().goTo("autocompleteOnWhenTrue"); + HtmlForm form = page.getFormByName("config"); + String autocomplete = form.getAttribute("autocomplete"); + assertNotNull(autocomplete); + assertEquals("on", autocomplete); + } + + @Test + @Issue("JENKINS-18435") + public void inputsCanSetAutocomplete() throws IOException, SAXException { + HtmlPage page = j.createWebClient().goTo("inputsCanSetAutocomplete"); + HtmlForm form = page.getFormByName("config"); + HtmlInput a = form.getInputByName("a"); + String autocomplete = a.getAttribute("autocomplete"); + assertNotNull(autocomplete); + assertEquals("on", autocomplete); + } + + @TestExtension("autocompleteOffByDefault") + public static class AutocompleteOffByDefault implements RootAction { + @Override + public String getIconFileName() { + return "gear2.png"; + } + + @Override + public String getDisplayName() { + return "AutocompleteOffByDefault"; + } + + @Override + public String getUrlName() { + return "autocompleteOffByDefault"; + } + } + + @TestExtension("autocompleteOnWhenTrue") + public static class AutocompleteOnWhenTrue implements RootAction { + @Override + public String getIconFileName() { + return "gear2.png"; + } + + @Override + public String getDisplayName() { + return "AutocompleteOnWhenTrue"; + } + + @Override + public String getUrlName() { + return "autocompleteOnWhenTrue"; + } + } + + @TestExtension("inputsCanSetAutocomplete") + public static class InputsCanSetAutocomplete implements RootAction { + @Override + public String getIconFileName() { + return "gear2.png"; + } + + @Override + public String getDisplayName() { + return "InputsCanSetAutocomplete"; + } + + @Override + public String getUrlName() { + return "inputsCanSetAutocomplete"; + } + } +} diff --git a/test/src/test/resources/lib/form/FormTest/AutocompleteOffByDefault/index.jelly b/test/src/test/resources/lib/form/FormTest/AutocompleteOffByDefault/index.jelly new file mode 100644 index 0000000000..3f822e8b00 --- /dev/null +++ b/test/src/test/resources/lib/form/FormTest/AutocompleteOffByDefault/index.jelly @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test/resources/lib/form/FormTest/AutocompleteOnWhenTrue/index.jelly b/test/src/test/resources/lib/form/FormTest/AutocompleteOnWhenTrue/index.jelly new file mode 100644 index 0000000000..46362ef77d --- /dev/null +++ b/test/src/test/resources/lib/form/FormTest/AutocompleteOnWhenTrue/index.jelly @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test/resources/lib/form/FormTest/InputsCanSetAutocomplete/index.jelly b/test/src/test/resources/lib/form/FormTest/InputsCanSetAutocomplete/index.jelly new file mode 100644 index 0000000000..bb3038e53f --- /dev/null +++ b/test/src/test/resources/lib/form/FormTest/InputsCanSetAutocomplete/index.jelly @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + \ No newline at end of file From a0431beba641a7536981765ac77256b6f3859fbe Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Fri, 15 Jul 2016 12:53:58 +0200 Subject: [PATCH 06/89] Turn on autocomplete for job and view name --- core/src/main/resources/hudson/model/Job/configure.jelly | 2 +- core/src/main/resources/hudson/model/View/configure.jelly | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/hudson/model/Job/configure.jelly b/core/src/main/resources/hudson/model/Job/configure.jelly index ac39d60dbb..ddf5467f8d 100644 --- a/core/src/main/resources/hudson/model/Job/configure.jelly +++ b/core/src/main/resources/hudson/model/Job/configure.jelly @@ -46,7 +46,7 @@ THE SOFTWARE. - + diff --git a/core/src/main/resources/hudson/model/View/configure.jelly b/core/src/main/resources/hudson/model/View/configure.jelly index b0c41a8387..11d4ea1f86 100644 --- a/core/src/main/resources/hudson/model/View/configure.jelly +++ b/core/src/main/resources/hudson/model/View/configure.jelly @@ -36,7 +36,7 @@ THE SOFTWARE. - + From 8a330acc5eba2697a671d5d6c13d7d7cb90e723e Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 15 Jul 2016 17:33:29 +0300 Subject: [PATCH 07/89] [JENKINS-36193] - Add direct unit test for the issue --- .../model/ParameterizedJobMixInTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java diff --git a/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java b/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java new file mode 100644 index 0000000000..d6a9715585 --- /dev/null +++ b/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * + * Copyright (c) 2016 Oleg Nenashev. + * + * 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.model; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.FreeStyleProject; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.StringParameterDefinition; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests of {@link ParameterizedJobMixIn}. + * @author Oleg Nenashev + */ +public class ParameterizedJobMixInTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void doBuild_shouldFailWhenInvokingDisabledProject() throws Exception { + final FreeStyleProject project = j.createFreeStyleProject(); + project.doDisable(); + + final JenkinsRule.WebClient webClient = j.createWebClient(); + webClient.assertFails(project.getUrl() + "build", HttpServletResponse.SC_CONFLICT); + } + + @Test + @Issue("JENKINS-36193") + public void doBuildWithParameters_shouldFailWhenInvokingDisabledProject() throws Exception { + final FreeStyleProject project = j.createFreeStyleProject(); + project.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("FOO", "BAR"))); + project.doDisable(); + + final JenkinsRule.WebClient webClient = j.createWebClient(); + webClient.assertFails(project.getUrl() + "buildWithParameters", HttpServletResponse.SC_CONFLICT); + } +} From 7d5a2e3f367499af6f3be3fb4fe9137d8d00e053 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 16 Jul 2016 22:14:16 +0300 Subject: [PATCH 08/89] [JENKINS-36592] Prevent NPE in BlockedBecauseOfBuildInProgress#getDescription() (#2448) * [JENKINS-36592] Prevent NPE in BlockedBecauseOfBuildInProgress#getDescription() in the case of race condition * [JENKINS-36592] - Address comments from @jglick * [JENKINS-36592] - Fix wording. Attempt #2 --- core/src/main/java/hudson/model/AbstractProject.java | 12 ++++++++++-- .../model/BlockedBecauseOfBuildInProgress.java | 8 +++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 8dab4cb80c..2c2fe31c76 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -1105,7 +1105,7 @@ public abstract class AbstractProject

,R extends A */ @Deprecated public static class BecauseOfBuildInProgress extends BlockedBecauseOfBuildInProgress { - public BecauseOfBuildInProgress(AbstractBuild build) { + public BecauseOfBuildInProgress(@Nonnull AbstractBuild build) { super(build); } } @@ -1146,7 +1146,15 @@ public abstract class AbstractProject

,R extends A public CauseOfBlockage getCauseOfBlockage() { // Block builds until they are done with post-production if (isLogUpdated() && !isConcurrentBuild()) { - return new BlockedBecauseOfBuildInProgress(getLastBuild()); + final R lastBuild = getLastBuild(); + if (lastBuild != null) { + return new BlockedBecauseOfBuildInProgress(lastBuild); + } else { + // The build has been likely deleted after the isLogUpdated() call. + // Another cause may be an API implemetation glit�h in the implementation for AbstractProject. + // Anyway, we should let the code go then. + LOGGER.log(Level.FINE, "The last build has been deleted during the non-concurrent cause creation. The build is not blocked anymore"); + } } if (blockBuildWhenDownstreamBuilding()) { AbstractProject bup = getBuildingDownstream(); diff --git a/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java b/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java index f55537c2ca..9abdd8fa88 100644 --- a/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java +++ b/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java @@ -28,6 +28,7 @@ import hudson.model.Executor; import hudson.model.Job; import hudson.model.Run; import hudson.model.queue.CauseOfBlockage; +import javax.annotation.Nonnull; /** * Indicates that a new build is blocked because the previous build is already in progress. @@ -36,9 +37,14 @@ import hudson.model.queue.CauseOfBlockage; */ public class BlockedBecauseOfBuildInProgress extends CauseOfBlockage { + @Nonnull private final Run build; - public BlockedBecauseOfBuildInProgress(Run build) { + /** + * Creates a cause for the specified build. + * @param build Build, which is already in progress + */ + public BlockedBecauseOfBuildInProgress(@Nonnull Run build) { this.build = build; } From c31392d1401a45e8e9df4b91b2445cb4b1901f4d Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Sat, 16 Jul 2016 15:14:48 -0400 Subject: [PATCH 09/89] [JENKINS-36476] Underprivileged users could not use the default value of a password parameter (#2454) * [FIXED JENKINS-36476] Do not even send the default password in encrypted form; leave the text field blank, and apply the default upon submission. * [JENKINS-36476] Use a special token, not the empty string, as the marker for the default value. --- core/src/main/java/hudson/Functions.java | 3 +- .../model/PasswordParameterDefinition.java | 7 +++ .../PasswordParameterDefinition/index.jelly | 2 +- .../PasswordParameterDefinitionTest.java | 56 +++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index e2e7e7ce69..85a148595f 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -154,6 +154,7 @@ import org.kohsuke.stapler.jelly.InternationalizedStringExpression.RawHtmlArgume import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import hudson.model.PasswordParameterDefinition; import hudson.util.RunList; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.CheckForNull; @@ -1779,7 +1780,7 @@ public class Functions { } return ((Secret) o).getEncryptedValue(); } - if (getIsUnitTest()) { + if (getIsUnitTest() && !o.equals(PasswordParameterDefinition.DEFAULT_VALUE)) { throw new SecurityException("attempted to render plaintext ‘" + o + "’ in password field; use a getter of type Secret instead"); } return o.toString(); diff --git a/core/src/main/java/hudson/model/PasswordParameterDefinition.java b/core/src/main/java/hudson/model/PasswordParameterDefinition.java index f4cad5a8c3..f3dfe79356 100644 --- a/core/src/main/java/hudson/model/PasswordParameterDefinition.java +++ b/core/src/main/java/hudson/model/PasswordParameterDefinition.java @@ -31,6 +31,7 @@ import hudson.Extension; import hudson.util.Secret; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Parameter whose value is a {@link Secret} and is hidden from the UI. @@ -40,6 +41,9 @@ import org.kohsuke.accmod.restrictions.DoNotUse; */ public class PasswordParameterDefinition extends SimpleParameterDefinition { + @Restricted(NoExternalUse.class) + public static final String DEFAULT_VALUE = ""; + private Secret defaultValue; @DataBoundConstructor @@ -66,6 +70,9 @@ public class PasswordParameterDefinition extends SimpleParameterDefinition { @Override public PasswordParameterValue createValue(StaplerRequest req, JSONObject jo) { PasswordParameterValue value = req.bindJSON(PasswordParameterValue.class, jo); + if (value.getValue().getPlainText().equals(DEFAULT_VALUE)) { + value = new PasswordParameterValue(getName(), getDefaultValue()); + } value.setDescription(getDescription()); return value; } diff --git a/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly b/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly index 9bac23608c..81b8c0190a 100644 --- a/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly +++ b/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly @@ -29,7 +29,7 @@ THE SOFTWARE.

- +
\ No newline at end of file diff --git a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java index 8b4dcb2906..2129c9fce9 100644 --- a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java +++ b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java @@ -24,10 +24,18 @@ package hudson.model; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; +import hudson.Launcher; +import java.io.IOException; +import jenkins.model.Jenkins; import static org.junit.Assert.assertEquals; import org.junit.Rule; import org.junit.Test; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; +import org.jvnet.hudson.test.TestBuilder; public class PasswordParameterDefinitionTest { @@ -40,4 +48,52 @@ public class PasswordParameterDefinitionTest { assertEquals("s3cr3t", ((PasswordParameterDefinition) p.getProperty(ParametersDefinitionProperty.class).getParameterDefinition("p")).getDefaultValue()); } + @Issue("JENKINS-36476") + @Test public void defaultValueAlwaysAvailable() throws Exception { + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy(). + grant(Jenkins.ADMINISTER).everywhere().to("admin"). + grant(Jenkins.READ, Item.READ, Item.BUILD).everywhere().to("dev")); + FreeStyleProject p = j.createFreeStyleProject(); + p.addProperty(new ParametersDefinitionProperty(new PasswordParameterDefinition("secret", "s3cr3t", ""))); + p.getBuildersList().add(new TestBuilder() { + @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + listener.getLogger().println("I heard about a " + build.getEnvironment(listener).get("secret") + "!"); + return true; + } + }); + JenkinsRule.WebClient wc = j.createWebClient(); + wc.getOptions().setThrowExceptionOnFailingStatusCode(false); // ParametersDefinitionProperty/index.jelly sends a 405 but really it is OK + // Control case: admin can use default value. + j.submit(wc.login("admin").getPage(p, "build?delay=0sec").getFormByName("parameters")); + j.waitUntilNoActivity(); + FreeStyleBuild b1 = p.getLastBuild(); + assertEquals(1, b1.getNumber()); + j.assertLogContains("I heard about a s3cr3t!", j.assertBuildStatusSuccess(b1)); + // Another control case: anyone can enter a different value. + HtmlForm form = wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters"); + HtmlPasswordInput input = form.getInputByName("value"); + input.setText("rumor"); + j.submit(form); + j.waitUntilNoActivity(); + FreeStyleBuild b2 = p.getLastBuild(); + assertEquals(2, b2.getNumber()); + j.assertLogContains("I heard about a rumor!", j.assertBuildStatusSuccess(b2)); + // Test case: anyone can use default value. + j.submit(wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters")); + j.waitUntilNoActivity(); + FreeStyleBuild b3 = p.getLastBuild(); + assertEquals(3, b3.getNumber()); + j.assertLogContains("I heard about a s3cr3t!", j.assertBuildStatusSuccess(b3)); + // Another control case: blank values. + form = wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters"); + input = form.getInputByName("value"); + input.setText(""); + j.submit(form); + j.waitUntilNoActivity(); + FreeStyleBuild b4 = p.getLastBuild(); + assertEquals(4, b4.getNumber()); + j.assertLogContains("I heard about a !", j.assertBuildStatusSuccess(b4)); + } + } From 3e3a48ee993e7ece5c4793e7af719095bfd90093 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sun, 17 Jul 2016 14:02:51 +0300 Subject: [PATCH 10/89] Noting #2443, #2420, #2408, #2433, #2450, #2449, #2448, #2454 --- changelog.html | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/changelog.html b/changelog.html index ff2228d6f8..5ea6ba9aaa 100644 --- a/changelog.html +++ b/changelog.html @@ -56,13 +56,36 @@ Upcoming changes

What's new in 2.13 (2016/07/10)

From 3fdc58e39d6469c28fba53be9966d68cdf570d04 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 17 Jul 2016 13:57:07 -0700 Subject: [PATCH 11/89] [maven-release-plugin] prepare release jenkins-2.14 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 85514610ff..bab2088db9 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.14-SNAPSHOT + 2.14 cli diff --git a/core/pom.xml b/core/pom.xml index 34a6bafa58..f2eea6ddb9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14-SNAPSHOT + 2.14 jenkins-core diff --git a/pom.xml b/pom.xml index 12fa507560..185f8a0429 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14-SNAPSHOT + 2.14 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.14 diff --git a/test/pom.xml b/test/pom.xml index ce6913134f..0e68ef8b3b 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14-SNAPSHOT + 2.14 test diff --git a/war/pom.xml b/war/pom.xml index 2ce00c402c..adf3d028b2 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14-SNAPSHOT + 2.14 jenkins-war From 9b3785b36c049e0f891738adef959be9c5b955a6 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 17 Jul 2016 13:57:07 -0700 Subject: [PATCH 12/89] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index bab2088db9..7fca7c71d6 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.14 + 2.15-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index f2eea6ddb9..c8c368b0c4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14 + 2.15-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 185f8a0429..2ee0f0b833 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14 + 2.15-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.14 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 0e68ef8b3b..05cbb181db 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14 + 2.15-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index adf3d028b2..3490b4a3ae 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.14 + 2.15-SNAPSHOT jenkins-war From 64270cc2ab9d19e9954017d9ac2afd88a9c13c44 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 17 Jul 2016 14:03:11 -0700 Subject: [PATCH 13/89] updated changelog for release --- changelog.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.html b/changelog.html index 5ea6ba9aaa..08a7506891 100644 --- a/changelog.html +++ b/changelog.html @@ -55,6 +55,11 @@ Upcoming changes +

What's new in 2.14 (2016/07/17)

  • Minor optimization in calculation of recent build stability health report. @@ -87,7 +92,6 @@ Upcoming changes Developer API: Usage of ItemCategory#MIN_TOSHOW in external plugins is now restricted. (issue 36593)
-

What's new in 2.13 (2016/07/10)

  • From 1231d8213994b2b02211a297262e3bbf8076c670 Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Mon, 18 Jul 2016 12:03:23 +0200 Subject: [PATCH 14/89] Invalidate the key in existanceCache instead of setting null --- core/src/main/java/jenkins/security/UserDetailsCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java index 71a3db8402..f4bda59e47 100644 --- a/core/src/main/java/jenkins/security/UserDetailsCache.java +++ b/core/src/main/java/jenkins/security/UserDetailsCache.java @@ -182,7 +182,7 @@ public final class UserDetailsCache { existanceCache.put(this.idOrFullName, Boolean.FALSE); throw e; } catch (DataAccessException e) { - existanceCache.put(this.idOrFullName, null); //TODO verify this is the correct way + existanceCache.invalidate(this.idOrFullName); throw e; } } From 5005026b66170cba8c6c42be8fda79801b812703 Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Mon, 18 Jul 2016 18:13:46 +0200 Subject: [PATCH 15/89] Invalidate user details cache when user is removed --- core/src/main/java/hudson/model/User.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index e811b9bfdd..fb80976ab9 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -752,6 +752,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr byNameLock.readLock().unlock(); } Util.deleteRecursive(new File(getRootDir(), strategy.filenameOf(id))); + UserDetailsCache.get().invalidate(strategy.keyFor(id)); } /** From 082e22c81c55f71ffcb50c3524c603b9b7678e3d Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Tue, 19 Jul 2016 11:47:42 +0200 Subject: [PATCH 16/89] Invalidate UserDetailsCache on user rekey and reload --- core/src/main/java/hudson/model/User.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index fb80976ab9..47f965e054 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -581,6 +581,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr } } finally { byNameLock.readLock().unlock(); + UserDetailsCache.get().invalidateAll(); } } @@ -614,6 +615,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr } } finally { byNameLock.writeLock().unlock(); + UserDetailsCache.get().invalidateAll(); } } From 6c5fbf000886a3e997410f01d9efd8d963f21834 Mon Sep 17 00:00:00 2001 From: Antonio Muniz Date: Tue, 19 Jul 2016 13:15:06 +0200 Subject: [PATCH 17/89] Update since tags for Item Categorization API (#2465) --- .../main/java/hudson/model/TopLevelItemDescriptor.java | 8 ++++---- .../model/item_category/StandaloneProjectsCategory.java | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java index aa6a2835b1..f53306d1b2 100644 --- a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java +++ b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java @@ -139,7 +139,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor { * * @return A string, by default the value from newInstanceDetail view is taken. * - * @since TODO + * @since 2.0 */ @Nonnull public String getDescription() { @@ -171,7 +171,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor { * * @return A string with the category identifier, {@link ItemCategory.UncategorizedCategory#getId()} by default. * - * @since TODO + * @since 2.0 */ @Nonnull public String getCategoryId() { @@ -188,7 +188,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor { * * @return A string or null if it is not defined. * - * @since TODO + * @since 2.0 */ @CheckForNull public String getIconFilePathPattern() { @@ -202,7 +202,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor { * * @return A string or null if it is not defined. * - * @since TODO + * @since 2.0 */ @CheckForNull public String getIconFilePath(String size) { diff --git a/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java b/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java index d1e5479bff..cf6d0daeaf 100644 --- a/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java +++ b/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java @@ -28,6 +28,8 @@ import hudson.Extension; /** * Designed for projects with a self-contained configuration and history. + * + * @since 2.0 */ @Extension(ordinal = -100) public class StandaloneProjectsCategory extends ItemCategory { From 642138dd3405958002329c129f1c75e978bb172d Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Tue, 19 Jul 2016 15:38:09 +0200 Subject: [PATCH 18/89] Invalidate UserDetailsCache when User.fullName changes --- core/src/main/java/hudson/model/User.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 47f965e054..1cf1ffdf8b 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -772,7 +772,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr checkPermission(Jenkins.ADMINISTER); JSONObject json = req.getSubmittedForm(); - + String oldFullName = this.fullName; fullName = json.getString("fullName"); description = json.getString("description"); @@ -798,6 +798,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr save(); + if (oldFullName != null && !oldFullName.equals(this.fullName)) { + UserDetailsCache.get().invalidate(oldFullName); + } + FormApply.success(".").generateResponse(req,rsp,this); } From a4a1847d74280c46a6566d7966d7fdbc60ff8e2e Mon Sep 17 00:00:00 2001 From: Robert Sandell Date: Tue, 19 Jul 2016 17:58:03 +0200 Subject: [PATCH 19/89] Remove API restriction on UserDetailsCache Followup to #2446 to remove the API restriction so that the first fix can potentially be picked for LTS while this stays in the weekly release. --- core/src/main/java/jenkins/security/UserDetailsCache.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java index f4bda59e47..be6e2d20b6 100644 --- a/core/src/main/java/jenkins/security/UserDetailsCache.java +++ b/core/src/main/java/jenkins/security/UserDetailsCache.java @@ -50,7 +50,6 @@ import static com.google.common.cache.CacheBuilder.newBuilder; * @since TODO */ @Extension -@Restricted(NoExternalUse.class) //TODO Keep for LTS, Remove when in weekly public final class UserDetailsCache { private static final String SYS_PROP_NAME = UserDetailsCache.class.getName() + ".EXPIRE_AFTER_WRITE_SEC"; From 40687bdc2268a117c40d8224ed6a7857b9af14b4 Mon Sep 17 00:00:00 2001 From: Felix Belzunce Arcos Date: Wed, 20 Jul 2016 13:54:17 +0200 Subject: [PATCH 20/89] [JENKINS-36748] Do not process null Triggers (#2463) * [JENKINS-36748] Not process null Triggers * [JENKINS-36748] Specify job * [JENKINS-36748] Correctly check NullPointerException --- .../main/java/hudson/triggers/Trigger.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java index c528983085..c05a4490ce 100644 --- a/core/src/main/java/hudson/triggers/Trigger.java +++ b/core/src/main/java/hudson/triggers/Trigger.java @@ -94,7 +94,7 @@ public abstract class Trigger implements Describable> } catch (ANTLRException e) { // this shouldn't fail because we've already parsed stuff in the constructor, // so if it fails, use whatever 'tabs' that we already have. - LOGGER.log(Level.FINE, "Failed to parse crontab spec: "+spec,e); + LOGGER.log(Level.WARNING, String.format("Failed to parse crontab spec %s in job %s", spec, project.getFullName()), e); } } @@ -264,20 +264,24 @@ public abstract class Trigger implements Describable> // Process all triggers, except SCMTriggers when synchronousPolling is set for (ParameterizedJobMixIn.ParameterizedJob p : inst.getAllItems(ParameterizedJobMixIn.ParameterizedJob.class)) { for (Trigger t : p.getTriggers().values()) { - if (! (t instanceof SCMTrigger && scmd.synchronousPolling)) { - LOGGER.log(Level.FINE, "cron checking {0} with spec ‘{1}’", new Object[] {p, t.spec.trim()}); + if (!(t instanceof SCMTrigger && scmd.synchronousPolling)) { + if (t !=null && t.spec != null && t.tabs != null) { + LOGGER.log(Level.FINE, "cron checking {0} with spec ‘{1}’", new Object[]{p, t.spec.trim()}); - if (t.tabs.check(cal)) { - LOGGER.log(Level.CONFIG, "cron triggered {0}", p); - try { - t.run(); - } catch (Throwable e) { - // t.run() is a plugin, and some of them throw RuntimeException and other things. - // don't let that cancel the polling activity. report and move on. - LOGGER.log(Level.WARNING, t.getClass().getName() + ".run() failed for " + p, e); + if (t.tabs.check(cal)) { + LOGGER.log(Level.CONFIG, "cron triggered {0}", p); + try { + t.run(); + } catch (Throwable e) { + // t.run() is a plugin, and some of them throw RuntimeException and other things. + // don't let that cancel the polling activity. report and move on. + LOGGER.log(Level.WARNING, t.getClass().getName() + ".run() failed for " + p, e); + } + } else { + LOGGER.log(Level.FINER, "did not trigger {0}", p); } } else { - LOGGER.log(Level.FINER, "did not trigger {0}", p); + LOGGER.log(Level.WARNING, "The job {0} has a syntactically incorrect config and is missing the cron spec for a trigger", p.getFullName()); } } } From 4ed803cea2944e5f83c12e681066e772cceb6107 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 20 Jul 2016 07:55:42 -0400 Subject: [PATCH 21/89] [JENKINS-36666] Defend against some fatal startup errors (#2453) * [JENKINS-36666] Properly report the reason for a failed call to Task.getDisplayName. * [JENKINS-36666] Even without passing fatal=false to @Initializer, recover gracefully from a LinkageError in a task. * Comment. --- core/src/main/java/hudson/PluginManager.java | 2 +- .../main/java/jenkins/InitReactorRunner.java | 19 ++++++++++++++--- core/src/main/java/jenkins/model/Jenkins.java | 21 +++++++++++++++---- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index d9a4686c26..5dba092cfc 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -465,7 +465,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas if(p.isActive()) activePlugins.add(p); } - } catch (CycleDetectedException e) { + } catch (CycleDetectedException e) { // TODO this should be impossible, since we override reactOnCycle to not throw the exception stop(); // disable all plugins since classloading from them can lead to StackOverflow throw e; // let Hudson fail } diff --git a/core/src/main/java/jenkins/InitReactorRunner.java b/core/src/main/java/jenkins/InitReactorRunner.java index 3c6f43988c..c5c2bfb844 100644 --- a/core/src/main/java/jenkins/InitReactorRunner.java +++ b/core/src/main/java/jenkins/InitReactorRunner.java @@ -25,6 +25,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import static java.util.logging.Level.SEVERE; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Executes the {@link Reactor} for the purpose of bootup. @@ -61,15 +63,15 @@ public class InitReactorRunner { r.add(new ReactorListener() { final Level level = Level.parse( Configuration.getStringConfigParameter("initLogLevel", "FINE") ); public void onTaskStarted(Task t) { - LOGGER.log(level,"Started "+t.getDisplayName()); + LOGGER.log(level, "Started {0}", getDisplayName(t)); } public void onTaskCompleted(Task t) { - LOGGER.log(level,"Completed "+t.getDisplayName()); + LOGGER.log(level, "Completed {0}", getDisplayName(t)); } public void onTaskFailed(Task t, Throwable err, boolean fatal) { - LOGGER.log(SEVERE, "Failed "+t.getDisplayName(),err); + LOGGER.log(SEVERE, "Failed " + getDisplayName(t), err); } public void onAttained(Milestone milestone) { @@ -86,6 +88,17 @@ public class InitReactorRunner { return new ReactorListener.Aggregator(r); } + /** Like {@link Task#getDisplayName} but more robust. */ + @Restricted(NoExternalUse.class) + public static String getDisplayName(Task t) { + try { + return t.getDisplayName(); + } catch (RuntimeException | Error x) { + LOGGER.log(Level.WARNING, "failed to find displayName of " + t, x); + return t.toString(); + } + } + /** * Called when the init milestone is attained. */ diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 65ac9f25c0..25481a0bb2 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -987,7 +987,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve if (is!=null && is.skipInitTask(task)) return; ACL.impersonate(ACL.SYSTEM); // full access in the initialization thread - String taskName = task.getDisplayName(); + String taskName = InitReactorRunner.getDisplayName(task); Thread t = Thread.currentThread(); String name = t.getName(); @@ -999,11 +999,24 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve if(LOG_STARTUP_PERFORMANCE) LOGGER.info(String.format("Took %dms for %s by %s", System.currentTimeMillis()-start, taskName, name)); + } catch (Exception | Error x) { + if (containsLinkageError(x)) { + LOGGER.log(Level.WARNING, taskName + " failed perhaps due to plugin dependency issues", x); + } else { + throw x; + } } finally { t.setName(name); SecurityContextHolder.clearContext(); } } + private boolean containsLinkageError(Throwable x) { + if (x instanceof LinkageError) { + return true; + } + Throwable x2 = x.getCause(); + return x2 != null && containsLinkageError(x2); + } }; new InitReactorRunner() { @@ -3022,15 +3035,15 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve final Level level = Level.parse(Configuration.getStringConfigParameter("termLogLevel", "FINE")); public void onTaskStarted(Task t) { - LOGGER.log(level, "Started " + t.getDisplayName()); + LOGGER.log(level, "Started {0}", InitReactorRunner.getDisplayName(t)); } public void onTaskCompleted(Task t) { - LOGGER.log(level, "Completed " + t.getDisplayName()); + LOGGER.log(level, "Completed {0}", InitReactorRunner.getDisplayName(t)); } public void onTaskFailed(Task t, Throwable err, boolean fatal) { - LOGGER.log(SEVERE, "Failed " + t.getDisplayName(), err); + LOGGER.log(SEVERE, "Failed " + InitReactorRunner.getDisplayName(t), err); } public void onAttained(Milestone milestone) { From fb7458f2e6acbe955fbb4b8f0afd9d352d839181 Mon Sep 17 00:00:00 2001 From: Pavel Janousek Date: Wed, 20 Jul 2016 14:30:09 +0200 Subject: [PATCH 22/89] [JENKINS-34468] CLI command offline-node extracted from core to CLI (#2392) * [JENKINS-34468] CLI command offline-node extracted from core to CLI offline-node extended to accept multiple node names offline-node covered by test-cases * Fixed translation properties * Fixes based on Oleg's comments * Fixed HelpCommandTest test-case * Minor fixes * Uses Matchers.instanceOf everywhere instead of Java operator instanceof --- .../java/hudson/cli/OfflineNodeCommand.java | 95 ++++ core/src/main/java/hudson/model/Computer.java | 9 +- .../resources/hudson/cli/Messages.properties | 2 + .../hudson/cli/Messages_bg.properties | 4 + .../hudson/cli/Messages_da.properties | 2 + .../hudson/cli/Messages_de.properties | 2 + .../hudson/cli/Messages_es.properties | 2 + .../hudson/cli/Messages_it.properties | 1 + .../hudson/cli/Messages_ja.properties | 2 + .../hudson/cli/Messages_lt.properties | 6 +- .../hudson/cli/Messages_pt_BR.properties | 2 + .../hudson/cli/Messages_zh_CN.properties | 2 + .../hudson/cli/Messages_zh_TW.properties | 2 + .../hudson/model/Messages.properties | 1 - .../hudson/model/Messages_bg.properties | 3 - .../hudson/model/Messages_da.properties | 1 - .../hudson/model/Messages_de.properties | 1 - .../hudson/model/Messages_es.properties | 1 - .../hudson/model/Messages_it.properties | 1 - .../hudson/model/Messages_ja.properties | 1 - .../hudson/model/Messages_lt.properties | 1 - .../hudson/model/Messages_pt_BR.properties | 2 - .../hudson/model/Messages_zh_CN.properties | 1 - .../hudson/model/Messages_zh_TW.properties | 1 - .../test/java/hudson/cli/HelpCommandTest.java | 4 +- .../hudson/cli/OfflineNodeCommandTest.java | 482 ++++++++++++++++++ 26 files changed, 608 insertions(+), 23 deletions(-) create mode 100644 core/src/main/java/hudson/cli/OfflineNodeCommand.java create mode 100644 test/src/test/java/hudson/cli/OfflineNodeCommandTest.java diff --git a/core/src/main/java/hudson/cli/OfflineNodeCommand.java b/core/src/main/java/hudson/cli/OfflineNodeCommand.java new file mode 100644 index 0000000000..b92cee59ca --- /dev/null +++ b/core/src/main/java/hudson/cli/OfflineNodeCommand.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * + * Copyright 2016 Red Hat, Inc. + * + * 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 hudson.cli; + +import hudson.AbortException; +import hudson.Extension; +import hudson.model.Computer; +import hudson.model.ComputerSet; +import hudson.util.EditDistance; +import jenkins.model.Jenkins; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +/** + * @author pjanouse + * @since TODO + */ +@Extension +public class OfflineNodeCommand extends CLICommand { + + @Argument(metaVar = "NAME", usage = "Agent name, or empty string for master", required = true, multiValued = true) + private List nodes; + + @Option(name = "-m", usage = "Record the reason about why you are disconnecting this node") + public String cause; + + @Override + public String getShortDescription() { + return Messages.OfflineNodeCommand_ShortDescription(); + } + + @Override + protected int run() throws Exception { + boolean errorOccurred = false; + final Jenkins jenkins = Jenkins.getInstance(); + final HashSet hs = new HashSet(nodes); + List names = null; + + for (String node_s : hs) { + try { + Computer computer = jenkins.getComputer(node_s); + if (computer == null) { + if (names == null) { + names = ComputerSet.getComputerNames(); + } + String adv = EditDistance.findNearest(node_s, names); + throw new IllegalArgumentException(adv == null ? + hudson.model.Messages.Computer_NoSuchSlaveExistsWithoutAdvice(node_s) : + hudson.model.Messages.Computer_NoSuchSlaveExists(node_s, adv)); + } + computer.cliOffline(cause); + } catch (Exception e) { + if (hs.size() == 1) { + throw e; + } + + stderr.println(node_s + ": " + e.getMessage()); + errorOccurred = true; + continue; + } + } + + if (errorOccurred) { + throw new AbortException("Error occured while performing this command, see previous stderr output."); + } + return 0; + } +} diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 4d4d1e5fe6..7db02878ae 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -521,10 +521,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } /** - * CLI command to mark the node offline. + * @deprecated Implementation of CLI command "offline-node" moved to {@link hudson.cli.OfflineNodeCommand}. + * + * @param cause + * Record the note about why you are disconnecting this node */ - @CLIMethod(name="offline-node") - public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException { + @Deprecated + public void cliOffline(String cause) throws ExecutionException, InterruptedException { checkPermission(DISCONNECT); setTemporarilyOffline(true, new ByCLI(cause)); } diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index 9f223cedc5..14330684d9 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -90,3 +90,5 @@ ConnectNodeCommand.ShortDescription=Reconnect to a node(s) DisconnectNodeCommand.ShortDescription=Disconnects from a node. QuietDownCommand.ShortDescription=Quiet down Jenkins, in preparation for a restart. Don\u2019t start any builds. CancelQuietDownCommand.ShortDescription=Cancel the effect of the "quiet-down" command. +OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. + diff --git a/core/src/main/resources/hudson/cli/Messages_bg.properties b/core/src/main/resources/hudson/cli/Messages_bg.properties index 68c5cc94ee..e2fe289895 100644 --- a/core/src/main/resources/hudson/cli/Messages_bg.properties +++ b/core/src/main/resources/hudson/cli/Messages_bg.properties @@ -134,3 +134,7 @@ QuietDownCommand.ShortDescription=\ \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043d\u0430 Jenkins \u0437\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435. \u0414\u0430 \u043d\u0435 \u0441\u0435 \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0442 \u043d\u043e\u0432\u0438 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f. CancelQuietDownCommand.ShortDescription=\ \u041e\u0442\u043c\u044f\u043d\u0430 \u043d\u0430 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u2014 \u0449\u0435 \u0441\u0435 \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0442 \u043d\u043e\u0432\u0438 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f. +OfflineNodeCommand.ShortDescription=\ + \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f \u0434\u043e\u043a\u0430\u0442\u043e \u043d\u0435 \u0441\u0435\ + \u0432\u044a\u0432\u0435\u0434\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430 \u201eonline-node\u201c. + diff --git a/core/src/main/resources/hudson/cli/Messages_da.properties b/core/src/main/resources/hudson/cli/Messages_da.properties index 63bd44a17a..bfd9e8d040 100644 --- a/core/src/main/resources/hudson/cli/Messages_da.properties +++ b/core/src/main/resources/hudson/cli/Messages_da.properties @@ -8,3 +8,5 @@ DisconnectNodeCommand.ShortDescription=Afbryder forbindelsen til en node ConnectNodeCommand.ShortDescription=Gentilslut en node QuietDownCommand.ShortDescription=Forbereder nedlukning, starter ingen nye byg. CancelQuietDownCommand.ShortDescription=Sl\u00e5 effekten af "quite-down" kommandoen fra. +OfflineNodeCommand.ShortDescription=Hold midlertidigt op med at bygge p\u00e5 en node, indtil n\u00e6ste "online-node" kommando. + diff --git a/core/src/main/resources/hudson/cli/Messages_de.properties b/core/src/main/resources/hudson/cli/Messages_de.properties index 1469580d2c..287aeef516 100644 --- a/core/src/main/resources/hudson/cli/Messages_de.properties +++ b/core/src/main/resources/hudson/cli/Messages_de.properties @@ -7,3 +7,5 @@ ConnectNodeCommand.ShortDescription=Erneut mit Knoten verbinden. DisconnectNodeCommand.ShortDescription=Knoten trennen. QuietDownCommand.ShortDescription=Jenkins' Aktivit\u00e4t reduzieren, z.B. zur Vorbereitung eines Neustarts. Es werden keine neuen Builds mehr gestartet. CancelQuietDownCommand.ShortDescription=Wirkung des Befehls "quiet-down" wieder aufheben. +OfflineNodeCommand.ShortDescription=Knoten wird bis zum n\u00e4chsten "online-node"-Kommando f\u00fcr keine neuen Builds verwendet. + diff --git a/core/src/main/resources/hudson/cli/Messages_es.properties b/core/src/main/resources/hudson/cli/Messages_es.properties index ed2d492031..31f6ba1279 100644 --- a/core/src/main/resources/hudson/cli/Messages_es.properties +++ b/core/src/main/resources/hudson/cli/Messages_es.properties @@ -56,3 +56,5 @@ ConnectNodeCommand.ShortDescription=Reconectarse con un nodo DisconnectNodeCommand.ShortDescription=Desconectarse de un nodo QuietDownCommand.ShortDescription=Poner Jenkins en modo quieto y estar preparado para un reinicio. No comenzar ninguna ejecuci\u00f3n. CancelQuietDownCommand.ShortDescription=Cancelar el efecto del comando "quiet-down". +OfflineNodeCommand.ShortDescription=Dejar de utilizar un nodo temporalmente hasta que se ejecute el comando "online-node". + diff --git a/core/src/main/resources/hudson/cli/Messages_it.properties b/core/src/main/resources/hudson/cli/Messages_it.properties index 0b7c38c2b6..195737cade 100644 --- a/core/src/main/resources/hudson/cli/Messages_it.properties +++ b/core/src/main/resources/hudson/cli/Messages_it.properties @@ -4,3 +4,4 @@ OnlineNodeCommand.ShortDescription=Resume using a node for performing builds, to ClearQueueCommand.ShortDescription=Pulisce la coda di lavoro ReloadConfigurationCommand.ShortDescription=Discard all the loaded data in memory and reload everything from file system. Useful when you modified config files directly on disk. ConnectNodeCommand.ShortDescription=Riconnettersi ad un nodo +OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. diff --git a/core/src/main/resources/hudson/cli/Messages_ja.properties b/core/src/main/resources/hudson/cli/Messages_ja.properties index 44578a51aa..0e7f20e2d6 100644 --- a/core/src/main/resources/hudson/cli/Messages_ja.properties +++ b/core/src/main/resources/hudson/cli/Messages_ja.properties @@ -78,3 +78,5 @@ ConnectNodeCommand.ShortDescription=\u30ce\u30fc\u30c9\u3068\u518d\u63a5\u7d9a\u DisconnectNodeCommand.ShortDescription=\u30ce\u30fc\u30c9\u3068\u306e\u63a5\u7d9a\u3092\u5207\u65ad\u3057\u307e\u3059\u3002 QuietDownCommand.ShortDescription=Jenkins\u306f\u518d\u8d77\u52d5\u306b\u5411\u3051\u3066\u7d42\u4e86\u51e6\u7406\u3092\u5b9f\u65bd\u4e2d\u3067\u3059\u3002\u30d3\u30eb\u30c9\u3092\u958b\u59cb\u3057\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002 CancelQuietDownCommand.ShortDescription="quiet-down"\u30b3\u30de\u30f3\u30c9\u306e\u51e6\u7406\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u307e\u3059\u3002 +OfflineNodeCommand.ShortDescription="online-node"\u30b3\u30de\u30f3\u30c9\u304c\u5b9f\u884c\u3055\u308c\u308b\u307e\u3067\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u4e00\u6642\u7684\u306b\u505c\u6b62\u3057\u307e\u3059\u3002 + diff --git a/core/src/main/resources/hudson/cli/Messages_lt.properties b/core/src/main/resources/hudson/cli/Messages_lt.properties index ef12b07b2f..ce8a4e6b28 100644 --- a/core/src/main/resources/hudson/cli/Messages_lt.properties +++ b/core/src/main/resources/hudson/cli/Messages_lt.properties @@ -82,14 +82,12 @@ BuildCommand.CLICause.CannotBuildUnknownReasons=\ Negalima vykdyti {0} d\u0117l ne\u017einom\u0173 prie\u017eas\u010di\u0173. DeleteNodeCommand.ShortDescription=Trina mazg\u0105(-us) - ReloadJobCommand.ShortDescription=I\u0161 naujo \u012fkelti darb\u0105(-us) - OnlineNodeCommand.ShortDescription=V\u0117l naudoti mazg\u0105 vykdymams, atjungiant ankstesn\u012f re\u017eim\u0105 \u201eatsijung\u0119s\u201c. - ClearQueueCommand.ShortDescription=Valo vykdymo eil\u0119. ReloadConfigurationCommand.ShortDescription=I\u0161mesti visus atminties duomenis ir visk\u0105 \u012fkelti i\u0161 fail\u0173 sistemos. Naudinga, jei konfig\u016bracinius failus keit\u0117te tiesiai diske. - DisconnectNodeCommand.ShortDescription=Atsijungia nuo mazgo. QuietDownCommand.ShortDescription=Tyliai nuleisti Jenkins\u0105, pasiruo\u0161iant paleidimui i\u0161 naujo. Neprad\u0117ti joki\u0173 vykdym\u0173. CancelQuietDownCommand.ShortDescription=Nutraukti \u201etylaus i\u0161jungimo\u201c komandos efekt\u0105. +OfflineNodeCommand.ShortDescription=Laikinai nebenaudoti mazgo darb\u0173 vykdymui, kol bus \u012fvykdyta kita \u201emazgas \u012fjungtas\u201c komanda. + diff --git a/core/src/main/resources/hudson/cli/Messages_pt_BR.properties b/core/src/main/resources/hudson/cli/Messages_pt_BR.properties index d51546dc17..7c47e2fc3d 100644 --- a/core/src/main/resources/hudson/cli/Messages_pt_BR.properties +++ b/core/src/main/resources/hudson/cli/Messages_pt_BR.properties @@ -124,3 +124,5 @@ ConnectNodeCommand.ShortDescription=Reconectar ao n\u00f3 DisconnectNodeCommand.ShortDescription=Desconectar do n\u00f3 QuietDownCommand.ShortDescription=Desativar em modo silencioso, em prepara\u00e7\u00e3o para o rein\u00edcio. N\u00e3o come\u00e7e nenhum build. CancelQuietDownCommand.ShortDescription=Cancela o comando "quiet-down" +OfflineNodeCommand.ShortDescription=Temporariamente n\u00e3o usando um n\u00f3 para os builds. Aguardando o pr\u00f3ximo comando de modo-online + diff --git a/core/src/main/resources/hudson/cli/Messages_zh_CN.properties b/core/src/main/resources/hudson/cli/Messages_zh_CN.properties index 5365b3ec1a..c767020ed3 100644 --- a/core/src/main/resources/hudson/cli/Messages_zh_CN.properties +++ b/core/src/main/resources/hudson/cli/Messages_zh_CN.properties @@ -6,3 +6,5 @@ ReloadConfigurationCommand.ShortDescription=Discard all the loaded data in memor ConnectNodeCommand.ShortDescription=Reconnect to a node QuietDownCommand.ShortDescription=Quiet down Jenkins, in preparation for a restart. Don''t start any builds. CancelQuietDownCommand.ShortDescription=Cancel the effect of the "quiet-down" command. +OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. + diff --git a/core/src/main/resources/hudson/cli/Messages_zh_TW.properties b/core/src/main/resources/hudson/cli/Messages_zh_TW.properties index 7fb1008db9..bd85c6bce1 100644 --- a/core/src/main/resources/hudson/cli/Messages_zh_TW.properties +++ b/core/src/main/resources/hudson/cli/Messages_zh_TW.properties @@ -81,3 +81,5 @@ ReloadConfigurationCommand.ShortDescription=\u653e\u68c4\u6240\u6709\u8a18\u61b6 ConnectNodeCommand.ShortDescription=\u9023\u7dda\u5230\u6307\u5b9a\u7bc0\u9ede\u3002 QuietDownCommand.ShortDescription=\u8b93 Jenkins \u6c89\u6fb1\u4e00\u4e0b\uff0c\u6e96\u5099\u91cd\u65b0\u555f\u52d5\u3002\u5148\u4e0d\u8981\u518d\u5efa\u7f6e\u4efb\u4f55\u4f5c\u696d\u3002 CancelQuietDownCommand.ShortDescription=\u53d6\u6d88 "quiet-down" \u6307\u4ee4\u3002 +OfflineNodeCommand.ShortDescription=\u66ab\u6642\u4e0d\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u76f4\u5230\u57f7\u884c "online-node" \u6307\u4ee4\u70ba\u6b62\u3002 + diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index 46e360d433..c2a1574cfb 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -95,7 +95,6 @@ CLI.clear-queue.shortDescription=Clears the build queue. CLI.disable-job.shortDescription=Disables a job. CLI.enable-job.shortDescription=Enables a job. CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command. -CLI.offline-node.shortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. CLI.wait-node-online.shortDescription=Wait for a node to become online. CLI.wait-node-offline.shortDescription=Wait for a node to become offline. diff --git a/core/src/main/resources/hudson/model/Messages_bg.properties b/core/src/main/resources/hudson/model/Messages_bg.properties index 3cb62a1d60..19ce0ac210 100644 --- a/core/src/main/resources/hudson/model/Messages_bg.properties +++ b/core/src/main/resources/hudson/model/Messages_bg.properties @@ -148,9 +148,6 @@ CLI.delete-node.shortDescription=\ CLI.online-node.shortDescription=\ \u041e\u0442\u043d\u043e\u0432\u043e \u0434\u0430 \u0441\u0435 \u043f\u043e\u043b\u0437\u0432\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f. \u041e\u0442\u043c\u0435\u043d\u044f\u043d\u0435 \u043d\u0430 \u043f\u0440\u0435\u0434\u0445\u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\ \u201eoffline-node\u201c. -CLI.offline-node.shortDescription=\ - \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f \u0434\u043e\u043a\u0430\u0442\u043e \u043d\u0435 \u0441\u0435\ - \u0432\u044a\u0432\u0435\u0434\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430 \u201eonline-node\u201c. CLI.wait-node-online.shortDescription=\ \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f. CLI.wait-node-offline.shortDescription=\ diff --git a/core/src/main/resources/hudson/model/Messages_da.properties b/core/src/main/resources/hudson/model/Messages_da.properties index af79835829..6cd683c17a 100644 --- a/core/src/main/resources/hudson/model/Messages_da.properties +++ b/core/src/main/resources/hudson/model/Messages_da.properties @@ -153,7 +153,6 @@ Job.NoRecentBuildFailed=Ingen byg har fejlet for nyligt. Permalink.LastUnsuccessfulBuild=Seneste fejlede byg RunParameterDefinition.DisplayName=K\u00f8rselsparameter Hudson.JobAlreadyExists=Et job eksisterer allerede med navnet ''{0}'' -CLI.offline-node.shortDescription=Hold midlertidigt op med at bygge p\u00e5 en node, indtil n\u00e6ste "online-node" kommando. MyViewsProperty.DisplayName=Mine visninger UpdateCenter.PluginCategory.must-be-labeled=Ukatagoriserede AbstractProject.Disabled=Byg sl\u00e5et fra diff --git a/core/src/main/resources/hudson/model/Messages_de.properties b/core/src/main/resources/hudson/model/Messages_de.properties index a160fefc7b..08765ea186 100644 --- a/core/src/main/resources/hudson/model/Messages_de.properties +++ b/core/src/main/resources/hudson/model/Messages_de.properties @@ -79,7 +79,6 @@ CLI.clear-queue.shortDescription=Build-Warteschlange l\u00f6schen. CLI.disable-job.shortDescription=Job deaktivieren. CLI.enable-job.shortDescription=Job aktivieren. CLI.disconnect-node.shortDescription=Knoten trennen. -CLI.offline-node.shortDescription=Knoten wird bis zum n\u00e4chsten "online-node"-Kommando f\u00fcr keine neuen Builds verwendet. CLI.safe-restart.shortDescription=Startet Jenkins neu. Queue.init=Build-Warteschlange neu initialisieren diff --git a/core/src/main/resources/hudson/model/Messages_es.properties b/core/src/main/resources/hudson/model/Messages_es.properties index 4d8a67d60d..ff4cbe2688 100644 --- a/core/src/main/resources/hudson/model/Messages_es.properties +++ b/core/src/main/resources/hudson/model/Messages_es.properties @@ -59,7 +59,6 @@ BallColor.Unstable=Inestable CLI.disable-job.shortDescription=Desactivar una tarea CLI.enable-job.shortDescription=Activar una tarea CLI.online-node.shortDescription=Continuar usando un nodo y candelar el comando "offline-node" mas reciente. -CLI.offline-node.shortDescription=Dejar de utilizar un nodo temporalmente hasta que se ejecute el comando "online-node". Computer.Caption=Remoto {0} Computer.Permissions.Title=Nodo diff --git a/core/src/main/resources/hudson/model/Messages_it.properties b/core/src/main/resources/hudson/model/Messages_it.properties index 67ec7630c1..2cd2acf307 100644 --- a/core/src/main/resources/hudson/model/Messages_it.properties +++ b/core/src/main/resources/hudson/model/Messages_it.properties @@ -73,7 +73,6 @@ BallColor.Unstable=Instabile CLI.disable-job.shortDescription=Disabilita un job CLI.enable-job.shortDescription=Abilita un job CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command. -CLI.offline-node.shortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. CLI.wait-node-online.shortDescription=Attende che il nodo sia attivo CLI.wait-node-offline.shortDescription=Attende che un nodo sia disattivo diff --git a/core/src/main/resources/hudson/model/Messages_ja.properties b/core/src/main/resources/hudson/model/Messages_ja.properties index 2699e4fcc8..e9aea8f9d6 100644 --- a/core/src/main/resources/hudson/model/Messages_ja.properties +++ b/core/src/main/resources/hudson/model/Messages_ja.properties @@ -295,7 +295,6 @@ CLI.clear-queue.shortDescription=\u30d3\u30eb\u30c9\u30ad\u30e5\u30fc\u3092\u30a CLI.disable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u7121\u52b9\u5316\u3057\u307e\u3059\u3002 CLI.enable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u6709\u52b9\u5316\u3057\u307e\u3059\u3002 CLI.online-node.shortDescription=\u76f4\u524d\u306b\u5b9f\u884c\u3057\u305f"online-node"\u30b3\u30de\u30f3\u30c9\u3092\u53d6\u308a\u6d88\u3057\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u518d\u958b\u3057\u307e\u3059\u3002 -CLI.offline-node.shortDescription="online-node"\u30b3\u30de\u30f3\u30c9\u304c\u5b9f\u884c\u3055\u308c\u308b\u307e\u3067\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u4e00\u6642\u7684\u306b\u505c\u6b62\u3057\u307e\u3059\u3002 CLI.wait-node-online.shortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30f3\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002 CLI.wait-node-offline.shortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002 diff --git a/core/src/main/resources/hudson/model/Messages_lt.properties b/core/src/main/resources/hudson/model/Messages_lt.properties index b80d8ca16a..13bb1cad49 100644 --- a/core/src/main/resources/hudson/model/Messages_lt.properties +++ b/core/src/main/resources/hudson/model/Messages_lt.properties @@ -70,7 +70,6 @@ CLI.clear-queue.shortDescription=I\u0161valo vykdymo eil\u0119. CLI.disable-job.shortDescription=I\u0161jungia darb\u0105. CLI.enable-job.shortDescription=\u012ejungia darb\u0105. CLI.disconnect-node.shortDescription=Atsijungia nuo mazgo. -CLI.offline-node.shortDescription=Laikinai nebenaudoti mazgo darb\u0173 vykdymui, kol bus \u012fvykdyta kita \u201emazgas \u012fjungtas\u201c komanda. CLI.wait-node-online.shortDescription=Laukti, kol mazgas prisijungs. CLI.wait-node-offline.shortDescription=Laukti, kol mazgas atsijungs. diff --git a/core/src/main/resources/hudson/model/Messages_pt_BR.properties b/core/src/main/resources/hudson/model/Messages_pt_BR.properties index 550313d9eb..8e837a6441 100644 --- a/core/src/main/resources/hudson/model/Messages_pt_BR.properties +++ b/core/src/main/resources/hudson/model/Messages_pt_BR.properties @@ -271,8 +271,6 @@ Hudson.NodeDescription=N\u00f3 master do Jenkins Permalink.LastUnsuccessfulBuild=\u00daltimo build que falhou # Run Parameter RunParameterDefinition.DisplayName=Rodar o par\u00e2metro -# Stop using a node for performing builds temporarily, until the next "online-node" command. -CLI.offline-node.shortDescription=Temporariamente n\u00e3o usando um n\u00f3 para os builds. Aguardando o pr\u00f3ximo comando de modo-online # My Views MyViewsProperty.DisplayName=Minhas views # Uncategorized diff --git a/core/src/main/resources/hudson/model/Messages_zh_CN.properties b/core/src/main/resources/hudson/model/Messages_zh_CN.properties index af1d3635d5..1d07150da1 100644 --- a/core/src/main/resources/hudson/model/Messages_zh_CN.properties +++ b/core/src/main/resources/hudson/model/Messages_zh_CN.properties @@ -64,7 +64,6 @@ CLI.disable-job.shortDescription=Disables a job CLI.enable-job.shortDescription=Enables a job CLI.disconnect-node.shortDescription=Disconnects from a node CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command. -CLI.offline-node.shortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. ComputerSet.DisplayName=nodes diff --git a/core/src/main/resources/hudson/model/Messages_zh_TW.properties b/core/src/main/resources/hudson/model/Messages_zh_TW.properties index a5fb398930..011fdf5d31 100644 --- a/core/src/main/resources/hudson/model/Messages_zh_TW.properties +++ b/core/src/main/resources/hudson/model/Messages_zh_TW.properties @@ -80,7 +80,6 @@ CLI.disable-job.shortDescription=\u505c\u7528\u4f5c\u696d\u3002 CLI.enable-job.shortDescription=\u555f\u7528\u4f5c\u696d\u3002 CLI.disconnect-node.shortDescription=\u4e2d\u65b7\u8207\u6307\u5b9a\u7bc0\u9ede\u7684\u9023\u7dda\u3002 CLI.online-node.shortDescription=\u7e7c\u7e8c\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u53d6\u6d88\u5148\u524d\u7684 "offline-node" \u6307\u4ee4\u3002 -CLI.offline-node.shortDescription=\u66ab\u6642\u4e0d\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u76f4\u5230\u57f7\u884c "online-node" \u6307\u4ee4\u70ba\u6b62\u3002 CLI.wait-node-online.shortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u4e0a\u7dda\u3002 CLI.wait-node-offline.shortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u96e2\u7dda\u3002 diff --git a/test/src/test/java/hudson/cli/HelpCommandTest.java b/test/src/test/java/hudson/cli/HelpCommandTest.java index 26c2da6ccd..7ed7283dea 100644 --- a/test/src/test/java/hudson/cli/HelpCommandTest.java +++ b/test/src/test/java/hudson/cli/HelpCommandTest.java @@ -100,9 +100,9 @@ public class HelpCommandTest { } private void assertContainsUsageOfMethodCommand(String text) { - assertThat(text, containsString("offline-node NAME [-m VAL]")); + assertThat(text, containsString("offline-node NAME ... [-m VAL]")); assertThat(text, containsStrings("NAME", "Agent name, or empty string for master")); - assertThat(text, containsStrings("-m VAL", "Record the note about why you are disconnecting this node")); + assertThat(text, containsStrings("-m VAL", "Record the reason about why you are disconnecting this node")); } private static Matcher containsStrings(String... strings) { diff --git a/test/src/test/java/hudson/cli/OfflineNodeCommandTest.java b/test/src/test/java/hudson/cli/OfflineNodeCommandTest.java new file mode 100644 index 0000000000..a1623f853b --- /dev/null +++ b/test/src/test/java/hudson/cli/OfflineNodeCommandTest.java @@ -0,0 +1,482 @@ +/* + * The MIT License + * + * Copyright 2016 Red Hat, Inc. + * + * 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. + */ + +/** + * @author pjanouse + */ + +package hudson.cli; + +import hudson.model.Computer; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.slaves.DumbSlave; +import hudson.slaves.OfflineCause; +import hudson.util.OneShotEvent; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import java.util.concurrent.Future; + +import static hudson.cli.CLICommandInvoker.Matcher.failedWith; +import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput; +import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; + +public class OfflineNodeCommandTest { + + private CLICommandInvoker command; + + @Rule + public final JenkinsRule j = new JenkinsRule(); + + @Before + public void setUp() { + command = new CLICommandInvoker(j, "offline-node"); + } + + @Test + public void offlineNodeShouldFailWithoutComputerDisconnectPermission() throws Exception { + j.createSlave("aNode", "", null); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, failedWith(6)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: user is missing the Agent/Disconnect permission")); + assertThat(result.stderr(), not(containsString("ERROR: Error occured while performing this command, see previous stderr output."))); + } + + @Test + public void offlineNodeShouldFailIfNodeDoesNotExist() throws Exception { + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("never_created"); + assertThat(result, failedWith(3)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: No such agent \"never_created\" exists.")); + assertThat(result.stderr(), not(containsString("ERROR: Error occured while performing this command, see previous stderr output."))); + } + + @Test + public void offlineNodeShouldSucceed() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeShouldSucceedOnOnlineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + assertThat(slave.toComputer().isOnline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(false)); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeShouldSucceedOnOfflineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().setTemporarilyOffline(true, null); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeShouldSucceedOnDisconnectedNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + assertThat(slave.toComputer().isOnline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + slave.toComputer().disconnect(); + slave.toComputer().waitUntilOffline(); + assertThat(slave.toComputer().isOnline(), equalTo(false)); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(false)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOnline(), equalTo(false)); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeShouldSucceedWithCause() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeShouldSucceedOnOnlineNodeWithCause() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + assertThat(slave.toComputer().isOnline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(false)); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeShouldSucceedOnOfflineNodeWithCause() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().setTemporarilyOffline(true, null); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeShouldSucceedOnDisconnectedNodeWithCause() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + assertThat(slave.toComputer().isOnline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), equalTo(null)); + slave.toComputer().disconnect(); + slave.toComputer().waitUntilOffline(); + assertThat(slave.toComputer().isOnline(), equalTo(false)); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(false)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOnline(), equalTo(false)); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeShouldSucceedOnBuildingNode() throws Exception { + final OneShotEvent finish = new OneShotEvent(); + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.setAssignedNode(slave); + final Future build = OnlineNodeCommandTest.startBlockingAndFinishingBuild(project, finish); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + slave.toComputer().waitUntilOffline(); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo(null)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); + assertThat(project.isBuilding(), equalTo(true)); + + finish.signal(); + build.get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); + assertThat(project.isBuilding(), equalTo(false)); + j.assertBuildStatusSuccess(build); + } + + @Test + public void offlineNodeShouldSucceedOnBuildingNodeWithCause() throws Exception { + final OneShotEvent finish = new OneShotEvent(); + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.setAssignedNode(slave); + final Future build = OnlineNodeCommandTest.startBlockingAndFinishingBuild(project, finish); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode", "-m", "aCause"); + assertThat(result, succeededSilently()); + slave.toComputer().waitUntilOffline(); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + assertThat(slave.toComputer().isTemporarilyOffline(), equalTo(true)); + assertThat(slave.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave.toComputer().getOfflineCause()).message, equalTo("aCause")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); + assertThat(project.isBuilding(), equalTo(true)); + + finish.signal(); + build.get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); + assertThat(project.isBuilding(), equalTo(false)); + j.assertBuildStatusSuccess(build); + } + + @Test + public void offlineNodeManyShouldSucceed() throws Exception { + DumbSlave slave1 = j.createSlave("aNode1", "", null); + DumbSlave slave2 = j.createSlave("aNode2", "", null); + DumbSlave slave3 = j.createSlave("aNode3", "", null); + slave1.toComputer().waitUntilOnline(); + assertThat(slave1.toComputer().isOnline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), equalTo(null)); + slave2.toComputer().waitUntilOnline(); + assertThat(slave2.toComputer().isOnline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), equalTo(null)); + slave3.toComputer().waitUntilOnline(); + assertThat(slave3.toComputer().isOnline(), equalTo(true)); + assertThat(slave3.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode1", "aNode2", "aNode3"); + assertThat(result, succeededSilently()); + assertThat(slave1.toComputer().isOffline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave1.toComputer().getOfflineCause()).message, equalTo(null)); + assertThat(slave2.toComputer().isOffline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave2.toComputer().getOfflineCause()).message, equalTo(null)); + assertThat(slave3.toComputer().isOffline(), equalTo(true)); + assertThat(slave3.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave3.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeManyShouldSucceedWithCause() throws Exception { + DumbSlave slave1 = j.createSlave("aNode1", "", null); + DumbSlave slave2 = j.createSlave("aNode2", "", null); + DumbSlave slave3 = j.createSlave("aNode3", "", null); + slave1.toComputer().waitUntilOnline(); + assertThat(slave1.toComputer().isOnline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), equalTo(null)); + slave2.toComputer().waitUntilOnline(); + assertThat(slave2.toComputer().isOnline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), equalTo(null)); + slave3.toComputer().waitUntilOnline(); + assertThat(slave3.toComputer().isOnline(), equalTo(true)); + assertThat(slave3.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode1", "aNode2", "aNode3", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(slave1.toComputer().isOffline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave1.toComputer().getOfflineCause()).message, equalTo("aCause")); + assertThat(slave2.toComputer().isOffline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave2.toComputer().getOfflineCause()).message, equalTo("aCause")); + assertThat(slave3.toComputer().isOffline(), equalTo(true)); + assertThat(slave3.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave3.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeManyShouldFailIfANodeDoesNotExist() throws Exception { + DumbSlave slave1 = j.createSlave("aNode1", "", null); + DumbSlave slave2 = j.createSlave("aNode2", "", null); + slave1.toComputer().waitUntilOnline(); + assertThat(slave1.toComputer().isOnline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), equalTo(null)); + slave2.toComputer().waitUntilOnline(); + assertThat(slave2.toComputer().isOnline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode1", "aNode2", "never_created"); + assertThat(result, failedWith(5)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("never_created: No such agent \"never_created\" exists. Did you mean \"aNode1\"?")); + assertThat(result.stderr(), containsString("ERROR: Error occured while performing this command, see previous stderr output.")); + assertThat(slave1.toComputer().isOffline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave1.toComputer().getOfflineCause()).message, equalTo(null)); + assertThat(slave2.toComputer().isOffline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave2.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeManyShouldFailIfANodeDoesNotExistWithCause() throws Exception { + DumbSlave slave1 = j.createSlave("aNode1", "", null); + DumbSlave slave2 = j.createSlave("aNode2", "", null); + slave1.toComputer().waitUntilOnline(); + assertThat(slave1.toComputer().isOnline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), equalTo(null)); + slave2.toComputer().waitUntilOnline(); + assertThat(slave2.toComputer().isOnline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode1", "aNode2", "never_created", "-m", "aCause"); + assertThat(result, failedWith(5)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("never_created: No such agent \"never_created\" exists. Did you mean \"aNode1\"?")); + assertThat(result.stderr(), containsString("ERROR: Error occured while performing this command, see previous stderr output.")); + assertThat(slave1.toComputer().isOffline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave1.toComputer().getOfflineCause()).message, equalTo("aCause")); + assertThat(slave2.toComputer().isOffline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave2.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeManyShouldSucceedEvenANodeIsSpecifiedTwice() throws Exception { + DumbSlave slave1 = j.createSlave("aNode1", "", null); + DumbSlave slave2 = j.createSlave("aNode2", "", null); + slave1.toComputer().waitUntilOnline(); + assertThat(slave1.toComputer().isOnline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), equalTo(null)); + slave2.toComputer().waitUntilOnline(); + assertThat(slave2.toComputer().isOnline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode1", "aNode2", "aNode1"); + assertThat(result, succeededSilently()); + assertThat(slave1.toComputer().isOffline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave1.toComputer().getOfflineCause()).message, equalTo(null)); + assertThat(slave2.toComputer().isOffline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave2.toComputer().getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeManyShouldSucceedEvenANodeIsSpecifiedTwiceWithCause() throws Exception { + DumbSlave slave1 = j.createSlave("aNode1", "", null); + DumbSlave slave2 = j.createSlave("aNode2", "", null); + slave1.toComputer().waitUntilOnline(); + assertThat(slave1.toComputer().isOnline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), equalTo(null)); + slave2.toComputer().waitUntilOnline(); + assertThat(slave2.toComputer().isOnline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), equalTo(null)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("aNode1", "aNode2", "aNode1", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(slave1.toComputer().isOffline(), equalTo(true)); + assertThat(slave1.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave1.toComputer().getOfflineCause()).message, equalTo("aCause")); + assertThat(slave2.toComputer().isOffline(), equalTo(true)); + assertThat(slave2.toComputer().getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) slave2.toComputer().getOfflineCause()).message, equalTo("aCause")); + } + + @Test + public void offlineNodeShouldSucceedOnMaster() throws Exception { + final Computer masterComputer = Jenkins.getActiveInstance().getComputer(""); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs(""); + assertThat(result, succeededSilently()); + assertThat(masterComputer.isOffline(), equalTo(true)); + assertThat(masterComputer.isTemporarilyOffline(), equalTo(true)); + assertThat(masterComputer.getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) masterComputer.getOfflineCause()).message, equalTo(null)); + } + + @Test + public void offlineNodeShouldSucceedOnMasterWithCause() throws Exception { + final Computer masterComputer = Jenkins.getActiveInstance().getComputer(""); + + final CLICommandInvoker.Result result = command + .authorizedTo(Computer.DISCONNECT, Jenkins.READ) + .invokeWithArgs("", "-m", "aCause"); + assertThat(result, succeededSilently()); + assertThat(masterComputer.isOffline(), equalTo(true)); + assertThat(masterComputer.isTemporarilyOffline(), equalTo(true)); + assertThat(masterComputer.getOfflineCause(), instanceOf(OfflineCause.ByCLI.class)); + assertThat(((OfflineCause.ByCLI) masterComputer.getOfflineCause()).message, equalTo("aCause")); + } +} From a06c2a4b1141ea5e101399629f78a318eda58180 Mon Sep 17 00:00:00 2001 From: Sebastien Guillemot Date: Wed, 20 Jul 2016 13:29:38 -0700 Subject: [PATCH 23/89] [FIXED JENKINS-36775] - No longer hard-code the icon for the executor panel (#2464) * no longer hard-code the icon for the executor panel [FIXED JENKINS-36775] * add documentation to Computer.java for custom icons * add recommended implementation to the javadoc --- core/src/main/java/hudson/model/Computer.java | 20 +++++++++++++++++++ .../main/resources/lib/hudson/executors.jelly | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 7db02878ae..bbdb5be498 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -68,6 +68,8 @@ import jenkins.model.Jenkins; import jenkins.util.ContextResettingExecutorService; import jenkins.security.MasterToSlaveCallable; +import org.jenkins.ui.icon.Icon; +import org.jenkins.ui.icon.IconSet; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -721,6 +723,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } } + /** + * Returns the icon for this computer. + * + * It is both the recommended and default implementation to serve different icons based on {@link #isOffline} + * + * @see #getIconClassName() + */ @Exported public String getIcon() { if(isOffline()) @@ -729,6 +738,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces return "computer.png"; } + /** + * Returns the class name that will be used to lookup the icon. + * + * This class name will be added as a class tag to the html img tags where the icon should + * show up followed by a size specifier given by {@link Icon#toNormalizedIconSizeClass(String)} + * The conversion of class tag to src tag is registered through {@link IconSet#addIcon(Icon)} + * + * It is both the recommended and default implementation to serve different icons based on {@link #isOffline} + * + * @see #getIcon() + */ @Exported public String getIconClassName() { if(isOffline()) diff --git a/core/src/main/resources/lib/hudson/executors.jelly b/core/src/main/resources/lib/hudson/executors.jelly index 125cb61922..99c4c5b77e 100644 --- a/core/src/main/resources/lib/hudson/executors.jelly +++ b/core/src/main/resources/lib/hudson/executors.jelly @@ -34,7 +34,7 @@ THE SOFTWARE. - ${title} + ${title} (${%offline}) (${%suspended}) From cb8e9ec5e96eb6b2903f47917240c8b123e6c197 Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Mon, 23 May 2016 08:36:43 +0200 Subject: [PATCH 24/89] [JENKINS-34915] CLI commands wait-node-online/wait-node-offline extracted from core to CLI wait-node-online and wait-node-offline covered by test-cases --- .../hudson/cli/WaitNodeOfflineCommand.java | 51 +++++ .../hudson/cli/WaitNodeOnlineCommand.java | 51 +++++ core/src/main/java/hudson/model/Computer.java | 2 - .../resources/hudson/cli/Messages.properties | 2 + .../hudson/cli/Messages_bg.properties | 4 + .../hudson/cli/Messages_es.properties | 2 + .../hudson/cli/Messages_it.properties | 3 + .../hudson/cli/Messages_ja.properties | 2 + .../hudson/cli/Messages_lt.properties | 2 + .../hudson/cli/Messages_pt_BR.properties | 2 + .../hudson/cli/Messages_zh_TW.properties | 2 + .../hudson/model/Messages.properties | 2 - .../hudson/model/Messages_bg.properties | 4 - .../hudson/model/Messages_es.properties | 2 - .../hudson/model/Messages_it.properties | 4 - .../hudson/model/Messages_ja.properties | 2 - .../hudson/model/Messages_lt.properties | 2 - .../hudson/model/Messages_pt_BR.properties | 4 - .../hudson/model/Messages_zh_TW.properties | 5 - .../cli/WaitNodeOfflineCommandTest.java | 157 +++++++++++++++ .../hudson/cli/WaitNodeOnlineCommandTest.java | 182 ++++++++++++++++++ 21 files changed, 460 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java create mode 100644 core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java create mode 100644 test/src/test/java/hudson/cli/WaitNodeOfflineCommandTest.java create mode 100644 test/src/test/java/hudson/cli/WaitNodeOnlineCommandTest.java diff --git a/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java new file mode 100644 index 0000000000..4c1139e484 --- /dev/null +++ b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Red Hat, Inc. + * + * 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 hudson.cli; + +import hudson.Extension; +import hudson.model.Node; +import org.kohsuke.args4j.Argument; + +/** + * @author pjanouse + * @since TODO + */ +@Extension +public class WaitNodeOfflineCommand extends CLICommand { + + @Argument(metaVar="NODE", usage="Name of the node", required=true) + public Node node; + + @Override + public String getShortDescription() { + + return Messages.WaitNodeOfflineCommand_ShortDescription(); + } + + @Override + protected int run() throws Exception { + node.toComputer().waitUntilOffline(); + return 0; + } +} diff --git a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java new file mode 100644 index 0000000000..10083d8dc6 --- /dev/null +++ b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Red Hat, Inc. + * + * 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 hudson.cli; + +import hudson.Extension; +import hudson.model.Node; +import org.kohsuke.args4j.Argument; + +/** + * @author pjanouse + * @since TODO + */ +@Extension +public class WaitNodeOnlineCommand extends CLICommand { + + @Argument(metaVar="NODE", usage="Name of the node", required=true) + public Node node; + + @Override + public String getShortDescription() { + + return Messages.WaitNodeOnlineCommand_ShortDescription(); + } + + @Override + protected int run() throws Exception { + node.toComputer().waitUntilOnline(); + return 0; + } +} diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index bbdb5be498..00f52e197c 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1494,7 +1494,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces /** * Blocks until the node becomes online/offline. */ - @CLIMethod(name="wait-node-online") public void waitUntilOnline() throws InterruptedException { synchronized (statusChangeLock) { while (!isOnline()) @@ -1502,7 +1501,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } } - @CLIMethod(name="wait-node-offline") public void waitUntilOffline() throws InterruptedException { synchronized (statusChangeLock) { while (!isOffline()) diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index 14330684d9..a6ef44d760 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -91,4 +91,6 @@ DisconnectNodeCommand.ShortDescription=Disconnects from a node. QuietDownCommand.ShortDescription=Quiet down Jenkins, in preparation for a restart. Don\u2019t start any builds. CancelQuietDownCommand.ShortDescription=Cancel the effect of the "quiet-down" command. OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. +WaitNodeOnlineCommand.ShortDescription=Wait for a node to become online. +WaitNodeOfflineCommand.ShortDescription=Wait for a node to become offline. diff --git a/core/src/main/resources/hudson/cli/Messages_bg.properties b/core/src/main/resources/hudson/cli/Messages_bg.properties index e2fe289895..52e27ca91c 100644 --- a/core/src/main/resources/hudson/cli/Messages_bg.properties +++ b/core/src/main/resources/hudson/cli/Messages_bg.properties @@ -137,4 +137,8 @@ CancelQuietDownCommand.ShortDescription=\ OfflineNodeCommand.ShortDescription=\ \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f \u0434\u043e\u043a\u0430\u0442\u043e \u043d\u0435 \u0441\u0435\ \u0432\u044a\u0432\u0435\u0434\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430 \u201eonline-node\u201c. +WaitNodeOnlineCommand.ShortDescription=\ + \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f. +WaitNodeOfflineCommand.ShortDescription=\ + \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u043d\u0435 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f. diff --git a/core/src/main/resources/hudson/cli/Messages_es.properties b/core/src/main/resources/hudson/cli/Messages_es.properties index 31f6ba1279..11525579ca 100644 --- a/core/src/main/resources/hudson/cli/Messages_es.properties +++ b/core/src/main/resources/hudson/cli/Messages_es.properties @@ -57,4 +57,6 @@ DisconnectNodeCommand.ShortDescription=Desconectarse de un nodo QuietDownCommand.ShortDescription=Poner Jenkins en modo quieto y estar preparado para un reinicio. No comenzar ninguna ejecuci\u00f3n. CancelQuietDownCommand.ShortDescription=Cancelar el efecto del comando "quiet-down". OfflineNodeCommand.ShortDescription=Dejar de utilizar un nodo temporalmente hasta que se ejecute el comando "online-node". +WaitNodeOnlineCommand.ShortDescription=Esperando hasta que el nodo esté activado +WaitNodeOfflineCommand.ShortDescription=Esperando a que el nodo esté desactivado diff --git a/core/src/main/resources/hudson/cli/Messages_it.properties b/core/src/main/resources/hudson/cli/Messages_it.properties index 195737cade..ca09f191df 100644 --- a/core/src/main/resources/hudson/cli/Messages_it.properties +++ b/core/src/main/resources/hudson/cli/Messages_it.properties @@ -5,3 +5,6 @@ ClearQueueCommand.ShortDescription=Pulisce la coda di lavoro ReloadConfigurationCommand.ShortDescription=Discard all the loaded data in memory and reload everything from file system. Useful when you modified config files directly on disk. ConnectNodeCommand.ShortDescription=Riconnettersi ad un nodo OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command. +WaitNodeOnlineCommand.ShortDescription=Attende che il nodo sia attivo +WaitNodeOfflineCommand.ShortDescription=Attende che un nodo sia disattivo + diff --git a/core/src/main/resources/hudson/cli/Messages_ja.properties b/core/src/main/resources/hudson/cli/Messages_ja.properties index 0e7f20e2d6..fb1ddd6860 100644 --- a/core/src/main/resources/hudson/cli/Messages_ja.properties +++ b/core/src/main/resources/hudson/cli/Messages_ja.properties @@ -79,4 +79,6 @@ DisconnectNodeCommand.ShortDescription=\u30ce\u30fc\u30c9\u3068\u306e\u63a5\u7d9 QuietDownCommand.ShortDescription=Jenkins\u306f\u518d\u8d77\u52d5\u306b\u5411\u3051\u3066\u7d42\u4e86\u51e6\u7406\u3092\u5b9f\u65bd\u4e2d\u3067\u3059\u3002\u30d3\u30eb\u30c9\u3092\u958b\u59cb\u3057\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002 CancelQuietDownCommand.ShortDescription="quiet-down"\u30b3\u30de\u30f3\u30c9\u306e\u51e6\u7406\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u307e\u3059\u3002 OfflineNodeCommand.ShortDescription="online-node"\u30b3\u30de\u30f3\u30c9\u304c\u5b9f\u884c\u3055\u308c\u308b\u307e\u3067\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u4e00\u6642\u7684\u306b\u505c\u6b62\u3057\u307e\u3059\u3002 +WaitNodeOnlineCommand.ShortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30f3\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002 +WaitNodeOfflineCommand.ShortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002 diff --git a/core/src/main/resources/hudson/cli/Messages_lt.properties b/core/src/main/resources/hudson/cli/Messages_lt.properties index ce8a4e6b28..f83756b5ab 100644 --- a/core/src/main/resources/hudson/cli/Messages_lt.properties +++ b/core/src/main/resources/hudson/cli/Messages_lt.properties @@ -90,4 +90,6 @@ DisconnectNodeCommand.ShortDescription=Atsijungia nuo mazgo. QuietDownCommand.ShortDescription=Tyliai nuleisti Jenkins\u0105, pasiruo\u0161iant paleidimui i\u0161 naujo. Neprad\u0117ti joki\u0173 vykdym\u0173. CancelQuietDownCommand.ShortDescription=Nutraukti \u201etylaus i\u0161jungimo\u201c komandos efekt\u0105. OfflineNodeCommand.ShortDescription=Laikinai nebenaudoti mazgo darb\u0173 vykdymui, kol bus \u012fvykdyta kita \u201emazgas \u012fjungtas\u201c komanda. +WaitNodeOnlineCommand.ShortDescription=Laukti, kol mazgas prisijungs. +WaitNodeOfflineCommand.ShortDescription=Laukti, kol mazgas atsijungs. diff --git a/core/src/main/resources/hudson/cli/Messages_pt_BR.properties b/core/src/main/resources/hudson/cli/Messages_pt_BR.properties index 7c47e2fc3d..7965b0b7e1 100644 --- a/core/src/main/resources/hudson/cli/Messages_pt_BR.properties +++ b/core/src/main/resources/hudson/cli/Messages_pt_BR.properties @@ -125,4 +125,6 @@ DisconnectNodeCommand.ShortDescription=Desconectar do n\u00f3 QuietDownCommand.ShortDescription=Desativar em modo silencioso, em prepara\u00e7\u00e3o para o rein\u00edcio. N\u00e3o come\u00e7e nenhum build. CancelQuietDownCommand.ShortDescription=Cancela o comando "quiet-down" OfflineNodeCommand.ShortDescription=Temporariamente n\u00e3o usando um n\u00f3 para os builds. Aguardando o pr\u00f3ximo comando de modo-online +WaitNodeOnlineCommand.ShortDescription=Aguarda por um n\u00f3 que se torne online +WaitNodeOfflineCommand.ShortDescription=Aguarde por um n\u00f3 ficar offline. diff --git a/core/src/main/resources/hudson/cli/Messages_zh_TW.properties b/core/src/main/resources/hudson/cli/Messages_zh_TW.properties index bd85c6bce1..b2e5746b7a 100644 --- a/core/src/main/resources/hudson/cli/Messages_zh_TW.properties +++ b/core/src/main/resources/hudson/cli/Messages_zh_TW.properties @@ -82,4 +82,6 @@ ConnectNodeCommand.ShortDescription=\u9023\u7dda\u5230\u6307\u5b9a\u7bc0\u9ede\u QuietDownCommand.ShortDescription=\u8b93 Jenkins \u6c89\u6fb1\u4e00\u4e0b\uff0c\u6e96\u5099\u91cd\u65b0\u555f\u52d5\u3002\u5148\u4e0d\u8981\u518d\u5efa\u7f6e\u4efb\u4f55\u4f5c\u696d\u3002 CancelQuietDownCommand.ShortDescription=\u53d6\u6d88 "quiet-down" \u6307\u4ee4\u3002 OfflineNodeCommand.ShortDescription=\u66ab\u6642\u4e0d\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u76f4\u5230\u57f7\u884c "online-node" \u6307\u4ee4\u70ba\u6b62\u3002 +WaitNodeOnlineCommand.ShortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u4e0a\u7dda\u3002 +WaitNodeOfflineCommand.ShortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u96e2\u7dda\u3002 diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index c2a1574cfb..f6d08fbd1f 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -95,8 +95,6 @@ CLI.clear-queue.shortDescription=Clears the build queue. CLI.disable-job.shortDescription=Disables a job. CLI.enable-job.shortDescription=Enables a job. CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command. -CLI.wait-node-online.shortDescription=Wait for a node to become online. -CLI.wait-node-offline.shortDescription=Wait for a node to become offline. Computer.Caption=Agent {0} Computer.NoSuchSlaveExists=No such agent "{0}" exists. Did you mean "{1}"? diff --git a/core/src/main/resources/hudson/model/Messages_bg.properties b/core/src/main/resources/hudson/model/Messages_bg.properties index 19ce0ac210..106bf1d055 100644 --- a/core/src/main/resources/hudson/model/Messages_bg.properties +++ b/core/src/main/resources/hudson/model/Messages_bg.properties @@ -148,10 +148,6 @@ CLI.delete-node.shortDescription=\ CLI.online-node.shortDescription=\ \u041e\u0442\u043d\u043e\u0432\u043e \u0434\u0430 \u0441\u0435 \u043f\u043e\u043b\u0437\u0432\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f. \u041e\u0442\u043c\u0435\u043d\u044f\u043d\u0435 \u043d\u0430 \u043f\u0440\u0435\u0434\u0445\u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\ \u201eoffline-node\u201c. -CLI.wait-node-online.shortDescription=\ - \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f. -CLI.wait-node-offline.shortDescription=\ - \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u043d\u0435 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f. ComputerSet.DisplayName=\ \u041a\u043e\u043c\u043f\u044e\u0442\u0440\u0438 diff --git a/core/src/main/resources/hudson/model/Messages_es.properties b/core/src/main/resources/hudson/model/Messages_es.properties index ff4cbe2688..d1f693619d 100644 --- a/core/src/main/resources/hudson/model/Messages_es.properties +++ b/core/src/main/resources/hudson/model/Messages_es.properties @@ -284,8 +284,6 @@ ResultTrend.Aborted=Cancelado TextParameterDefinition.DisplayName=Parámetro de texto AbstractProject.AssignedLabelString_NoMatch_DidYouMean=No hay ningún nodo que cumpla esta asignación. ¿Quizás se refiera a ''{1}'' en lugar de ''{0}''? ResultTrend.Success=Correcto -CLI.wait-node-online.shortDescription=Esperando hasta que el nodo esté activado -CLI.wait-node-offline.shortDescription=Esperando a que el nodo esté desactivado AbstractProject.CancelPermission.Description=Este permiso permite que se pueda cancelar un trabajo. Run.Summary.NotBuilt=no se ha ejecutado AbstractProject.DiscoverPermission.Description=Este permiso garantiza que se pueda descubrir tareas. \ diff --git a/core/src/main/resources/hudson/model/Messages_it.properties b/core/src/main/resources/hudson/model/Messages_it.properties index 2cd2acf307..8458a59a89 100644 --- a/core/src/main/resources/hudson/model/Messages_it.properties +++ b/core/src/main/resources/hudson/model/Messages_it.properties @@ -73,14 +73,10 @@ BallColor.Unstable=Instabile CLI.disable-job.shortDescription=Disabilita un job CLI.enable-job.shortDescription=Abilita un job CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command. -CLI.wait-node-online.shortDescription=Attende che il nodo sia attivo -CLI.wait-node-offline.shortDescription=Attende che un nodo sia disattivo - ComputerSet.DisplayName=nodes Executor.NotAvailable=N/A - FreeStyleProject.DisplayName=Configura una build di tipo generico FreeStyleProject.Description=Questa \u00E8 la funzione principale di Jenkins. Jenkins effettuer\u00E0 una compilazione del progetto, combinando qualsiasi SCM con qualsiasi sistema di compilazione e si pu\u00F2 usare anche per qualcosa di altro che non sia un programma di compilazione. diff --git a/core/src/main/resources/hudson/model/Messages_ja.properties b/core/src/main/resources/hudson/model/Messages_ja.properties index e9aea8f9d6..3d51ee20c7 100644 --- a/core/src/main/resources/hudson/model/Messages_ja.properties +++ b/core/src/main/resources/hudson/model/Messages_ja.properties @@ -295,8 +295,6 @@ CLI.clear-queue.shortDescription=\u30d3\u30eb\u30c9\u30ad\u30e5\u30fc\u3092\u30a CLI.disable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u7121\u52b9\u5316\u3057\u307e\u3059\u3002 CLI.enable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u6709\u52b9\u5316\u3057\u307e\u3059\u3002 CLI.online-node.shortDescription=\u76f4\u524d\u306b\u5b9f\u884c\u3057\u305f"online-node"\u30b3\u30de\u30f3\u30c9\u3092\u53d6\u308a\u6d88\u3057\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u518d\u958b\u3057\u307e\u3059\u3002 -CLI.wait-node-online.shortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30f3\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002 -CLI.wait-node-offline.shortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002 BuildAuthorizationToken.InvalidTokenProvided=\u8a8d\u8a3c\u30c8\u30fc\u30af\u30f3\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059\u3002 ManageJenkinsAction.DisplayName=Jenkins\u306e\u7ba1\u7406 diff --git a/core/src/main/resources/hudson/model/Messages_lt.properties b/core/src/main/resources/hudson/model/Messages_lt.properties index 13bb1cad49..6be646658f 100644 --- a/core/src/main/resources/hudson/model/Messages_lt.properties +++ b/core/src/main/resources/hudson/model/Messages_lt.properties @@ -70,8 +70,6 @@ CLI.clear-queue.shortDescription=I\u0161valo vykdymo eil\u0119. CLI.disable-job.shortDescription=I\u0161jungia darb\u0105. CLI.enable-job.shortDescription=\u012ejungia darb\u0105. CLI.disconnect-node.shortDescription=Atsijungia nuo mazgo. -CLI.wait-node-online.shortDescription=Laukti, kol mazgas prisijungs. -CLI.wait-node-offline.shortDescription=Laukti, kol mazgas atsijungs. Computer.Caption=Agentas {0} Computer.NoSuchSlaveExists=N\u0117ra tokio agento \u201e{0}\u201c. Ar tur\u0117jote omeny \u201e{1}\u201c? diff --git a/core/src/main/resources/hudson/model/Messages_pt_BR.properties b/core/src/main/resources/hudson/model/Messages_pt_BR.properties index 8e837a6441..4ed07299f1 100644 --- a/core/src/main/resources/hudson/model/Messages_pt_BR.properties +++ b/core/src/main/resources/hudson/model/Messages_pt_BR.properties @@ -347,8 +347,6 @@ AbstractProject.WorkspaceTitle=Workspace de {0} UpdateCenter.PluginCategory.listview-column=Lista as colunas da view # {0} is waiting for a checkpoint on {1} Run._is_waiting_for_a_checkpoint_on_={0} est\u00e1 aguardando por um checkpoint em {1} -# Wait for a node to become offline. -CLI.wait-node-offline.shortDescription=Aguarde por um n\u00f3 ficar offline. # \ in workspace {0} AbstractBuild.BuildingInWorkspace=\ no workspace {0} # Jenkins is restarting @@ -439,8 +437,6 @@ AbstractProject.CancelPermission.Description=Esta permiss\u00e3o deixa que seja Jenkins.CheckDisplayName.NameNotUniqueWarning=O nome de exibi\u00e7\u00e3o "{0}", \u00e9 usado por um job e pode causar confus\u00e3o nos resultados da pesquisa. # Custom workspace is empty. AbstractProject.CustomWorkspaceEmpty=O workspace customizado est\u00e1 vazio. -# Wait for a node to become online. -CLI.wait-node-online.shortDescription=Aguarda por um n\u00f3 que se torne online # Artifacts of {0} {1} Run.ArtifactsBrowserTitle=Artefatos de {0} {1} # Build with Parameters diff --git a/core/src/main/resources/hudson/model/Messages_zh_TW.properties b/core/src/main/resources/hudson/model/Messages_zh_TW.properties index 011fdf5d31..2ffc39e21f 100644 --- a/core/src/main/resources/hudson/model/Messages_zh_TW.properties +++ b/core/src/main/resources/hudson/model/Messages_zh_TW.properties @@ -80,9 +80,6 @@ CLI.disable-job.shortDescription=\u505c\u7528\u4f5c\u696d\u3002 CLI.enable-job.shortDescription=\u555f\u7528\u4f5c\u696d\u3002 CLI.disconnect-node.shortDescription=\u4e2d\u65b7\u8207\u6307\u5b9a\u7bc0\u9ede\u7684\u9023\u7dda\u3002 CLI.online-node.shortDescription=\u7e7c\u7e8c\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u53d6\u6d88\u5148\u524d\u7684 "offline-node" \u6307\u4ee4\u3002 -CLI.wait-node-online.shortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u4e0a\u7dda\u3002 -CLI.wait-node-offline.shortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u96e2\u7dda\u3002 - ComputerSet.DisplayName=\u7bc0\u9ede @@ -90,14 +87,12 @@ Descriptor.From=(from {0}) Executor.NotAvailable=N/A - FreeStyleProject.DisplayName=\u5efa\u7f6e Free-Style \u8edf\u9ad4\u5c08\u6848 FreeStyleProject.Description=\ \u9019\u662f Jenkins \u7684\u4e3b\u8981\u529f\u80fd\u3002\ Jenkins \u80fd\u642d\u914d\u5404\u5f0f\u7a0b\u5f0f\u78bc\u7ba1\u7406\u3001\u5efa\u7f6e\u7cfb\u7d71\u4f86\u5efa\u7f6e\u60a8\u7684\u5c08\u6848\uff0c\ \u751a\u81f3\u9084\u80fd\u505a\u8edf\u9ad4\u5efa\u7f6e\u4ee5\u5916\u7684\u5176\u4ed6\u4e8b\u60c5\u3002 - Hudson.BadPortNumber=\u9023\u63a5\u57e0\u865f {0} \u7121\u6548 Hudson.Computer.Caption=Master Hudson.Computer.DisplayName=master diff --git a/test/src/test/java/hudson/cli/WaitNodeOfflineCommandTest.java b/test/src/test/java/hudson/cli/WaitNodeOfflineCommandTest.java new file mode 100644 index 0000000000..19fb107320 --- /dev/null +++ b/test/src/test/java/hudson/cli/WaitNodeOfflineCommandTest.java @@ -0,0 +1,157 @@ +/* + * The MIT License + * + * Copyright 2016 Red Hat, Inc. + * + * 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. + */ + +/** + * @author pjanouse + */ + +package hudson.cli; + +import hudson.slaves.DumbSlave; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static hudson.cli.CLICommandInvoker.Matcher.failedWith; +import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput; +import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.fail; + +public class WaitNodeOfflineCommandTest { + + private CLICommandInvoker command; + + @Rule + public final JenkinsRule j = new JenkinsRule(); + + @Before + public void setUp() { + command = new CLICommandInvoker(j, "wait-node-offline"); + } + + @Test + public void waitNodeOfflineShouldFailIfNodeDoesNotExist() throws Exception { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("never_created"); + assertThat(result, failedWith(3)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: No such node 'never_created'")); + } + + @Test + public void waitNodeOfflineShouldSucceedOnOfflineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().setTemporarilyOffline(true); + while (true) { + if(slave.toComputer().isOffline()) + break; + Thread.sleep(100); + } + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + } + + @Test + public void waitNodeOfflineShouldSucceedOnGoingOfflineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().setTemporarilyOffline(true); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + } + + @Test + public void waitNodeOfflineShouldSucceedOnDisconnectedNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().disconnect(); + while (true) { + if(slave.toComputer().isOffline()) + break; + Thread.sleep(100); + } + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + } + + @Test + public void waitNodeOfflineShouldSucceedOnDisconnectingNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().disconnect(); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOffline(), equalTo(true)); + } + + @Test + public void waitNodeOfflineShouldTimeoutOnOnlineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + boolean timeoutOccurred = false; + + FutureTask task = new FutureTask(new Callable() { + public Object call() { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + fail("Never should return from previous CLI call!"); + return null; + } + }); + try { + task.get(30, TimeUnit.SECONDS); + } catch (TimeoutException e) { + timeoutOccurred = true; + } finally { + task.cancel(true); + } + + if(!timeoutOccurred) + fail("Missing timeout for CLI call"); + } +} diff --git a/test/src/test/java/hudson/cli/WaitNodeOnlineCommandTest.java b/test/src/test/java/hudson/cli/WaitNodeOnlineCommandTest.java new file mode 100644 index 0000000000..09a5aafb7c --- /dev/null +++ b/test/src/test/java/hudson/cli/WaitNodeOnlineCommandTest.java @@ -0,0 +1,182 @@ +/* + * The MIT License + * + * Copyright 2016 Red Hat, Inc. + * + * 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. + */ + +/** + * @author pjanouse + */ + +package hudson.cli; + +import hudson.slaves.DumbSlave; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static hudson.cli.CLICommandInvoker.Matcher.failedWith; +import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput; +import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.fail; + +public class WaitNodeOnlineCommandTest { + + private CLICommandInvoker command; + + @Rule + public final JenkinsRule j = new JenkinsRule(); + + @Before + public void setUp() { + command = new CLICommandInvoker(j, "wait-node-online"); + } + + @Test + public void waitNodeOnlineShouldFailIfNodeDoesNotExist() throws Exception { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("never_created"); + assertThat(result, failedWith(3)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: No such node 'never_created'")); + } + + @Test + public void waitNodeOnlineShouldSucceedOnGoingOnlineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOnline(), equalTo(true)); + } + + @Test + public void waitNodeOnlineShouldTimeoutOnGoingOfflineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().setTemporarilyOffline(true); + + boolean timeoutOccurred = false; + FutureTask task = new FutureTask(new Callable() { + public Object call() { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + fail("Never should return from previous CLI call!"); + return null; + } + }); + try { + task.get(30, TimeUnit.SECONDS); + } catch (TimeoutException e) { + timeoutOccurred = true; + } finally { + task.cancel(true); + } + + if(!timeoutOccurred) + fail("Missing timeout for CLI call"); + } + + @Test + public void waitNodeOnlineShouldTimeoutOnDisconnectedNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().disconnect(); + slave.toComputer().waitUntilOffline(); + + boolean timeoutOccurred = false; + FutureTask task = new FutureTask(new Callable() { + public Object call() { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + fail("Never should return from previous CLI call!"); + return null; + } + }); + try { + task.get(30, TimeUnit.SECONDS); + } catch (TimeoutException e) { + timeoutOccurred = true; + } finally { + task.cancel(true); + } + + if(!timeoutOccurred) + fail("Missing timeout for CLI call"); + } + + @Test + public void waitNodeOnlineShouldTimeoutOnDisconnectingNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().disconnect(); + + boolean timeoutOccurred = false; + FutureTask task = new FutureTask(new Callable() { + public Object call() { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + fail("Never should return from previous CLI call!"); + return null; + } + }); + try { + task.get(30, TimeUnit.SECONDS); + } catch (TimeoutException e) { + timeoutOccurred = true; + } finally { + task.cancel(true); + } + + if(!timeoutOccurred) + fail("Missing timeout for CLI call"); + } + + @Test + public void waitNodeOnlineShouldSuccessOnOnlineNode() throws Exception { + DumbSlave slave = j.createSlave("aNode", "", null); + slave.toComputer().waitUntilOnline(); + while (true) { + if(slave.toComputer().isOnline()) + break; + Thread.sleep(100); + } + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aNode"); + assertThat(result, succeededSilently()); + assertThat(slave.toComputer().isOnline(), equalTo(true)); + } +} From 538dba39c732fc2884f6e3b9c9743e8f6135fef2 Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Thu, 9 Jun 2016 12:58:16 +0200 Subject: [PATCH 25/89] Removed unnecessary empty line --- core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java | 1 - core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java | 1 - 2 files changed, 2 deletions(-) diff --git a/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java index 4c1139e484..161fe1879d 100644 --- a/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java +++ b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java @@ -39,7 +39,6 @@ public class WaitNodeOfflineCommand extends CLICommand { @Override public String getShortDescription() { - return Messages.WaitNodeOfflineCommand_ShortDescription(); } diff --git a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java index 10083d8dc6..fa33bdd17d 100644 --- a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java +++ b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java @@ -39,7 +39,6 @@ public class WaitNodeOnlineCommand extends CLICommand { @Override public String getShortDescription() { - return Messages.WaitNodeOnlineCommand_ShortDescription(); } From 13c8971c945364be6b6efba49053cbbbd9497d2b Mon Sep 17 00:00:00 2001 From: Manuel Recena Date: Fri, 22 Jul 2016 19:44:26 +0200 Subject: [PATCH 26/89] Do not force the representation --- .../main/resources/hudson/model/Messages_fr.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/resources/hudson/model/Messages_fr.properties b/core/src/main/resources/hudson/model/Messages_fr.properties index f9d5295135..30e1b2e65b 100644 --- a/core/src/main/resources/hudson/model/Messages_fr.properties +++ b/core/src/main/resources/hudson/model/Messages_fr.properties @@ -1,17 +1,17 @@ # The MIT License -# +# # Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Eric Lefevre-Ardant, Damien Finck -# +# # 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 @@ -55,7 +55,7 @@ Executor.NotAvailable=N/A FreeStyleProject.DisplayName=Construire un projet free-style -FreeStyleProject.Description=Ceci est la fonction principale de Jenkins qui sert \u00E0 builder (construire) votre projet.
    Vous pouvez int\u00E9grer tous les outils de gestion de version avec tous les syst\u00E8mes de build.
    Il est m\u00EAme possible d''utiliser Jenkins pour tout autre chose qu''un build logiciel. +FreeStyleProject.Description=Ceci est la fonction principale de Jenkins qui sert \u00E0 builder (construire) votre projet. Vous pouvez int\u00E9grer tous les outils de gestion de version avec tous les syst\u00E8mes de build. Il est m\u00EAme possible d''utiliser Jenkins pour tout autre chose qu''un build logiciel. Hudson.BadPortNumber=Num\u00e9ro de port incorrect {0} Hudson.Computer.Caption=Ma\u00eetre From ae59dde643367c90ec4427490b841db9570e32ad Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 24 Jul 2016 15:29:58 -0700 Subject: [PATCH 27/89] [maven-release-plugin] prepare release jenkins-2.15 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 7fca7c71d6..88b5b323af 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.15-SNAPSHOT + 2.15 cli diff --git a/core/pom.xml b/core/pom.xml index c8c368b0c4..1f213bdf54 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15-SNAPSHOT + 2.15 jenkins-core diff --git a/pom.xml b/pom.xml index 2ee0f0b833..b2c001aa6b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15-SNAPSHOT + 2.15 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.15 diff --git a/test/pom.xml b/test/pom.xml index 05cbb181db..172ce59302 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15-SNAPSHOT + 2.15 test diff --git a/war/pom.xml b/war/pom.xml index 3490b4a3ae..4dbf16a88d 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15-SNAPSHOT + 2.15 jenkins-war From 3aee361e530ba7a8e0ef8e4dc273d6e6b58267d2 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 24 Jul 2016 15:29:58 -0700 Subject: [PATCH 28/89] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 88b5b323af..36c2b0ec06 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.15 + 2.16-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 1f213bdf54..c2539d94b8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15 + 2.16-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index b2c001aa6b..f5eee45f9f 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15 + 2.16-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.15 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 172ce59302..384daa39a4 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15 + 2.16-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 4dbf16a88d..7d63edd576 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.15 + 2.16-SNAPSHOT jenkins-war From c04edbe2024bda9946a243ab3e4f151cd6446158 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 24 Jul 2016 15:35:56 -0700 Subject: [PATCH 29/89] updated changelog for release --- changelog.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.html b/changelog.html index 08a7506891..5c585f0045 100644 --- a/changelog.html +++ b/changelog.html @@ -59,6 +59,10 @@ Upcoming changes
+

What's new in 2.15 (2016/07/24)

+
    +
  • +

What's new in 2.14 (2016/07/17)

  • From ac1ff2baf4009c1cf3aa75bb99b89e67f9de6d51 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 25 Jul 2016 10:36:00 +0200 Subject: [PATCH 30/89] Noting #2446 #2453 #2455 #2463 #2464 #2465 #2466 --- changelog.html | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/changelog.html b/changelog.html index 5c585f0045..7855d24612 100644 --- a/changelog.html +++ b/changelog.html @@ -61,7 +61,25 @@ Upcoming changes

    What's new in 2.15 (2016/07/24)

      -
    • +
    • + Tell browsers not to cache or try to autocomplete forms in Jenkins to prevent problems due to invalid data in form submissions. + From now on, only select form fields (e.g. job name) will offer autocompletion. + (issue 18435) +
    • + Add a cache for user information to fix performance regression due to SECURITY-243. + (issue 35493) +
    • + Prevent null pointer exceptions when not entering a cron spec for a trigger. + (issue 36748) +
    • + Defend against some fatal startup errors. + (issue 36666) +
    • + Use the icon specified by the computer implementation on its overview page. + (issue 36775) +
    • + Internal: Extract the CLI command offline-node from core. + (issue 34468)

    What's new in 2.14 (2016/07/17)

      From d031cc3d157db85749ca1729b40873eb506c4431 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 25 Jul 2016 10:53:08 +0200 Subject: [PATCH 31/89] Use images from ci.jenkins.io --- changelog.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.html b/changelog.html index 7855d24612..c9c9677174 100644 --- a/changelog.html +++ b/changelog.html @@ -40,11 +40,11 @@ Some tips: Help other Jenkins users by letting the community know which releases you've used, and whether they had any significant issues.
      Legend:
      - Sunny = I use it on my production site without major issues.
      - Cloudy = I don't recommend it.
      - Lightning = I tried it but rolled back to a previous version.
      View ratings below, and click one of the icons next to your version to provide your input. From 70a9ca4d53c1d2ff4a65709d5caadccab2a93ce9 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 25 Jul 2016 17:00:10 +0100 Subject: [PATCH 32/89] [FIXED JENKINS-36922] Upgrade to instance-identity-module 2.0 - We migrate the bcpkix dependency from instance-identity to the war's WEB-INF/lib so that the net effect is zero and we are still not exposiing the bcpkix as a transitive dependency of jenkins-core --- war/pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/war/pom.xml b/war/pom.xml index 7d63edd576..5843b3ceaf 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -99,7 +99,13 @@ THE SOFTWARE. org.jenkins-ci.modules instance-identity - 1.5.1 + 2.0 + + + + org.bouncycastle + bcpkix-jdk15on + 1.54 org.jenkins-ci.modules From 3e72adafeb006ee4f557365291a15d309a68d801 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 25 Jul 2016 17:09:24 +0100 Subject: [PATCH 33/89] [JENKINS-36923] Give ownership of bcpkix dependency to bouncycastle-api plugin --- core/src/main/java/hudson/ClassicPluginStrategy.java | 3 ++- war/pom.xml | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 7164ffeef6..b9cc54fd54 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -417,7 +417,8 @@ public class ClassicPluginStrategy implements PluginStrategy { new DetachedPlugin("windows-slaves", "1.547.*", "1.0"), new DetachedPlugin("antisamy-markup-formatter", "1.553.*", "1.0"), new DetachedPlugin("matrix-project", "1.561.*", "1.0"), - new DetachedPlugin("junit", "1.577.*", "1.0") + new DetachedPlugin("junit", "1.577.*", "1.0"), + new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0-SNAPSHOT") // TODO remove -SNAPSHOT )); /** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */ diff --git a/war/pom.xml b/war/pom.xml index 5843b3ceaf..6af7f951c9 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -101,12 +101,6 @@ THE SOFTWARE. instance-identity 2.0 - - - org.bouncycastle - bcpkix-jdk15on - 1.54 - org.jenkins-ci.modules ssh-cli-auth @@ -411,6 +405,12 @@ THE SOFTWARE. 1.2-beta-4 hpi + + org.jenkins-ci.plugins + bouncycastle-api + 2.16.0-SNAPSHOT + hpi + ${project.build.directory}/${project.build.finalName}/WEB-INF/detached-plugins true From 03066d587f4b6f1c8c55a2a64d804b7e0c795cf3 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 25 Jul 2016 17:44:39 +0100 Subject: [PATCH 34/89] [JENKINS-36923] The PR builder should at least work for this now --- core/src/main/java/hudson/ClassicPluginStrategy.java | 2 +- war/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index b9cc54fd54..b27a911471 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -418,7 +418,7 @@ public class ClassicPluginStrategy implements PluginStrategy { new DetachedPlugin("antisamy-markup-formatter", "1.553.*", "1.0"), new DetachedPlugin("matrix-project", "1.561.*", "1.0"), new DetachedPlugin("junit", "1.577.*", "1.0"), - new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0-SNAPSHOT") // TODO remove -SNAPSHOT + new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0-20160725.164257-1") // TODO remove -SNAPSHOT )); /** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */ diff --git a/war/pom.xml b/war/pom.xml index 6af7f951c9..485cf5e01b 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -408,7 +408,7 @@ THE SOFTWARE. org.jenkins-ci.plugins bouncycastle-api - 2.16.0-SNAPSHOT + 2.16.0-20160725.164257-1 hpi From 5a3823e85cf2275f8b790288307016a8d8ff935e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 25 Jul 2016 13:12:52 -0400 Subject: [PATCH 35/89] Adding a few nullability annotations. --- .../java/hudson/model/PasswordParameterValue.java | 5 ++++- core/src/main/java/hudson/util/Secret.java | 15 ++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/model/PasswordParameterValue.java b/core/src/main/java/hudson/model/PasswordParameterValue.java index d7d917ee48..9f384fbb22 100644 --- a/core/src/main/java/hudson/model/PasswordParameterValue.java +++ b/core/src/main/java/hudson/model/PasswordParameterValue.java @@ -29,12 +29,14 @@ import hudson.util.VariableResolver; import org.kohsuke.stapler.DataBoundConstructor; import java.util.Locale; +import javax.annotation.Nonnull; /** * @author Kohsuke Kawaguchi */ public class PasswordParameterValue extends ParameterValue { + @Nonnull private final Secret value; // kept for backward compatibility @@ -68,7 +70,8 @@ public class PasswordParameterValue extends ParameterValue { public boolean isSensitive() { return true; } - + + @Nonnull public Secret getValue() { return value; } diff --git a/core/src/main/java/hudson/util/Secret.java b/core/src/main/java/hudson/util/Secret.java index 0cfd9171a9..a2a2bcb971 100644 --- a/core/src/main/java/hudson/util/Secret.java +++ b/core/src/main/java/hudson/util/Secret.java @@ -42,6 +42,8 @@ import java.io.UnsupportedEncodingException; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.regex.Pattern; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -62,6 +64,7 @@ public final class Secret implements Serializable { /** * Unencrypted secret text. */ + @Nonnull private final String value; private Secret(String value) { @@ -87,6 +90,7 @@ public final class Secret implements Serializable { * Before using this method, ask yourself if you'd be better off using {@link Secret#toString(Secret)} * to avoid NPE. */ + @Nonnull public String getPlainText() { return value; } @@ -144,7 +148,8 @@ public final class Secret implements Serializable { * Reverse operation of {@link #getEncryptedValue()}. Returns null * if the given cipher text was invalid. */ - public static Secret decrypt(String data) { + @CheckForNull + public static Secret decrypt(@CheckForNull String data) { if(data==null) return null; try { byte[] in = Base64.decode(data.toCharArray()); @@ -192,10 +197,9 @@ public final class Secret implements Serializable { * *

      * Useful for recovering a value from a form field. - * - * @return never null */ - public static Secret fromString(String data) { + @Nonnull + public static Secret fromString(@CheckForNull String data) { data = Util.fixNull(data); Secret s = decrypt(data); if(s==null) s=new Secret(data); @@ -207,7 +211,8 @@ public final class Secret implements Serializable { * To be consistent with {@link #fromString(String)}, this method doesn't distinguish * empty password and null password. */ - public static String toString(Secret s) { + @Nonnull + public static String toString(@CheckForNull Secret s) { return s==null ? "" : s.value; } From 0e8fe8745a0e9f724b64f5503a83bcb79ba88e01 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 25 Jul 2016 22:48:23 +0100 Subject: [PATCH 36/89] [JENKINS-36923] Allow snapshot resolution so the PR builder can run --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5eee45f9f..b1e73cac2c 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ THE SOFTWARE. true - false + true From a264bfca758be01777a6d4bd299e5e58a32f64cb Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 25 Jul 2016 18:38:58 -0400 Subject: [PATCH 37/89] Tests are not run unless the class name ends with "Test". --- .../groovy/hudson/cli/CLIActionTest.groovy | 87 ------- .../hudson/model/AbstractBuildTest.groovy | 188 -------------- .../test/java/hudson/PluginManagerTest2.java | 48 ---- ...va => AbstractBuildRangeCommand2Test.java} | 2 +- .../test/java/hudson/cli/CLIActionTest.java | 106 ++++++++ .../test/java/hudson/cli/CLIActionTest2.java | 70 ------ .../java/hudson/model/AbstractBuildTest.java | 234 ++++++++++++++++++ .../java/hudson/model/AbstractBuildTest2.java | 76 ------ ...nTest2.java => ParametersAction2Test.java} | 2 +- .../backwardCompatibility.zip | Bin 10 files changed, 342 insertions(+), 471 deletions(-) delete mode 100644 test/src/test/groovy/hudson/cli/CLIActionTest.groovy delete mode 100644 test/src/test/groovy/hudson/model/AbstractBuildTest.groovy delete mode 100644 test/src/test/java/hudson/PluginManagerTest2.java rename test/src/test/java/hudson/cli/{AbstractBuildRangeCommandTest2.java => AbstractBuildRangeCommand2Test.java} (99%) create mode 100644 test/src/test/java/hudson/cli/CLIActionTest.java delete mode 100644 test/src/test/java/hudson/cli/CLIActionTest2.java create mode 100644 test/src/test/java/hudson/model/AbstractBuildTest.java delete mode 100644 test/src/test/java/hudson/model/AbstractBuildTest2.java rename test/src/test/java/hudson/model/{ParametersActionTest2.java => ParametersAction2Test.java} (99%) rename test/src/test/resources/hudson/model/{ParametersActionTest2 => ParametersAction2Test}/backwardCompatibility.zip (100%) diff --git a/test/src/test/groovy/hudson/cli/CLIActionTest.groovy b/test/src/test/groovy/hudson/cli/CLIActionTest.groovy deleted file mode 100644 index 99b4437e60..0000000000 --- a/test/src/test/groovy/hudson/cli/CLIActionTest.groovy +++ /dev/null @@ -1,87 +0,0 @@ -package hudson.cli - -import hudson.Functions -import hudson.remoting.Channel -import hudson.security.FullControlOnceLoggedInAuthorizationStrategy -import org.codehaus.groovy.runtime.Security218 -import org.junit.Assert; -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.recipes.PresetData; -import org.jvnet.hudson.test.recipes.PresetData.DataSet; - -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors - -import static org.junit.Assert.fail - -/** - * @author Kohsuke Kawaguchi - * @author christ66 - */ -class CLIActionTest { - @Rule - public JenkinsRule j = new JenkinsRule() - - ExecutorService pool; - - /** - * Makes sure that the /cli endpoint is functioning. - */ - @Test - public void testDuplexHttp() { - pool = Executors.newCachedThreadPool() - try { - FullDuplexHttpStream con = new FullDuplexHttpStream(new URL(j.URL,"cli")); - Channel ch = new Channel("test connection", pool, con.inputStream, con.outputStream); - ch.close(); - } finally { - pool.shutdown(); - } - } - - @Test - public void security218() throws Exception { - pool = Executors.newCachedThreadPool() - try { - FullDuplexHttpStream con = new FullDuplexHttpStream(new URL(j.URL, "cli")); - Channel ch = new Channel("test connection", pool, con.inputStream, con.outputStream); - ch.call(new Security218()); - fail("Expected the call to be rejected"); - } catch (Exception e) { - assert Functions.printThrowable(e).contains("Rejected: "+Security218.class.name); - } finally { - pool.shutdown(); - } - - } - - @Test - public void security218_take2() throws Exception { - pool = Executors.newCachedThreadPool() - try { - new CLI(j.URL).execute([new Security218()]); - fail("Expected the call to be rejected"); - } catch (Exception e) { - assert Functions.printThrowable(e).contains("Rejected: "+Security218.class.name); - } finally { - pool.shutdown(); - } - } - - //TODO: Integrate the tests into existing ones in CLIActionTest2 - @Test - @PresetData(DataSet.NO_ANONYMOUS_READACCESS) - public void serveCliActionToAnonymousUserWithoutPermissions() throws Exception { - def wc = j.createWebClient(); - // The behavior changed due to SECURITY-192. index page is no longer accessible to anonymous - wc.assertFails("cli", HttpURLConnection.HTTP_FORBIDDEN); - } - - @Test - public void serveCliActionToAnonymousUser() throws Exception { - def wc = j.createWebClient(); - wc.goTo("cli"); - } -} diff --git a/test/src/test/groovy/hudson/model/AbstractBuildTest.groovy b/test/src/test/groovy/hudson/model/AbstractBuildTest.groovy deleted file mode 100644 index d50fb513d0..0000000000 --- a/test/src/test/groovy/hudson/model/AbstractBuildTest.groovy +++ /dev/null @@ -1,188 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. - * - * 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 hudson.model - -import java.io.IOException; - -import com.gargoylesoftware.htmlunit.Page - -import hudson.Launcher; -import hudson.model.Descriptor; -import hudson.slaves.EnvironmentVariablesNodeProperty -import hudson.slaves.EnvironmentVariablesNodeProperty.Entry -import hudson.tasks.Builder - -import org.jvnet.hudson.test.CaptureEnvironmentBuilder -import org.jvnet.hudson.test.GroovyJenkinsRule -import org.jvnet.hudson.test.FakeChangeLogSCM -import org.jvnet.hudson.test.FailureBuilder -import org.jvnet.hudson.test.TestExtension -import org.jvnet.hudson.test.UnstableBuilder - -import static org.junit.Assert.* -import static org.hamcrest.CoreMatchers.*; - -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.Issue - -public class AbstractBuildTest { - - @Rule public GroovyJenkinsRule j = new GroovyJenkinsRule(); - - @Test void variablesResolved() { - def project = j.createFreeStyleProject(); - j.jenkins.nodeProperties.replaceBy([ - new EnvironmentVariablesNodeProperty(new Entry("KEY1", "value"), new Entry("KEY2",'$KEY1'))]); - def builder = new CaptureEnvironmentBuilder(); - project.buildersList.add(builder); - - j.buildAndAssertSuccess(project); - - def envVars = builder.getEnvVars(); - assertEquals("value", envVars["KEY1"]); - assertEquals("value", envVars["KEY2"]); - } - - /** - * Makes sure that raw console output doesn't get affected by XML escapes. - */ - @Test void rawConsoleOutput() { - def out = "&"; - - def p = j.createFreeStyleProject(); - p.buildersList.add(j.builder { builder,launcher,BuildListener listener -> - listener.logger.println(out); - }) - def b = j.buildAndAssertSuccess(p); - Page rsp = j.createWebClient().goTo("${b.url}/consoleText", "text/plain"); - println "Output:\n"+rsp.webResponse.contentAsString - assertTrue(rsp.webResponse.contentAsString.contains(out)); - } - - def assertCulprits(AbstractBuild b, Collection expectedIds) { - assertEquals(expectedIds as Set, b.culprits*.id as Set); - } - - @Test void culprits() { - - def p = j.createFreeStyleProject(); - def scm = new FakeChangeLogSCM() - p.scm = scm - - // 1st build, successful, no culprits - scm.addChange().withAuthor("alice") - def b = j.assertBuildStatus(Result.SUCCESS,p.scheduleBuild2(0).get()) - assertCulprits(b,["alice"]) - - // 2nd build - scm.addChange().withAuthor("bob") - p.buildersList.add(new FailureBuilder()) - b = j.assertBuildStatus(Result.FAILURE,p.scheduleBuild2(0).get()) - assertCulprits(b,["bob"]) - - // 3rd build. bob continues to be in culprit - scm.addChange().withAuthor("charlie") - b = j.assertBuildStatus(Result.FAILURE,p.scheduleBuild2(0).get()) - assertCulprits(b,["bob","charlie"]) - - // 4th build, unstable. culprit list should continue - scm.addChange().withAuthor("dave") - p.buildersList.replaceBy([new UnstableBuilder()]) - b = j.assertBuildStatus(Result.UNSTABLE,p.scheduleBuild2(0).get()) - assertCulprits(b,["bob","charlie","dave"]) - - // 5th build, unstable. culprit list should continue - scm.addChange().withAuthor("eve") - b = j.assertBuildStatus(Result.UNSTABLE,p.scheduleBuild2(0).get()) - assertCulprits(b,["bob","charlie","dave","eve"]) - - // 6th build, success, accumulation continues up to this point - scm.addChange().withAuthor("fred") - p.buildersList.replaceBy([]) - b = j.assertBuildStatus(Result.SUCCESS,p.scheduleBuild2(0).get()) - assertCulprits(b,["bob","charlie","dave","eve","fred"]) - - // 7th build, back to empty culprits - scm.addChange().withAuthor("george") - b = j.assertBuildStatus(Result.SUCCESS,p.scheduleBuild2(0).get()) - assertCulprits(b,["george"]) - } - - @Issue("JENKINS-19920") - @Test void lastBuildNextBuild() { - FreeStyleProject p = j.createFreeStyleProject(); - AbstractBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); - AbstractBuild b2 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); - assertEquals(b2, p.getLastBuild()); - b2.getNextBuild(); // force this to be initialized - b2.delete(); - assertEquals(b1, p.getLastBuild()); - b1 = p.getLastBuild(); - assertEquals(null, b1.getNextBuild()); - } - - @Test void doNotInterruptBuildAbruptlyWhenExceptionThrownFromBuildStep() { - FreeStyleProject p = j.createFreeStyleProject(); - p.getBuildersList().add(new ThrowBuilder()); - def build = p.scheduleBuild2(0).get(); - j.assertBuildStatus(Result.FAILURE, build); - - def log = build.log; - assertThat(log, containsString("Finished: FAILURE")); - assertThat(log, containsString("Build step 'ThrowBuilder' marked build as failure")); - } - - @Test void fixEmptyDisplayName() { - FreeStyleProject p = j.createFreeStyleProject("foo"); - p.setDisplayName(""); - assertEquals("An empty display name should be ignored.", "foo", p.getDisplayName()); - } - - @Test void fixBlankDisplayName() { - FreeStyleProject p = j.createFreeStyleProject("foo"); - p.setDisplayName(" "); - assertEquals("A blank display name should be ignored.", "foo", p.getDisplayName()); - } - - @Test void validDisplayName() { - FreeStyleProject p = j.createFreeStyleProject("foo"); - p.setDisplayName("bar"); - assertEquals("A non-blank display name should be used.", "bar", p.getDisplayName()); - } - - @Test void trimValidDisplayName() { - FreeStyleProject p = j.createFreeStyleProject("foo"); - p.setDisplayName(" bar "); - assertEquals("A non-blank display name with whitespace should be trimmed.", "bar", p.getDisplayName()); - } - - private static class ThrowBuilder extends Builder { - @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { - throw new NullPointerException(); - } - @TestExtension("doNotInterruptBuildAbruptlyWhenExceptionThrownFromBuildStep") - public static class DescriptorImpl extends Descriptor {} - } -} diff --git a/test/src/test/java/hudson/PluginManagerTest2.java b/test/src/test/java/hudson/PluginManagerTest2.java deleted file mode 100644 index f889130cb4..0000000000 --- a/test/src/test/java/hudson/PluginManagerTest2.java +++ /dev/null @@ -1,48 +0,0 @@ -package hudson; - -import java.io.File; -import java.io.IOException; -import java.net.URL; - -import javax.servlet.ServletContext; - -import org.jvnet.hudson.test.HudsonTestCase; -import org.xml.sax.SAXException; - -import com.gargoylesoftware.htmlunit.html.HtmlForm; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import org.apache.commons.io.FileUtils; - -public class PluginManagerTest2 extends HudsonTestCase { - @Override - protected void setUp() throws Exception { - setPluginManager(null); // use a fresh instance - super.setUp(); - } - - private ServletContext servletContext; - - // need to keep the ServletContext to use in the actual test - protected ServletContext createWebServer() throws Exception { - servletContext=super.createWebServer(); - return servletContext; - } - - private void uploadPlugin(String pluginName, boolean useServerRoot) throws IOException, SAXException, Exception { - HtmlPage page = new WebClient().goTo("pluginManager/advanced"); - HtmlForm f = page.getFormByName("uploadPlugin"); - File plugin; - if(useServerRoot) { - String pluginsPath=servletContext.getRealPath("WEB-INF/plugins"); - plugin = new File(pluginsPath+"/"+pluginName); - } else { - File dir = env.temporaryDirectoryAllocator.allocate(); - plugin = new File(dir, pluginName); - URL resource = getClass().getClassLoader().getResource("plugins/"+pluginName); - FileUtils.copyURLToFile(resource,plugin); - } - f.getInputByName("name").setValueAttribute(plugin.getAbsolutePath()); - submit(f); - } - -} diff --git a/test/src/test/java/hudson/cli/AbstractBuildRangeCommandTest2.java b/test/src/test/java/hudson/cli/AbstractBuildRangeCommand2Test.java similarity index 99% rename from test/src/test/java/hudson/cli/AbstractBuildRangeCommandTest2.java rename to test/src/test/java/hudson/cli/AbstractBuildRangeCommand2Test.java index 64c68f0172..d2643b2d13 100644 --- a/test/src/test/java/hudson/cli/AbstractBuildRangeCommandTest2.java +++ b/test/src/test/java/hudson/cli/AbstractBuildRangeCommand2Test.java @@ -52,7 +52,7 @@ import static org.hamcrest.Matchers.not; /** * @author pjanouse */ -public class AbstractBuildRangeCommandTest2 { +public class AbstractBuildRangeCommand2Test { private CLICommandInvoker command; diff --git a/test/src/test/java/hudson/cli/CLIActionTest.java b/test/src/test/java/hudson/cli/CLIActionTest.java new file mode 100644 index 0000000000..b42a89b329 --- /dev/null +++ b/test/src/test/java/hudson/cli/CLIActionTest.java @@ -0,0 +1,106 @@ +package hudson.cli; + +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import hudson.Functions; +import hudson.remoting.Channel; +import hudson.remoting.ChannelBuilder; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.codehaus.groovy.runtime.Security218; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.recipes.PresetData; +import org.jvnet.hudson.test.recipes.PresetData.DataSet; + +public class CLIActionTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + private ExecutorService pool; + + /** + * Makes sure that the /cli endpoint is functioning. + */ + @Test + public void testDuplexHttp() throws Exception { + pool = Executors.newCachedThreadPool(); + try { + FullDuplexHttpStream con = new FullDuplexHttpStream(new URL(j.getURL(), "cli"), null); + Channel ch = new ChannelBuilder("test connection", pool).build(con.getInputStream(), con.getOutputStream()); + ch.close(); + } finally { + pool.shutdown(); + } + } + + @Test + public void security218() throws Exception { + pool = Executors.newCachedThreadPool(); + try { + FullDuplexHttpStream con = new FullDuplexHttpStream(new URL(j.getURL(), "cli"), null); + Channel ch = new ChannelBuilder("test connection", pool).build(con.getInputStream(), con.getOutputStream()); + ch.call(new Security218()); + fail("Expected the call to be rejected"); + } catch (Exception e) { + assertThat(Functions.printThrowable(e), containsString("Rejected: " + Security218.class.getName())); + } finally { + pool.shutdown(); + } + + } + + @SuppressWarnings({"unchecked", "rawtypes"}) // intentionally passing an unreifiable argument here + @Test + public void security218_take2() throws Exception { + pool = Executors.newCachedThreadPool(); + try { + List/**/ commands = new ArrayList(); + commands.add(new Security218()); + new CLI(j.getURL()).execute(commands); + fail("Expected the call to be rejected"); + } catch (Exception e) { + assertThat(Functions.printThrowable(e), containsString("Rejected: " + Security218.class.getName())); + } finally { + pool.shutdown(); + } + } + + @Test + @PresetData(DataSet.NO_ANONYMOUS_READACCESS) + @Issue("SECURITY-192") + public void serveCliActionToAnonymousUserWithoutPermissions() throws Exception { + JenkinsRule.WebClient wc = j.createWebClient(); + // The behavior changed due to SECURITY-192. index page is no longer accessible to anonymous + wc.assertFails("cli", HttpURLConnection.HTTP_FORBIDDEN); + // so we check the access by emulating the CLI connection post request + WebRequest settings = new WebRequest(new URL(j.getURL(), "cli")); + settings.setHttpMethod(HttpMethod.POST); + settings.setAdditionalHeader("Session", UUID.randomUUID().toString()); + settings.setAdditionalHeader("Side", "download"); // We try to download something to init the duplex channel + settings = wc.addCrumb(settings); + + Page page = wc.getPage(settings); + WebResponse webResponse = page.getWebResponse(); + assertEquals("We expect that the proper POST request from CLI gets processed successfully", + 200, webResponse.getStatusCode()); + } + + @Test + public void serveCliActionToAnonymousUserWithAnonymousUserWithPermissions() throws Exception { + JenkinsRule.WebClient wc = j.createWebClient(); + wc.goTo("cli"); + } + +} diff --git a/test/src/test/java/hudson/cli/CLIActionTest2.java b/test/src/test/java/hudson/cli/CLIActionTest2.java deleted file mode 100644 index cfe7cf0555..0000000000 --- a/test/src/test/java/hudson/cli/CLIActionTest2.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2015 Oleg Nenashev. - * - * 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 hudson.cli; - -import com.gargoylesoftware.htmlunit.HttpMethod; -import com.gargoylesoftware.htmlunit.Page; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.WebResponse; -import java.net.URL; -import java.util.UUID; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.recipes.PresetData; -import org.jvnet.hudson.test.recipes.PresetData.DataSet; - -import static org.junit.Assert.*; -import org.jvnet.hudson.test.Issue; - -/** - * Tests for {@link CLIAction}. - * CLIActionTest is being generated from Groovy, hence here we specify another one. - * @author Oleg Nenashev - */ -public class CLIActionTest2 { - - @Rule - public final JenkinsRule j = new JenkinsRule(); - - @Test - @PresetData(DataSet.NO_ANONYMOUS_READACCESS) - @Issue("SECURITY-192") - public void serveCliActionToAnonymousUser() throws Exception { - JenkinsRule.WebClient wc = j.createWebClient(); - - // The behavior changed due to SECURITY-192. index page is no longer accessible to anonymous, - // so we check the access by emulating the CLI connection post request - WebRequest settings = new WebRequest(new URL(j.getURL(), "cli")); - settings.setHttpMethod(HttpMethod.POST); - settings.setAdditionalHeader("Session", UUID.randomUUID().toString()); - settings.setAdditionalHeader("Side", "download"); // We try to download something to init the duplex channel - settings = wc.addCrumb(settings); - - Page page = wc.getPage(settings); - WebResponse webResponse = page.getWebResponse(); - assertEquals("We expect that the proper POST request from CLI gets processed successfully", - 200, webResponse.getStatusCode()); - } -} diff --git a/test/src/test/java/hudson/model/AbstractBuildTest.java b/test/src/test/java/hudson/model/AbstractBuildTest.java new file mode 100644 index 0000000000..15cbc0b4ff --- /dev/null +++ b/test/src/test/java/hudson/model/AbstractBuildTest.java @@ -0,0 +1,234 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2015, Sun Microsystems, Inc., CloudBees, Inc. + * + * 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 hudson.model; + +import com.gargoylesoftware.htmlunit.Page; +import hudson.EnvVars; +import hudson.Launcher; +import hudson.model.queue.QueueTaskFuture; +import hudson.slaves.EnvironmentVariablesNodeProperty; +import hudson.tasks.Builder; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.CaptureEnvironmentBuilder; +import org.jvnet.hudson.test.FailureBuilder; +import org.jvnet.hudson.test.FakeChangeLogSCM; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestBuilder; +import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.UnstableBuilder; + +/** + * Unit tests of {@link AbstractBuild}. + */ +public class AbstractBuildTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @Issue("JENKINS-30730") + @SuppressWarnings("deprecation") + public void reportErrorShouldNotFailForNonPublisherClass() throws Exception { + FreeStyleProject prj = j.createFreeStyleProject(); + ErrorneousJobProperty errorneousJobProperty = new ErrorneousJobProperty(); + prj.addProperty(errorneousJobProperty); + QueueTaskFuture future = prj.scheduleBuild2(0); + assertThat("Build should be actually scheduled by Jenkins", future, notNullValue()); + FreeStyleBuild build = future.get(); + j.assertLogContains(ErrorneousJobProperty.ERROR_MESSAGE, build); + j.assertLogNotContains(ClassCastException.class.getName(), build); + } + + /** + * Job property, which always fails with an exception. + */ + public static class ErrorneousJobProperty extends JobProperty { + + public static final String ERROR_MESSAGE = "This publisher fails by design"; + + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + throw new IOException(ERROR_MESSAGE); + } + + @TestExtension("reportErrorShouldNotFailForNonPublisherClass") + public static class DescriptorImpl extends JobPropertyDescriptor {} + } + + @Test + public void variablesResolved() throws Exception { + FreeStyleProject project = j.createFreeStyleProject(); + j.jenkins.getNodeProperties().add(new EnvironmentVariablesNodeProperty(new EnvironmentVariablesNodeProperty.Entry("KEY1", "value"), new EnvironmentVariablesNodeProperty.Entry("KEY2", "$KEY1"))); + CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); + project.getBuildersList().add(builder); + + j.buildAndAssertSuccess(project); + + EnvVars envVars = builder.getEnvVars(); + assertEquals("value", envVars.get("KEY1")); + assertEquals("value", envVars.get("KEY2")); + } + + /** + * Makes sure that raw console output doesn't get affected by XML escapes. + */ + @Test + public void rawConsoleOutput() throws Exception { + final String out = "&"; + + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new TestBuilder() { + @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + listener.getLogger().println(out); + return true; + } + }); + FreeStyleBuild b = j.buildAndAssertSuccess(p); + Page rsp = j.createWebClient().goTo(b.getUrl() + "/consoleText", "text/plain"); + assertThat(rsp.getWebResponse().getContentAsString(), containsString(out)); + } + + private void assertCulprits(AbstractBuild b, String... expectedIds) { + Set actual = new TreeSet<>(); + for (User u : b.getCulprits()) { + actual.add(u.getId()); + } + assertEquals(actual, new TreeSet<>(Arrays.asList(expectedIds))); + } + + @Test + public void culprits() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + FakeChangeLogSCM scm = new FakeChangeLogSCM(); + p.setScm(scm); + + // 1st build, successful, no culprits + scm.addChange().withAuthor("alice"); + FreeStyleBuild b = j.buildAndAssertSuccess(p); + assertCulprits(b, "alice"); + + // 2nd build + scm.addChange().withAuthor("bob"); + p.getBuildersList().add(new FailureBuilder()); + b = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + assertCulprits(b, "bob"); + + // 3rd build. bob continues to be in culprit + scm.addChange().withAuthor("charlie"); + b = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + assertCulprits(b, "bob", "charlie"); + + // 4th build, unstable. culprit list should continue + scm.addChange().withAuthor("dave"); + p.getBuildersList().replaceBy(Collections.singleton(new UnstableBuilder())); + b = j.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0).get()); + assertCulprits(b, "bob", "charlie", "dave"); + + // 5th build, unstable. culprit list should continue + scm.addChange().withAuthor("eve"); + b = j.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0).get()); + assertCulprits(b, "bob", "charlie", "dave", "eve"); + + // 6th build, success, accumulation continues up to this point + scm.addChange().withAuthor("fred"); + p.getBuildersList().clear(); + b = j.buildAndAssertSuccess(p); + assertCulprits(b, "bob", "charlie", "dave", "eve", "fred"); + + // 7th build, back to empty culprits + scm.addChange().withAuthor("george"); + b = j.buildAndAssertSuccess(p); + assertCulprits(b, "george"); + } + + @Issue("JENKINS-19920") + @Test + public void lastBuildNextBuild() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + FreeStyleBuild b1 = j.buildAndAssertSuccess(p); + FreeStyleBuild b2 = j.buildAndAssertSuccess(p); + assertEquals(b2, p.getLastBuild()); + b2.getNextBuild(); // force this to be initialized + b2.delete(); + assertEquals(b1, p.getLastBuild()); + b1 = p.getLastBuild(); + assertNull(b1.getNextBuild()); + } + + @Test + public void doNotInterruptBuildAbruptlyWhenExceptionThrownFromBuildStep() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new ThrowBuilder()); + FreeStyleBuild build = p.scheduleBuild2(0).get(); + j.assertBuildStatus(Result.FAILURE, build); + j.assertLogContains("Finished: FAILURE", build); + j.assertLogContains("Build step 'ThrowBuilder' marked build as failure", build); + } + + @Test + public void fixEmptyDisplayName() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("foo"); + p.setDisplayName(""); + assertEquals("An empty display name should be ignored.", "foo", p.getDisplayName()); + } + + @Test + public void fixBlankDisplayName() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("foo"); + p.setDisplayName(" "); + assertEquals("A blank display name should be ignored.", "foo", p.getDisplayName()); + } + + @Test + public void validDisplayName() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("foo"); + p.setDisplayName("bar"); + assertEquals("A non-blank display name should be used.", "bar", p.getDisplayName()); + } + + @Test + public void trimValidDisplayName() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("foo"); + p.setDisplayName(" bar "); + assertEquals("A non-blank display name with whitespace should be trimmed.", "bar", p.getDisplayName()); + } + + private static class ThrowBuilder extends Builder { + @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + throw new NullPointerException(); + } + @TestExtension("doNotInterruptBuildAbruptlyWhenExceptionThrownFromBuildStep") + public static class DescriptorImpl extends Descriptor {} + } + +} diff --git a/test/src/test/java/hudson/model/AbstractBuildTest2.java b/test/src/test/java/hudson/model/AbstractBuildTest2.java deleted file mode 100644 index 9f3504be0a..0000000000 --- a/test/src/test/java/hudson/model/AbstractBuildTest2.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2015 CloudBees, Inc. - * - * 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 hudson.model; - -import hudson.Launcher; -import hudson.model.queue.QueueTaskFuture; -import java.io.IOException; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.TestExtension; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; - -/** - * Unit tests of {@link AbstractBuild}. - * @author Oleg Nenashev - */ -public class AbstractBuildTest2 { - - @Rule - public JenkinsRule rule = new JenkinsRule(); - - @Test - @Issue("JENKINS-30730") - @SuppressWarnings("deprecation") - public void reportErrorShouldNotFailForNonPublisherClass() throws Exception { - FreeStyleProject prj = rule.createFreeStyleProject(); - ErrorneousJobProperty errorneousJobProperty = new ErrorneousJobProperty(); - prj.addProperty(errorneousJobProperty); - QueueTaskFuture future = prj.scheduleBuild2(0); - assertThat("Build should be actually scheduled by Jenkins", future, notNullValue()); - FreeStyleBuild build = future.get(); - rule.assertLogContains(ErrorneousJobProperty.ERROR_MESSAGE, build); - rule.assertLogNotContains(ClassCastException.class.getName(), build); - } - - /** - * Job property, which always fails with an exception. - */ - public static class ErrorneousJobProperty extends JobProperty { - - public static final String ERROR_MESSAGE = "This publisher fails by design"; - - @Override - public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { - throw new IOException(ERROR_MESSAGE); - } - - @TestExtension("reportErrorShouldNotFailForNonPublisherClass") - public static class DescriptorImpl extends JobPropertyDescriptor {} - } -} diff --git a/test/src/test/java/hudson/model/ParametersActionTest2.java b/test/src/test/java/hudson/model/ParametersAction2Test.java similarity index 99% rename from test/src/test/java/hudson/model/ParametersActionTest2.java rename to test/src/test/java/hudson/model/ParametersAction2Test.java index 10e4a2af39..32a9d59b8f 100644 --- a/test/src/test/java/hudson/model/ParametersActionTest2.java +++ b/test/src/test/java/hudson/model/ParametersAction2Test.java @@ -16,7 +16,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class ParametersActionTest2 { +public class ParametersAction2Test { @Rule public JenkinsRule j = new JenkinsRule(); diff --git a/test/src/test/resources/hudson/model/ParametersActionTest2/backwardCompatibility.zip b/test/src/test/resources/hudson/model/ParametersAction2Test/backwardCompatibility.zip similarity index 100% rename from test/src/test/resources/hudson/model/ParametersActionTest2/backwardCompatibility.zip rename to test/src/test/resources/hudson/model/ParametersAction2Test/backwardCompatibility.zip From 9a056c591b40c6178c93e64f864f213697cb5195 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 25 Jul 2016 18:46:35 -0400 Subject: [PATCH 38/89] Correcting a typo in the original test. --- test/src/test/java/hudson/model/AbstractBuildTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/src/test/java/hudson/model/AbstractBuildTest.java b/test/src/test/java/hudson/model/AbstractBuildTest.java index 15cbc0b4ff..99cffb8718 100644 --- a/test/src/test/java/hudson/model/AbstractBuildTest.java +++ b/test/src/test/java/hudson/model/AbstractBuildTest.java @@ -60,19 +60,19 @@ public class AbstractBuildTest { @SuppressWarnings("deprecation") public void reportErrorShouldNotFailForNonPublisherClass() throws Exception { FreeStyleProject prj = j.createFreeStyleProject(); - ErrorneousJobProperty errorneousJobProperty = new ErrorneousJobProperty(); + ErroneousJobProperty errorneousJobProperty = new ErroneousJobProperty(); prj.addProperty(errorneousJobProperty); QueueTaskFuture future = prj.scheduleBuild2(0); assertThat("Build should be actually scheduled by Jenkins", future, notNullValue()); FreeStyleBuild build = future.get(); - j.assertLogContains(ErrorneousJobProperty.ERROR_MESSAGE, build); + j.assertLogContains(ErroneousJobProperty.ERROR_MESSAGE, build); j.assertLogNotContains(ClassCastException.class.getName(), build); } /** * Job property, which always fails with an exception. */ - public static class ErrorneousJobProperty extends JobProperty { + public static class ErroneousJobProperty extends JobProperty { public static final String ERROR_MESSAGE = "This publisher fails by design"; From 3956994c286941dab2da810c649269d637dc98d9 Mon Sep 17 00:00:00 2001 From: Ivan Meredith Date: Tue, 26 Jul 2016 18:39:33 +1200 Subject: [PATCH 39/89] Remove trailing space from Hudson.DisplayName (#2471) --- core/src/main/resources/jenkins/model/Messages_es.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/model/Messages_es.properties b/core/src/main/resources/jenkins/model/Messages_es.properties index 73760e5fe6..ac1f233aed 100644 --- a/core/src/main/resources/jenkins/model/Messages_es.properties +++ b/core/src/main/resources/jenkins/model/Messages_es.properties @@ -24,7 +24,7 @@ Hudson.BadPortNumber=N\u00famero erroneo de puerto {0} Hudson.Computer.Caption=Principal Hudson.Computer.DisplayName=principal Hudson.ControlCodeNotAllowed=El c\u00f3digo de control {0} no est\u00e1 permitido -Hudson.DisplayName=Jenkins +Hudson.DisplayName=Jenkins Hudson.JobAlreadyExists=Una tarea con el nombre ''{0}'' ya existe Hudson.NoJavaInPath=No se encuentra el comando java en el ''PATH''. Quiz\u00e1s necesite configurar JDKs? Hudson.NoName=No se ha especificado un nombre From 9e1d1b0ab6aece09f23140701067dfc501b34a20 Mon Sep 17 00:00:00 2001 From: ikedam Date: Tue, 26 Jul 2016 15:41:19 +0900 Subject: [PATCH 40/89] [FIXED JENKINS-36732] Use build start times instead of build scheduled times in `BuildTimelineWidget`. (#2460) --- core/src/main/java/hudson/model/BuildTimelineWidget.java | 4 ++-- core/src/main/java/jenkins/widgets/BuildListTable.java | 5 +++++ core/src/main/resources/lib/hudson/buildListTable.jelly | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/model/BuildTimelineWidget.java b/core/src/main/java/hudson/model/BuildTimelineWidget.java index 9b8bc849b8..31810b5f5e 100644 --- a/core/src/main/java/hudson/model/BuildTimelineWidget.java +++ b/core/src/main/java/hudson/model/BuildTimelineWidget.java @@ -62,8 +62,8 @@ public class BuildTimelineWidget { TimelineEventList result = new TimelineEventList(); for (Run r : builds.byTimestamp(min,max)) { Event e = new Event(); - e.start = r.getTime(); - e.end = new Date(r.timestamp+r.getDuration()); + e.start = new Date(r.getStartTimeInMillis()); + e.end = new Date(r.getStartTimeInMillis()+r.getDuration()); e.title = r.getFullDisplayName(); // what to put in the description? // e.description = "Longish description of event "+r.getFullDisplayName(); diff --git a/core/src/main/java/jenkins/widgets/BuildListTable.java b/core/src/main/java/jenkins/widgets/BuildListTable.java index ec8e60e368..7be989feaf 100644 --- a/core/src/main/java/jenkins/widgets/BuildListTable.java +++ b/core/src/main/java/jenkins/widgets/BuildListTable.java @@ -25,9 +25,13 @@ package jenkins.widgets; import hudson.Functions; +import hudson.Util; import hudson.model.BallColor; import hudson.model.Run; import net.sf.json.JSONObject; + +import java.util.Date; + import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; @@ -45,6 +49,7 @@ public class BuildListTable extends RunListProgressiveRendering { element.put("displayName", build.getDisplayName()); element.put("timestampString", build.getTimestampString()); element.put("timestampString2", build.getTimestampString2()); + element.put("timestampString3", Util.XS_DATETIME_FORMATTER.format(new Date(build.getStartTimeInMillis()))); Run.Summary buildStatusSummary = build.getBuildStatusSummary(); element.put("buildStatusSummaryWorse", buildStatusSummary.isWorse); element.put("buildStatusSummaryMessage", buildStatusSummary.message); diff --git a/core/src/main/resources/lib/hudson/buildListTable.jelly b/core/src/main/resources/lib/hudson/buildListTable.jelly index 80cb00dad0..9465606ddb 100644 --- a/core/src/main/resources/lib/hudson/buildListTable.jelly +++ b/core/src/main/resources/lib/hudson/buildListTable.jelly @@ -47,7 +47,7 @@ THE SOFTWARE. insert('\u00A0'). insert(new Element('a', {href: '${rootURL}/' + e.url, 'class': 'model-link inside'}). update(e.displayName.escapeHTML()))); - tr.insert(new Element('td', {data: e.timestampString2, tooltip: '${%Click to center timeline on event}', onclick: 'javascript:tl.getBand(0).scrollToCenter(Timeline.DateTime.parseGregorianDateTime("' + e.timestampString2 + '"))'}). + tr.insert(new Element('td', {data: e.timestampString2, tooltip: '${%Click to center timeline on event}', onclick: 'javascript:tl.getBand(0).scrollToCenter(Timeline.DateTime.parseGregorianDateTime("' + e.timestampString3 + '"))'}). update(e.timestampString.escapeHTML())); tr.insert(new Element('td', {style: e.buildStatusSummaryWorse ? 'color: red' : ''}). update(e.buildStatusSummaryMessage.escapeHTML())); From e48cb2994245bdbb52e8c687a75468a90d964a76 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 26 Jul 2016 15:08:02 +0100 Subject: [PATCH 41/89] [JENKINS-36871] Need to be able to access the instance identity from core --- .../identity/InstanceIdentityProvider.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java diff --git a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java new file mode 100644 index 0000000000..6bd05190e0 --- /dev/null +++ b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java @@ -0,0 +1,102 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees Inc. + * + * 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.model.identity; + +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import javax.annotation.CheckForNull; + +/** + * A source of instance identity. + * + * @param the type of public key. + * @param the type of private key. + * @since FIXME + */ +public abstract class InstanceIdentityProvider implements + ExtensionPoint { + /** + * Gets the {@link KeyPair} that comprises the instance identity. + * + * @return the {@link KeyPair} that comprises the instance identity. + */ + @CheckForNull + public abstract KeyPair getKeyPair(); + + /** + * Shortcut to {@link KeyPair#getPublic()}. + * + * @return the public key. + */ + @CheckForNull + public PUB getPublicKey() { + KeyPair keyPair = getKeyPair(); + return keyPair == null ? null : (PUB) keyPair.getPublic(); + } + + /** + * Shortcut to {@link KeyPair#getPrivate()}. + * + * @return the private key. + */ + @CheckForNull + public PRIV getPrivateKey() { + KeyPair keyPair = getKeyPair(); + return keyPair == null ? null : (PRIV) keyPair.getPublic(); + } + + /** + * Gets the self-signed {@link X509Certificate} that is associated with this identity. The certificate + * will must be currently valid. Repeated calls to this method may result in new certificates being generated. + * + * @return the certificate. + */ + @CheckForNull + public abstract X509Certificate getCertificate(); + + /** + * Gets the provider of the required identity type. + * + * @param keyType the type of private key. + * @param the type of public key. + * @param the type of private key. + * @return the provider or {@code null} if no provider of the specified type is available. + */ + @CheckForNull + @SuppressWarnings("unchecked") + public static InstanceIdentityProvider get( + Class keyType) { + for (InstanceIdentityProvider provider : ExtensionList.lookup(InstanceIdentityProvider.class)) { + KeyPair keyPair = provider.getKeyPair(); + if (keyPair != null && keyType.isInstance(keyPair.getPrivate())) { + return (InstanceIdentityProvider) provider; + } + } + return null; + } +} From 624c0b13bd35ad2ebb4a8b34179dfbb38918f91b Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 26 Jul 2016 15:20:29 +0100 Subject: [PATCH 42/89] [JENKINS-36871] Let users discover the instance identity fingerprint - We will be asking users who wish to ensure a secure path for their JNLP agents to validate the fingerprint before initial connection (which leverages TLS encryption of the http end-point and trust by the JNLP agent of the Jenkins Master's HTTPS TLS certificate) - The fingerprint will then be used to validate the JNLP self-signed TLS certificate in order to ensure that the agent is talking to the master without fear of MiM - We need to use self-signed TLS certificates for the JNLP connection as we need these to be unique to the Jenkins master and if we let users provide their own then they would end up re-using... in any case it is simpler to leverage the strength of the HTTPS TLS certificate as that is at least the one certificate that users have a hope of understanding how to validate. - Thus we need a mechanism for users to verify the fingerprint. This root action provides that mechanism --- core/src/main/java/jenkins/model/Jenkins.java | 3 +- .../model/identity/IdentityRootAction.java | 81 +++++++++++++++++++ .../identity/IdentityRootAction/index.jelly | 22 +++++ .../IdentityRootAction/index.properties | 3 + 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/jenkins/model/identity/IdentityRootAction.java create mode 100644 core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.jelly create mode 100644 core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.properties diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 25481a0bb2..e01d3085f7 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -4796,7 +4796,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve "/signup", "/tcpSlaveAgentListener", "/federatedLoginService/", - "/securityRealm" + "/securityRealm", + "/instance-identity" ); /** diff --git a/core/src/main/java/jenkins/model/identity/IdentityRootAction.java b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java new file mode 100644 index 0000000000..9a3394c72e --- /dev/null +++ b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java @@ -0,0 +1,81 @@ +package jenkins.model.identity; + +import hudson.Extension; +import hudson.model.UnprotectedRootAction; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import org.apache.commons.codec.Charsets; +import org.apache.commons.codec.binary.Base64; +import org.jenkinsci.remoting.util.KeyUtils; + +/** + * A simple root action that exposes the public key to users so that they do not need to search for the + * {@code X-Instance-Identity} response header, also exposes the fingerprint of the public key so that people + * can verify a fingerprint of a master before connecting to it. + * + * @since FIXME + */ +@Extension +public class IdentityRootAction implements UnprotectedRootAction { + /** + * {@inheritDoc} + */ + @Override + public String getIconFileName() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getUrlName() { + return InstanceIdentityProvider.get(RSAPrivateKey.class) == null ? null : "instance-identity"; + } + + /** + * Returns the PEM encoded public key. + * + * @return the PEM encoded public key. + */ + public String getPublicKey() { + InstanceIdentityProvider provider = + InstanceIdentityProvider.get(RSAPrivateKey.class); + RSAPublicKey key = provider == null ? null : provider.getPublicKey(); + if (key == null) { + return null; + } + byte[] encoded = Base64.encodeBase64(key.getEncoded()); + int index = 0; + StringBuilder buf = new StringBuilder(encoded.length + 20); + while (index < encoded.length) { + int len = Math.min(64, encoded.length - index); + if (index > 0) { + buf.append("\n"); + } + buf.append(new String(encoded, index, len, Charsets.UTF_8)); + index += len; + } + return String.format("-----BEGIN PUBLIC KEY-----%n%s%n-----END PUBLIC KEY-----%n", buf.toString()); + } + + /** + * Returns the fingerprint of the public key. + * + * @return the fingerprint of the public key. + */ + public String getFingerprint() { + InstanceIdentityProvider provider = + InstanceIdentityProvider.get(RSAPrivateKey.class); + RSAPublicKey key = provider == null ? null : provider.getPublicKey(); + return key == null ? null : KeyUtils.fingerprint(key); + } +} diff --git a/core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.jelly b/core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.jelly new file mode 100644 index 0000000000..dbd7dfd51b --- /dev/null +++ b/core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.jelly @@ -0,0 +1,22 @@ + + + + + + + + + + + + +

      ${%Instance Identity}

      +

      ${%blurb}

      +

      ${%Public Key}

      +
      ${it.publicKey}
      +

      ${%Fingerprint}

      +
      ${it.fingerprint}
      + + + diff --git a/core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.properties b/core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.properties new file mode 100644 index 0000000000..618080811c --- /dev/null +++ b/core/src/main/resources/jenkins/model/identity/IdentityRootAction/index.properties @@ -0,0 +1,3 @@ +blurb=Every Jenkins instance has a pair of public and private keys used to uniquely identify that Jenkins instance. \ + The public key is published in the X-Instance-Identity header for web requests against the Jenkins UI. \ + You can also find the key and the fingerprint of the key on this page. From e6c6de4b0eea3de74beff4406611f5a2c23fbc15 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 26 Jul 2016 15:23:26 +0100 Subject: [PATCH 43/89] [JENKINS-36871] `null` is the correct return value to indicate protocol disabled --- core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 00ae97001d..9ef7789bbf 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -39,8 +39,7 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { @Override public String getName() { - if (ENABLED) return "JNLP3-connect"; - else return "JNLP3-disabled"; + return ENABLED ? "JNLP3-connect" : null; } @Override From 16b0aede6ae4b68f223bf3dd972ddef3b982429c Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 26 Jul 2016 15:24:15 +0100 Subject: [PATCH 44/89] [JENKINS-36871] Advertise the instance identity for the TCP Agent details page --- .../java/hudson/TcpSlaveAgentListener.java | 20 +++++++++++++++++++ .../hudson/TcpSlaveAgentListener/index.jelly | 2 ++ 2 files changed, 22 insertions(+) diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index e66778a008..1f4d4ba79c 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -23,6 +23,11 @@ */ package hudson; +import java.nio.charset.Charset; +import java.security.interfaces.RSAPublicKey; +import java.security.interfaces.RSAPrivateKey; +import javax.annotation.Nullable; +import jenkins.model.identity.InstanceIdentityProvider; import jenkins.util.SystemProperties; import hudson.slaves.OfflineCause; import java.io.DataOutputStream; @@ -44,6 +49,7 @@ import java.net.Socket; import java.nio.channels.ServerSocketChannel; import java.util.logging.Level; import java.util.logging.Logger; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; /** @@ -103,6 +109,20 @@ public final class TcpSlaveAgentListener extends Thread { return CLI_PORT != null ? CLI_PORT : getPort(); } + /** + * Gets the Base64 encoded public key that forms part of this instance's identity keypair. + * @return the Base64 encoded public key + * @since FIXME + */ + @Nullable + public String getIdentityPublicKey() { + InstanceIdentityProvider provider = + InstanceIdentityProvider.get(RSAPrivateKey.class); + RSAPublicKey key = provider == null ? null : provider.getPublicKey(); + return key == null ? null : new String(Base64.encodeBase64(key.getEncoded()), Charset.forName("UTF-8")); + } + + @Override public void run() { try { diff --git a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly index 99585e9896..1aaa2aa4b3 100644 --- a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly +++ b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly @@ -34,6 +34,8 @@ THE SOFTWARE. if the host name is overriden, advertise that as well --> + + Jenkins From 0e353c72e201bbec1516835a6413f9e10a5a9d63 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 26 Jul 2016 17:12:55 +0100 Subject: [PATCH 45/89] [JENKINS-36871] Address code review comments --- .../model/identity/IdentityRootAction.java | 24 +++++++++++++++-- .../identity/InstanceIdentityProvider.java | 27 ++++++++++++------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/jenkins/model/identity/IdentityRootAction.java b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java index 9a3394c72e..33ec863350 100644 --- a/core/src/main/java/jenkins/model/identity/IdentityRootAction.java +++ b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java @@ -2,11 +2,12 @@ package jenkins.model.identity; import hudson.Extension; import hudson.model.UnprotectedRootAction; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.apache.commons.codec.Charsets; import org.apache.commons.codec.binary.Base64; -import org.jenkinsci.remoting.util.KeyUtils; /** * A simple root action that exposes the public key to users so that they do not need to search for the @@ -76,6 +77,25 @@ public class IdentityRootAction implements UnprotectedRootAction { InstanceIdentityProvider provider = InstanceIdentityProvider.get(RSAPrivateKey.class); RSAPublicKey key = provider == null ? null : provider.getPublicKey(); - return key == null ? null : KeyUtils.fingerprint(key); + if (key == null) { + return null; + } + // TODO replace with org.jenkinsci.remoting.util.KeyUtils once JENKINS-36871 changes are merged + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.reset(); + byte[] bytes = digest.digest(key.getEncoded()); + StringBuilder result = new StringBuilder(Math.max(0, bytes.length * 3 - 1)); + for (int i = 0; i < bytes.length; i++) { + if (i > 0) { + result.append(':'); + } + int b = bytes[i] & 0xFF; + result.append(Character.forDigit((b>>4)&0x0f, 16)).append(Character.forDigit(b&0xf, 16)); + } + return result.toString(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("JLS mandates MD5 support"); + } } } diff --git a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java index 6bd05190e0..953e027f00 100644 --- a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java +++ b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java @@ -30,6 +30,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; /** * A source of instance identity. @@ -43,17 +44,21 @@ public abstract class InstanceIdentityProvider Date: Tue, 26 Jul 2016 17:44:31 +0100 Subject: [PATCH 46/89] [JENKINS-36871] Add a test for the slave agent listener --- .../hudson/TcpSlaveAgentListener/index.jelly | 1 + .../hudson/TcpSlaveAgentListenerTest.java | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 test/src/test/java/hudson/TcpSlaveAgentListenerTest.java diff --git a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly index 1aaa2aa4b3..ae72aa3b78 100644 --- a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly +++ b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + + false diff --git a/war/pom.xml b/war/pom.xml index 485cf5e01b..c128b33a5a 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -408,7 +408,7 @@ THE SOFTWARE. org.jenkins-ci.plugins bouncycastle-api - 2.16.0-20160725.164257-1 + 2.16.0 hpi From 9ceab7a66acc1169ce107d99346934c12c32df55 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 27 Jul 2016 09:30:32 +0100 Subject: [PATCH 56/89] [JENKINS-36922,JENKINS-36923] Noting merges --- changelog.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog.html b/changelog.html index c9c9677174..f4e9c336d3 100644 --- a/changelog.html +++ b/changelog.html @@ -56,6 +56,12 @@ Upcoming changes From 78490d75c72eedea78690acbb8d0967fdb43c796 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 27 Jul 2016 10:12:31 +0100 Subject: [PATCH 57/89] [JENKINS-36871] Tighten the new API contract with regard to uncaught exceptions --- .../identity/InstanceIdentityProvider.java | 93 +++++++++++++++++-- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java index f6c41a53d0..1d7be2732d 100644 --- a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java +++ b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java @@ -35,6 +35,8 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -47,6 +49,11 @@ import javax.annotation.Nonnull; */ public abstract class InstanceIdentityProvider implements ExtensionPoint { + + /** + * Our logger. + */ + private static final Logger LOGGER = Logger.getLogger(InstanceIdentityProvider.class.getName()); /** * RSA keys. */ @@ -146,11 +153,23 @@ public abstract class InstanceIdentityProvider InstanceIdentityProvider get( @Nonnull KeyTypes type) { for (InstanceIdentityProvider provider : ExtensionList.lookup(InstanceIdentityProvider.class)) { - KeyPair keyPair = provider.getKeyPair(); - if (keyPair != null - && type.pubKeyType.isInstance(keyPair.getPublic()) - && type.privKeyType.isInstance(keyPair.getPrivate())) { - return (InstanceIdentityProvider) provider; + try { + KeyPair keyPair = provider.getKeyPair(); + if (keyPair != null + && type.pubKeyType.isInstance(keyPair.getPublic()) + && type.privKeyType.isInstance(keyPair.getPrivate())) { + return (InstanceIdentityProvider) provider; + } + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Instance identity provider " + provider + " propagated a runtime exception", e); + } catch (Error e) { + LOGGER.log(Level.INFO, + "Encountered an error while consulting instance identity provider " + provider, e); + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, + "Instance identity provider " + provider + " propagated an uncaught exception", e); } } return null; @@ -185,7 +204,21 @@ public abstract class InstanceIdentityProvider provider = get(this); - return provider == null ? null : provider.getKeyPair(); + try { + return provider == null ? null : provider.getKeyPair(); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Instance identity provider " + provider + " propagated a runtime exception", e); + return null; + } catch (Error e) { + LOGGER.log(Level.INFO, + "Encountered an error while consulting instance identity provider " + provider, e); + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, + "Instance identity provider " + provider + " propagated an uncaught exception", e); + return null; + } } /** @@ -196,7 +229,21 @@ public abstract class InstanceIdentityProvider provider = get(this); - return provider == null ? null : provider.getPublicKey(); + try { + return provider == null ? null : provider.getPublicKey(); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Instance identity provider " + provider + " propagated a runtime exception", e); + return null; + } catch (Error e) { + LOGGER.log(Level.INFO, + "Encountered an error while consulting instance identity provider " + provider, e); + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, + "Instance identity provider " + provider + " propagated an uncaught exception", e); + return null; + } } /** @@ -207,7 +254,21 @@ public abstract class InstanceIdentityProvider provider = get(this); - return provider == null ? null : provider.getPrivateKey(); + try { + return provider == null ? null : provider.getPrivateKey(); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Instance identity provider " + provider + " propagated a runtime exception", e); + return null; + } catch (Error e) { + LOGGER.log(Level.INFO, + "Encountered an error while consulting instance identity provider " + provider, e); + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, + "Instance identity provider " + provider + " propagated an uncaught exception", e); + return null; + } } /** @@ -219,7 +280,21 @@ public abstract class InstanceIdentityProvider provider = get(this); - return provider == null ? null : provider.getCertificate(); + try { + return provider == null ? null : provider.getCertificate(); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Instance identity provider " + provider + " propagated a runtime exception", e); + return null; + } catch (Error e) { + LOGGER.log(Level.INFO, + "Encountered an error while consulting instance identity provider " + provider, e); + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, + "Instance identity provider " + provider + " propagated an uncaught exception", e); + return null; + } } } From a8ee5752b9913f45350e0e795c5cec2aaa12b7ac Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 27 Jul 2016 14:35:07 +0100 Subject: [PATCH 58/89] Noting merge of PR#2480 and integrate instance-identity 2.1 release --- changelog.html | 2 +- .../model/identity/IdentityRootAction.java | 2 +- .../identity/InstanceIdentityProvider.java | 2 +- .../hudson/TcpSlaveAgentListenerTest.java | 24 ------------------- .../identity/IdentityRootActionTest.java | 24 ------------------- war/pom.xml | 2 +- 6 files changed, 4 insertions(+), 52 deletions(-) diff --git a/changelog.html b/changelog.html index f4e9c336d3..77338f5fec 100644 --- a/changelog.html +++ b/changelog.html @@ -60,7 +60,7 @@ Upcoming changes Decouple bouncycastle libraries from Jenkins into bouncycastle-api plugin (issue 36923)
    • - Upgrade to instance-identity module 2.0 + Upgrade to instance-identity module 2.1 (issue 36923)
    diff --git a/core/src/main/java/jenkins/model/identity/IdentityRootAction.java b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java index 12510dbd29..6497c012d1 100644 --- a/core/src/main/java/jenkins/model/identity/IdentityRootAction.java +++ b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java @@ -14,7 +14,7 @@ import org.apache.commons.codec.binary.Base64; * {@code X-Instance-Identity} response header, also exposes the fingerprint of the public key so that people * can verify a fingerprint of a master before connecting to it. * - * @since FIXME + * @since 2.16 */ @Extension public class IdentityRootAction implements UnprotectedRootAction { diff --git a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java index 1d7be2732d..c65e27fa90 100644 --- a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java +++ b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java @@ -45,7 +45,7 @@ import javax.annotation.Nonnull; * * @param the type of public key. * @param the type of private key. - * @since FIXME + * @since 2.16 */ public abstract class InstanceIdentityProvider implements ExtensionPoint { diff --git a/test/src/test/java/hudson/TcpSlaveAgentListenerTest.java b/test/src/test/java/hudson/TcpSlaveAgentListenerTest.java index 4619570452..596836c961 100644 --- a/test/src/test/java/hudson/TcpSlaveAgentListenerTest.java +++ b/test/src/test/java/hudson/TcpSlaveAgentListenerTest.java @@ -43,28 +43,4 @@ public class TcpSlaveAgentListenerTest { Page p = r.createWebClient().goTo("tcpSlaveAgentListener", "text/plain"); assertThat(p.getWebResponse().getResponseHeaderValue("X-Instance-Identity"), notNullValue()); } - - @TestExtension // TODO remove once instance-identity with provider impl embedded in war - public static class ProviderImpl extends InstanceIdentityProvider { - - private final KeyPair keyPair; - - public ProviderImpl() throws NoSuchAlgorithmException, InvalidKeySpecException { - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(1024); - this.keyPair = generator.generateKeyPair(); - } - - @Nullable - @Override - public KeyPair getKeyPair() { - return keyPair; - } - - @Nullable - @Override - public X509Certificate getCertificate() { - return null; - } - } } diff --git a/test/src/test/java/jenkins/model/identity/IdentityRootActionTest.java b/test/src/test/java/jenkins/model/identity/IdentityRootActionTest.java index a494b48832..70bd352619 100644 --- a/test/src/test/java/jenkins/model/identity/IdentityRootActionTest.java +++ b/test/src/test/java/jenkins/model/identity/IdentityRootActionTest.java @@ -30,28 +30,4 @@ public class IdentityRootActionTest { assertThat(p.getElementById("fingerprint").getTextContent(), containsString(ExtensionList.lookup(UnprotectedRootAction.class).get(IdentityRootAction.class).getFingerprint())); } - - @TestExtension // TODO remove once instance-identity with provider impl embedded in war - public static class ProviderImpl extends InstanceIdentityProvider { - - private final KeyPair keyPair; - - public ProviderImpl() throws NoSuchAlgorithmException, InvalidKeySpecException { - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(1024); - this.keyPair = generator.generateKeyPair(); - } - - @Nullable - @Override - public KeyPair getKeyPair() { - return keyPair; - } - - @Nullable - @Override - public X509Certificate getCertificate() { - return null; - } - } } diff --git a/war/pom.xml b/war/pom.xml index c128b33a5a..3579e55dc9 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -99,7 +99,7 @@ THE SOFTWARE. org.jenkins-ci.modules instance-identity - 2.0 + 2.1 org.jenkins-ci.modules From cd2416745847c87ef630ad89367c1b63450dbe18 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 27 Jul 2016 10:29:25 -0400 Subject: [PATCH 59/89] [JENKINS-29922] Correct the default value handling of ArtifactArchiver.excludes. --- .../java/hudson/tasks/ArtifactArchiver.java | 7 ++-- test/pom.xml | 6 ++++ .../hudson/tasks/ArtifactArchiverTest.java | 35 ++++++++++--------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/hudson/tasks/ArtifactArchiver.java b/core/src/main/java/hudson/tasks/ArtifactArchiver.java index 7ebc1a4a9a..ca1f154f15 100644 --- a/core/src/main/java/hudson/tasks/ArtifactArchiver.java +++ b/core/src/main/java/hudson/tasks/ArtifactArchiver.java @@ -50,6 +50,7 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.CheckForNull; import net.sf.json.JSONObject; import javax.annotation.Nonnull; @@ -76,7 +77,7 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep { /** * Possibly null 'excludes' pattern as in Ant. */ - private String excludes = ""; + private String excludes; @Deprecated private Boolean latestOnly; @@ -154,11 +155,11 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep { return artifacts; } - public String getExcludes() { + public @CheckForNull String getExcludes() { return excludes; } - @DataBoundSetter public final void setExcludes(String excludes) { + @DataBoundSetter public final void setExcludes(@CheckForNull String excludes) { this.excludes = Util.fixEmptyAndTrim(excludes); } diff --git a/test/pom.xml b/test/pom.xml index 384daa39a4..9693291958 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -132,6 +132,12 @@ THE SOFTWARE. 1.2-beta-4 test + + org.jenkins-ci.plugins + structs + 1.2 + test + org.jvnet.mock-javamail mock-javamail diff --git a/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java b/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java index 77008ff121..9ce04280ae 100644 --- a/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java +++ b/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java @@ -27,8 +27,8 @@ package hudson.tasks; import hudson.AbortException; import hudson.FilePath; import hudson.Launcher; +import hudson.Util; import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; @@ -40,6 +40,7 @@ import java.net.HttpURLConnection; import java.util.Collections; import java.util.List; import jenkins.util.VirtualFile; +import org.jenkinsci.plugins.structs.describable.DescribableModel; import static org.junit.Assert.*; import static org.junit.Assume.*; @@ -155,19 +156,7 @@ public class ArtifactArchiverTest { assertFalse(kids[0].exists()); j.createWebClient().assertFails(b.getUrl() + "artifact/hack", HttpURLConnection.HTTP_NOT_FOUND); } - - private void runNewBuildAndStartUnitlIsCreated(AbstractProject project) throws InterruptedException{ - int buildNumber = project.getNextBuildNumber(); - project.scheduleBuild2(0); - int count = 0; - while(project.getBuildByNumber(buildNumber)==null && count<50){ - Thread.sleep(100); - count ++; - } - if(project.getBuildByNumber(buildNumber)==null) - fail("Build " + buildNumber + " did not created."); - } - + static class CreateArtifact extends TestBuilder { public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { build.getWorkspace().child("f").write("content", "UTF-8"); @@ -197,8 +186,22 @@ public class ArtifactArchiverTest { assertFalse(project.getBuildByNumber(2).getHasArtifacts()); } - - + @Issue("JENKINS-29922") + @Test + public void configRoundTrip() throws Exception { + ArtifactArchiver aa = new ArtifactArchiver("*.txt"); + assertNull(Util.fixEmpty(aa.getExcludes())); // null and "" behave the same, we do not care which it is + assertEquals("{artifacts=*.txt}", DescribableModel.uninstantiate_(aa).toString()); // but we do care that excludes is considered to be at the default + aa = j.configRoundtrip(aa); + assertEquals("*.txt", aa.getArtifacts()); + assertNull(Util.fixEmpty(aa.getExcludes())); + assertEquals("{artifacts=*.txt}", DescribableModel.uninstantiate_(aa).toString()); + aa.setExcludes("README.txt"); + aa = j.configRoundtrip(aa); + assertEquals("*.txt", aa.getArtifacts()); + assertEquals("README.txt", aa.getExcludes()); + assertEquals("{artifacts=*.txt, excludes=README.txt}", DescribableModel.uninstantiate_(aa).toString()); // TreeMap, so attributes will be sorted + } static class CreateDefaultExcludesArtifact extends TestBuilder { public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { From 3a4ba01622076d399adc5975ff6d169af50b141c Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 27 Jul 2016 15:41:12 +0100 Subject: [PATCH 60/89] [FIXED JENKINS-36996] Hide the Java Web Start launcher when the TCP agent port is disabled --- .../main/java/hudson/slaves/JNLPLauncher.java | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/slaves/JNLPLauncher.java b/core/src/main/java/hudson/slaves/JNLPLauncher.java index 37218e4fac..a58242b3d5 100644 --- a/core/src/main/java/hudson/slaves/JNLPLauncher.java +++ b/core/src/main/java/hudson/slaves/JNLPLauncher.java @@ -23,10 +23,13 @@ */ package hudson.slaves; -import hudson.model.Descriptor; -import hudson.model.TaskListener; -import hudson.Util; import hudson.Extension; +import hudson.Util; +import hudson.model.Descriptor; +import hudson.model.DescriptorVisibilityFilter; +import hudson.model.TaskListener; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; @@ -94,4 +97,28 @@ public class JNLPLauncher extends ComputerLauncher { } }; + /** + * Hides the JNLP launcher when the JNLP agent port is not enabled. + * @since FIXME + */ + @Extension + public static class DescriptorVisibilityFilterImpl extends DescriptorVisibilityFilter { + + /** + * {@inheritDoc} + */ + @Override + public boolean filter(@CheckForNull Object context, @Nonnull Descriptor descriptor) { + return descriptor.clazz != JNLPLauncher.class || Jenkins.getInstance().getTcpSlaveAgentListener() != null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean filterType(@Nonnull Class contextClass, @Nonnull Descriptor descriptor) { + return descriptor.clazz != JNLPLauncher.class || Jenkins.getInstance().getTcpSlaveAgentListener() != null; + } + } + } From 404384eefe9e5abf65ae68b4b1bf0accf3dff8eb Mon Sep 17 00:00:00 2001 From: Alvaro Lobato Date: Wed, 27 Jul 2016 12:31:29 -0400 Subject: [PATCH 61/89] [JENKINS-36908] Make sure that the All view is created (#2472) * [JENKINS-36908] Make sure that the All view is created * Revert old All view behaviour * Fix changed comment * Tests * Delete unwanted file. --- core/src/main/java/jenkins/model/Jenkins.java | 5 +---- test/src/test/java/hudson/model/ViewTest.java | 16 ++++++++++++++++ .../ViewTest/testAllViewCreatedIfNoPrimary.zip | Bin 0 -> 364 bytes .../ViewTest/testAllViewNotCreatedIfPrimary.zip | Bin 0 -> 378 bytes 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 test/src/test/resources/hudson/model/ViewTest/testAllViewCreatedIfNoPrimary.zip create mode 100644 test/src/test/resources/hudson/model/ViewTest/testAllViewNotCreatedIfPrimary.zip diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index e01d3085f7..1852e7c110 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -2868,15 +2868,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve // initialize views by inserting the default view if necessary // this is both for clean Jenkins and for backward compatibility. - if(views.size()==0) { + if(views.size()==0 || primaryView==null) { View v = new AllView(Messages.Hudson_ViewName()); setViewOwner(v); views.add(0,v); primaryView = v.getViewName(); } - if (primaryView==null) { - primaryView = views.get(0).getViewName(); - } if (useSecurity!=null && !useSecurity) { // forced reset to the unsecure mode. diff --git a/test/src/test/java/hudson/model/ViewTest.java b/test/src/test/java/hudson/model/ViewTest.java index 36841da295..82314e9841 100644 --- a/test/src/test/java/hudson/model/ViewTest.java +++ b/test/src/test/java/hudson/model/ViewTest.java @@ -64,6 +64,7 @@ import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.jvnet.hudson.test.MockAuthorizationStrategy; import org.jvnet.hudson.test.MockFolder; import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.recipes.LocalData; import org.kohsuke.stapler.DataBoundConstructor; /** @@ -505,5 +506,20 @@ public class ViewTest { private void assertCheckJobName(ViewGroup context, String name, FormValidation.Kind expected) { assertEquals(expected, context.getPrimaryView().doCheckJobName(name).kind); } + + + @Test + @Issue("JENKINS-36908") + @LocalData + public void testAllViewCreatedIfNoPrimary() throws Exception { + assertNotNull(j.getInstance().getView("All")); + } + + @Test + @Issue("JENKINS-36908") + @LocalData + public void testAllViewNotCreatedIfPrimary() throws Exception { + assertNull(j.getInstance().getView("All")); + } } diff --git a/test/src/test/resources/hudson/model/ViewTest/testAllViewCreatedIfNoPrimary.zip b/test/src/test/resources/hudson/model/ViewTest/testAllViewCreatedIfNoPrimary.zip new file mode 100644 index 0000000000000000000000000000000000000000..7e32a82640eaa9259f3a158b50a35a5c216737c0 GIT binary patch literal 364 zcmWIWW@Zs#U|`^2IN|-Qab1q9taNshJVKjWm1;*WL{e{8$4rDan_TltcT z`);c*ENi;I?Zm57l|imq96^6h1^)?Id`yJ*QituH>I30YCcM99W}GmzXIpkVd)2Wu zL4Rh(be*qqbK1)g;LXS+$BZi+B!D5pzyS0u!;(f23prF+A)$g6A_3m4Y#^14Ko|g| I*+7~Z0H8>MF#rGn literal 0 HcmV?d00001 diff --git a/test/src/test/resources/hudson/model/ViewTest/testAllViewNotCreatedIfPrimary.zip b/test/src/test/resources/hudson/model/ViewTest/testAllViewNotCreatedIfPrimary.zip new file mode 100644 index 0000000000000000000000000000000000000000..397d6d14a44666ed3e5f0f52abb61fe3e055f76d GIT binary patch literal 378 zcmWIWW@Zs#U|`^2PzwI-;S$Mw`~r~I&d9*P#UR6woS&DLnXXrnn-dzs$-w*|e@3`m z!Hn?I3T_5QmajlXV7<8k8?z1@h}`)rdPu}3G;2HGCL`5gF5NA6du#JpH;LTa{P@Xx zZTHX>&JKq!-+g&+?w4mK7KJPudH1cG_cU1b!R-r?|6{}F%kMruJu8{(uW_l&9?g~u z?%gw|Yo6nr=KSKplh;4BgE~87X4racS(mu(w2BVvLiVGqE4($YKArOF*Aa8(p5uFd zWR&hqU9~-rCD%V=xuL-9eT6)0^B!*fQ+udotBGS|*@6OwUa>uVzpiJT{PAg#~waJu@?4|z~Xl&}%tYZxDW@M6M#uX|O!0=&U0Q#L_Nh64b97e2= XFhUET0B=?{kV-}%3;@z1Ak7Q_)<=uH literal 0 HcmV?d00001 From 8e536513e8d7b08cda63201ba14ee6e30289d713 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 27 Jul 2016 20:46:55 +0100 Subject: [PATCH 62/89] [JENKINS-36996] Noting merge --- changelog.html | 3 +++ core/src/main/java/hudson/slaves/JNLPLauncher.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.html b/changelog.html index 77338f5fec..fb81e637dc 100644 --- a/changelog.html +++ b/changelog.html @@ -62,6 +62,9 @@ Upcoming changes
  • Upgrade to instance-identity module 2.1 (issue 36923) +
  • + Hide the Java Web Start launcher when the TCP agent port is disabled + (issue 36996)
diff --git a/core/src/main/java/hudson/slaves/JNLPLauncher.java b/core/src/main/java/hudson/slaves/JNLPLauncher.java index a58242b3d5..c83382f796 100644 --- a/core/src/main/java/hudson/slaves/JNLPLauncher.java +++ b/core/src/main/java/hudson/slaves/JNLPLauncher.java @@ -99,7 +99,8 @@ public class JNLPLauncher extends ComputerLauncher { /** * Hides the JNLP launcher when the JNLP agent port is not enabled. - * @since FIXME + * + * @since 2.16 */ @Extension public static class DescriptorVisibilityFilterImpl extends DescriptorVisibilityFilter { From 389b05752c2f85d191950a5d3db95bc95707a661 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 28 Jul 2016 12:58:52 +0100 Subject: [PATCH 63/89] [JENKINS-37031] Advertise the enabled Agent Protocols to enable faster connection negotiation --- .../java/hudson/TcpSlaveAgentListener.java | 26 +++++++++++++++++-- .../hudson/TcpSlaveAgentListener/index.jelly | 2 ++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 76d95484c5..752d63bea2 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -25,7 +25,6 @@ package hudson; import java.nio.charset.Charset; import java.security.interfaces.RSAPublicKey; -import java.security.interfaces.RSAPrivateKey; import javax.annotation.Nullable; import jenkins.model.identity.InstanceIdentityProvider; import jenkins.util.SystemProperties; @@ -112,7 +111,7 @@ public final class TcpSlaveAgentListener extends Thread { /** * Gets the Base64 encoded public key that forms part of this instance's identity keypair. * @return the Base64 encoded public key - * @since FIXME + * @since 2.16 */ @Nullable public String getIdentityPublicKey() { @@ -120,6 +119,29 @@ public final class TcpSlaveAgentListener extends Thread { return key == null ? null : new String(Base64.encodeBase64(key.getEncoded()), Charset.forName("UTF-8")); } + /** + * Returns a comma separated list of the enabled {@link AgentProtocol#getName()} implementations so that + * clients can avoid creating additional work for the server attempting to connect with unsupported protocols. + * + * @return a comma separated list of the enabled {@link AgentProtocol#getName()} implementations + * @since FIXME + */ + public String getAgentProtocolNames() { + StringBuilder result = new StringBuilder(); + boolean first = true; + for (AgentProtocol p : AgentProtocol.all()) { + String name = p.getName(); + if (name != null) { + if (first) { + first = false; + } else { + result.append(", "); + } + result.append(name); + } + } + return result.toString(); + } @Override public void run() { diff --git a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly index ae72aa3b78..63e3d8de4e 100644 --- a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly +++ b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly @@ -37,6 +37,8 @@ THE SOFTWARE. + + Jenkins From 17894bc16216fa495b611224454eff8014065491 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 28 Jul 2016 15:36:33 +0100 Subject: [PATCH 64/89] [FIXED JENKINS-37032] Add a UI to allow controling the agent protocol enablement --- .../java/hudson/TcpSlaveAgentListener.java | 39 +++++---- .../security/GlobalSecurityConfiguration.java | 19 +++++ core/src/main/java/jenkins/AgentProtocol.java | 18 ++++ core/src/main/java/jenkins/model/Jenkins.java | 83 +++++++++++++++++++ .../hudson/TcpSlaveAgentListener/index.jelly | 2 +- .../help-agentProtocol.html | 4 + .../GlobalSecurityConfiguration/index.groovy | 15 ++++ 7 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 core/src/main/resources/hudson/security/GlobalSecurityConfiguration/help-agentProtocol.html diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 752d63bea2..6e8d1d0bf8 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -26,6 +26,7 @@ package hudson; import java.nio.charset.Charset; import java.security.interfaces.RSAPublicKey; import javax.annotation.Nullable; +import jenkins.model.Jenkins; import jenkins.model.identity.InstanceIdentityProvider; import jenkins.util.SystemProperties; import hudson.slaves.OfflineCause; @@ -50,6 +51,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; /** * Listens to incoming TCP connections from JNLP agents and CLI. @@ -127,20 +129,7 @@ public final class TcpSlaveAgentListener extends Thread { * @since FIXME */ public String getAgentProtocolNames() { - StringBuilder result = new StringBuilder(); - boolean first = true; - for (AgentProtocol p : AgentProtocol.all()) { - String name = p.getName(); - if (name != null) { - if (first) { - first = false; - } else { - result.append(", "); - } - result.append(name); - } - } - return result.toString(); + return StringUtils.join(Jenkins.getInstance().getAgentProtocols(), ", "); } @Override @@ -218,9 +207,13 @@ public final class TcpSlaveAgentListener extends Thread { if(s.startsWith("Protocol:")) { String protocol = s.substring(9); AgentProtocol p = AgentProtocol.of(protocol); - if (p!=null) - p.handle(this.s); - else + if (p!=null) { + if (Jenkins.getInstance().getAgentProtocols().contains(protocol)) { + p.handle(this.s); + } else { + error(out, "Disabled protocol:" + s); + } + } else error(out, "Unknown protocol:" + s); } else { error(out, "Unrecognized protocol: "+s); @@ -268,6 +261,18 @@ public final class TcpSlaveAgentListener extends Thread { } } + /** + * Allow essential {@link AgentProtocol} implementations (basically {@link PingAgentProtocol}) + * to be always enabled. + * + * @return {@code true} if the protocol can never be disbaled. + * @since FIXME + */ + @Override + public boolean isRequired() { + return true; + } + @Override public String getName() { return "Ping"; diff --git a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java index 36edde3f4e..5fa446fef7 100644 --- a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java +++ b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java @@ -35,6 +35,8 @@ import hudson.model.ManagementLink; import hudson.util.FormApply; import java.io.IOException; +import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -43,6 +45,7 @@ import javax.servlet.ServletException; import jenkins.model.GlobalConfigurationCategory; import jenkins.model.Jenkins; import jenkins.util.ServerTcpPort; +import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.jenkinsci.Symbol; @@ -69,6 +72,10 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr return Jenkins.getInstance().getSlaveAgentPort(); } + public Set getAgentProtocols() { + return Jenkins.getInstance().getAgentProtocols(); + } + public boolean isDisableRememberMe() { return Jenkins.getInstance().isDisableRememberMe(); } @@ -100,6 +107,18 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr } catch (IOException e) { throw new hudson.model.Descriptor.FormException(e, "slaveAgentPortType"); } + Set agentProtocols = new TreeSet<>(); + if (security.has("agentProtocol")) { + Object protocols = security.get("agentProtocol"); + if (protocols instanceof JSONArray) { + for (int i = 0; i < ((JSONArray) protocols).size(); i++) { + agentProtocols.add(((JSONArray) protocols).getString(i)); + } + } else { + agentProtocols.add(protocols.toString()); + } + } + j.setAgentProtocols(agentProtocols); } else { j.disableSecurity(); } diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java index 667f881cde..14d2a6858e 100644 --- a/core/src/main/java/jenkins/AgentProtocol.java +++ b/core/src/main/java/jenkins/AgentProtocol.java @@ -21,6 +21,24 @@ import java.net.Socket; * @see TcpSlaveAgentListener */ public abstract class AgentProtocol implements ExtensionPoint { + /** + * Allow experimental {@link AgentProtocol} implementations to declare being opt-in. + * @return {@code true} if the protocol requires explicit opt-in. + * @since FIXME + */ + public boolean isOptIn() { + return false; + } + /** + * Allow essential {@link AgentProtocol} implementations (basically {@link TcpSlaveAgentListener.PingAgentProtocol}) + * to be always enabled. + * + * @return {@code true} if the protocol can never be disbaled. + * @since FIXME + */ + public boolean isRequired() { + return false; + } /** * Protocol name. * diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index e01d3085f7..ca685aaabf 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -50,6 +50,7 @@ import hudson.Plugin; import hudson.PluginManager; import hudson.PluginWrapper; import hudson.ProxyConfiguration; +import jenkins.AgentProtocol; import jenkins.util.SystemProperties; import hudson.TcpSlaveAgentListener; import hudson.UDPBroadcastThread; @@ -224,6 +225,7 @@ import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.acegisecurity.ui.AbstractProcessingFilter; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.Script; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.jvnet.hudson.reactor.Executable; import org.jvnet.hudson.reactor.Milestone; @@ -598,6 +600,28 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve */ private int slaveAgentPort = SystemProperties.getInteger(Jenkins.class.getName()+".slaveAgentPort",0); + /** + * The TCP agent protocols that are explicitly disabled (we store the disabled ones so that newer protocols + * are enabled by default). + * + * @since FIXME + */ + private String disabledAgentProtocols; + + /** + * The TCP agent protocols that are {@link AgentProtocol#isOptIn()} and explicitly enabled. + * + * @since FIXME + */ + private String enabledAgentProtocols; + + /** + * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols}. + * + * @since FIXME + */ + private transient Set agentProtocols; + /** * Whitespace-separated labels assigned to the master as a {@link Node}. */ @@ -1060,6 +1084,65 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve launchTcpSlaveAgentListener(); } + /** + * Returns the enabled agent protocols. + * + * @return the enabled agent protocols. + * @since FIXME + */ + public Set getAgentProtocols() { + if (agentProtocols == null) { + // idempotent, so don't care if we do this concurrently, should all get same result + Set result = new TreeSet<>(); + Set disabled = new TreeSet<>(); + for (String p : StringUtils.split(StringUtils.defaultIfBlank(disabledAgentProtocols, ""), ",")) { + disabled.add(p.trim()); + } + Set enabled = new TreeSet<>(); + for (String p : StringUtils.split(StringUtils.defaultIfBlank(enabledAgentProtocols, ""), ",")) { + enabled.add(p.trim()); + } + for (AgentProtocol p : AgentProtocol.all()) { + String name = p.getName(); + if (name != null && (p.isRequired() + || (!disabled.contains(name) && (!p.isOptIn() || enabled.contains(name))))) { + result.add(name); + } + } + agentProtocols = result; + return result; + } + return agentProtocols; + } + + /** + * Sets the enabled agent protocols. + * + * @param protocols the enabled agent protocols. + * @since FIXME + */ + public void setAgentProtocols(Set protocols) { + Set disabled = new TreeSet<>(); + Set enabled = new TreeSet<>(); + for (AgentProtocol p : AgentProtocol.all()) { + String name = p.getName(); + if (name != null && !p.isRequired()) { + if (p.isOptIn()) { + if (protocols.contains(name)) { + enabled.add(name); + } + } else { + if (!protocols.contains(name)) { + disabled.add(name); + } + } + } + } + disabledAgentProtocols = fixEmpty(StringUtils.join(disabled, ", ")); + enabledAgentProtocols = fixEmpty(StringUtils.join(enabled, ", ")); + agentProtocols = null; + } + private void launchTcpSlaveAgentListener() throws IOException { synchronized(tcpSlaveAgentListenerLock) { // shutdown previous agent if the port has changed diff --git a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly index 63e3d8de4e..48854e95fb 100644 --- a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly +++ b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly @@ -23,7 +23,7 @@ THE SOFTWARE. --> - + From b012ea9539887d377dab98673fc82bbae2d51e6b Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 29 Jul 2016 00:01:32 +0100 Subject: [PATCH 68/89] [JENKINS-37032] Add display names and optional descriptions --- .../java/hudson/TcpSlaveAgentListener.java | 10 +++++++- .../src/main/java/hudson/cli/CliProtocol.java | 8 ++++++ .../main/java/hudson/cli/CliProtocol2.java | 8 ++++++ core/src/main/java/jenkins/AgentProtocol.java | 12 ++++++++- .../slaves/JnlpSlaveAgentProtocol.java | 8 ++++++ .../slaves/JnlpSlaveAgentProtocol2.java | 8 ++++++ .../slaves/JnlpSlaveAgentProtocol3.java | 8 ++++++ .../main/resources/hudson/Messages.properties | 4 ++- .../hudson/cli/CliProtocol/description.jelly | 4 +++ .../hudson/cli/CliProtocol2/description.jelly | 4 +++ .../resources/hudson/cli/Messages.properties | 2 ++ .../GlobalSecurityConfiguration/index.groovy | 24 ++++++++++++------ .../JnlpSlaveAgentProtocol/description.jelly | 4 +++ .../JnlpSlaveAgentProtocol2/description.jelly | 4 +++ .../JnlpSlaveAgentProtocol3/description.jelly | 4 +++ .../jenkins/slaves/Messages.properties | 25 +++++++++++++++++++ 16 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 core/src/main/resources/hudson/cli/CliProtocol/description.jelly create mode 100644 core/src/main/resources/hudson/cli/CliProtocol2/description.jelly create mode 100644 core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol/description.jelly create mode 100644 core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol2/description.jelly create mode 100644 core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol3/description.jelly create mode 100644 core/src/main/resources/jenkins/slaves/Messages.properties diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 6e8d1d0bf8..238dd51296 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -265,7 +265,7 @@ public final class TcpSlaveAgentListener extends Thread { * Allow essential {@link AgentProtocol} implementations (basically {@link PingAgentProtocol}) * to be always enabled. * - * @return {@code true} if the protocol can never be disbaled. + * @return {@code true} if the protocol can never be disabled. * @since FIXME */ @Override @@ -278,6 +278,14 @@ public final class TcpSlaveAgentListener extends Thread { return "Ping"; } + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return Messages.TcpSlaveAgentListener_PingAgentProtocol_displayName(); + } + @Override public void handle(Socket socket) throws IOException, InterruptedException { try { diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java index aa1d031b4b..b79430db4d 100644 --- a/core/src/main/java/hudson/cli/CliProtocol.java +++ b/core/src/main/java/hudson/cli/CliProtocol.java @@ -36,6 +36,14 @@ public class CliProtocol extends AgentProtocol { return "CLI-connect"; } + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return "Jenkins CLI Protocol/1"; + } + @Override public void handle(Socket socket) throws IOException, InterruptedException { new Handler(nio.getHub(),socket).run(); diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java index a4893cfd94..14913021a8 100644 --- a/core/src/main/java/hudson/cli/CliProtocol2.java +++ b/core/src/main/java/hudson/cli/CliProtocol2.java @@ -28,6 +28,14 @@ public class CliProtocol2 extends CliProtocol { return "CLI2-connect"; } + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return "Jenkins CLI Protocol/2"; + } + @Override public void handle(Socket socket) throws IOException, InterruptedException { new Handler2(nio.getHub(), socket).run(); diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java index 14d2a6858e..0e3374b0e1 100644 --- a/core/src/main/java/jenkins/AgentProtocol.java +++ b/core/src/main/java/jenkins/AgentProtocol.java @@ -33,7 +33,7 @@ public abstract class AgentProtocol implements ExtensionPoint { * Allow essential {@link AgentProtocol} implementations (basically {@link TcpSlaveAgentListener.PingAgentProtocol}) * to be always enabled. * - * @return {@code true} if the protocol can never be disbaled. + * @return {@code true} if the protocol can never be disabled. * @since FIXME */ public boolean isRequired() { @@ -50,6 +50,16 @@ public abstract class AgentProtocol implements ExtensionPoint { */ public abstract String getName(); + /** + * Returns the human readable protocol display name. + * + * @return the human readable protocol display name. + * @since FIXME + */ + public String getDisplayName() { + return getName(); + } + /** * Called by the connection handling thread to execute the protocol. */ diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java index 36ebaf2969..8615b54c00 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java @@ -65,6 +65,14 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol { return "JNLP-connect"; } + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return Messages.JnlpSlaveAgentProtocol_displayName(); + } + @Override public void handle(Socket socket) throws IOException, InterruptedException { new Handler(hub.getHub(),socket).run(); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java index ff9ce64c1a..81ec159372 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java @@ -27,6 +27,14 @@ public class JnlpSlaveAgentProtocol2 extends JnlpSlaveAgentProtocol { return "JNLP2-connect"; } + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return Messages.JnlpSlaveAgentProtocol2_displayName(); + } + @Override public void handle(Socket socket) throws IOException, InterruptedException { new Handler2(hub.getHub(),socket).run(); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 9ef7789bbf..80f755ae20 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -42,6 +42,14 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { return ENABLED ? "JNLP3-connect" : null; } + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return Messages.JnlpSlaveAgentProtocol3_displayName(); + } + @Override public void handle(Socket socket) throws IOException, InterruptedException { new Handler(hub.getHub(), socket).run(); diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 6722b6b940..038ecc0cd6 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -71,4 +71,6 @@ ProxyConfiguration.FailedToConnectViaProxy=Failed to connect to {0}. ProxyConfiguration.FailedToConnect=Failed to connect to {0} (code {1}). ProxyConfiguration.Success=Success -Functions.NoExceptionDetails=No Exception details \ No newline at end of file +Functions.NoExceptionDetails=No Exception details + +TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/cli/CliProtocol/description.jelly b/core/src/main/resources/hudson/cli/CliProtocol/description.jelly new file mode 100644 index 0000000000..e24d569129 --- /dev/null +++ b/core/src/main/resources/hudson/cli/CliProtocol/description.jelly @@ -0,0 +1,4 @@ + + + ${%Accepts connections from CLI clients} + diff --git a/core/src/main/resources/hudson/cli/CliProtocol2/description.jelly b/core/src/main/resources/hudson/cli/CliProtocol2/description.jelly new file mode 100644 index 0000000000..66f27c511c --- /dev/null +++ b/core/src/main/resources/hudson/cli/CliProtocol2/description.jelly @@ -0,0 +1,4 @@ + + + ${%Extends the version 1 protocol by adding transport encryption} + diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index a6ef44d760..f2cf34d4d6 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -94,3 +94,5 @@ OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temp WaitNodeOnlineCommand.ShortDescription=Wait for a node to become online. WaitNodeOfflineCommand.ShortDescription=Wait for a node to become offline. +CliProtocol.displayName=Jenkins CLI Protocol/1 +CliProtocol2.displayName=Jenkins CLI Protocol/2 diff --git a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy index 71c4900c7a..e56f86720c 100644 --- a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy +++ b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy @@ -32,13 +32,23 @@ l.layout(norefresh:true, permission:app.ADMINISTER, title:my.displayName, csscla f.advanced(title: _("Agent protocols"), align:"left") { f.entry(title: _("Agent protocols")) { def agentProtocols = my.agentProtocols; - for (AgentProtocol p: AgentProtocol.all()) { - if (p.name != null && !p.required) { - f.checkbox(name: "agentProtocol", - title: p.name, - checked:agentProtocols.contains(p.name), - json: p.name); - br(); + table(width:"100%") { + for (AgentProtocol p : AgentProtocol.all()) { + if (p.name != null && !p.required) { + f.block() { + f.checkbox(name: "agentProtocol", + title: p.displayName, + checked: agentProtocols.contains(p.name), + json: p.name); + } + tr() { + td(colspan:"2"); + td(class:"setting-description"){ + st.include(from:p, page: "description", optional:true); + } + td(); + } + } } } } diff --git a/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol/description.jelly b/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol/description.jelly new file mode 100644 index 0000000000..d848342d48 --- /dev/null +++ b/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol/description.jelly @@ -0,0 +1,4 @@ + + + ${%Accepts connections from remote clients so that they can be used as additional build agents} + diff --git a/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol2/description.jelly b/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol2/description.jelly new file mode 100644 index 0000000000..b3c0c58444 --- /dev/null +++ b/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol2/description.jelly @@ -0,0 +1,4 @@ + + + ${%Extends the version 1 protocol by adding a per-client cookie, so that we can detect a reconnection from the agent and take appropriate action} + diff --git a/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol3/description.jelly b/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol3/description.jelly new file mode 100644 index 0000000000..c052dfe335 --- /dev/null +++ b/core/src/main/resources/jenkins/slaves/JnlpSlaveAgentProtocol3/description.jelly @@ -0,0 +1,4 @@ + + + ${%Extends the version 2 protocol by adding basic encryption but requires a thread per client} + diff --git a/core/src/main/resources/jenkins/slaves/Messages.properties b/core/src/main/resources/jenkins/slaves/Messages.properties new file mode 100644 index 0000000000..c66841c308 --- /dev/null +++ b/core/src/main/resources/jenkins/slaves/Messages.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright 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. + +JnlpSlaveAgentProtocol.displayName=Java Web Start Agent Protocol/1 +JnlpSlaveAgentProtocol2.displayName=Java Web Start Agent Protocol/2 +JnlpSlaveAgentProtocol3.displayName=Java Web Start Agent Protocol/3 From eb0f66554a680bc631b334be0e05a6b0bdee6504 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 29 Jul 2016 10:17:47 +0100 Subject: [PATCH 69/89] [JENKINS-37032] Switch to element per protocol XML format - Also switch the A/B testing of JNLP3-connect to via opt-in - Also mark that JNLP3-connect will be deprecated by JENKINS-36871 - Also start A/B testing the disabling of JNLP-connect and CLI-connect (should be well safe by now) --- .../src/main/java/hudson/cli/CliProtocol.java | 19 ++++++++++++++ .../main/java/hudson/cli/CliProtocol2.java | 8 ++++++ core/src/main/java/jenkins/model/Jenkins.java | 21 +++++++++------- .../slaves/JnlpSlaveAgentProtocol.java | 19 ++++++++++++++ .../slaves/JnlpSlaveAgentProtocol2.java | 8 ++++++ .../slaves/JnlpSlaveAgentProtocol3.java | 25 +++++++++++++++---- .../java/jenkins/util/SystemProperties.java | 15 +++++++++++ 7 files changed, 101 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java index b79430db4d..f2160fe31f 100644 --- a/core/src/main/java/hudson/cli/CliProtocol.java +++ b/core/src/main/java/hudson/cli/CliProtocol.java @@ -1,6 +1,7 @@ package hudson.cli; import hudson.Extension; +import hudson.Util; import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.Channel.Mode; @@ -31,6 +32,14 @@ public class CliProtocol extends AgentProtocol { @Inject NioChannelSelector nio; + /** + * {@inheritDoc} + */ + @Override + public boolean isOptIn() { + return OPT_IN; + } + @Override public String getName() { return "CLI-connect"; @@ -93,4 +102,14 @@ public class CliProtocol extends AgentProtocol { channel.join(); } } + + /** + * A/B test turning off this protocol by default. + */ + private static final boolean OPT_IN; + + static { + byte hash = Util.fromHexString(Jenkins.getInstance().getLegacyInstanceId())[0]; + OPT_IN = (hash % 10) == 0; + } } diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java index 14913021a8..cd7d8b04f6 100644 --- a/core/src/main/java/hudson/cli/CliProtocol2.java +++ b/core/src/main/java/hudson/cli/CliProtocol2.java @@ -28,6 +28,14 @@ public class CliProtocol2 extends CliProtocol { return "CLI2-connect"; } + /** + * {@inheritDoc} + */ + @Override + public boolean isOptIn() { + return false; + } + /** * {@inheritDoc} */ diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index ca685aaabf..88e5470fb0 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -225,7 +225,6 @@ import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.acegisecurity.ui.AbstractProcessingFilter; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.Script; -import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.jvnet.hudson.reactor.Executable; import org.jvnet.hudson.reactor.Milestone; @@ -239,7 +238,6 @@ import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; @@ -602,18 +600,21 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve /** * The TCP agent protocols that are explicitly disabled (we store the disabled ones so that newer protocols - * are enabled by default). + * are enabled by default). Will be {@code null} instead of empty to simplify XML format. * * @since FIXME */ - private String disabledAgentProtocols; + @CheckForNull + private List disabledAgentProtocols; /** * The TCP agent protocols that are {@link AgentProtocol#isOptIn()} and explicitly enabled. + * Will be {@code null} instead of empty to simplify XML format. * * @since FIXME */ - private String enabledAgentProtocols; + @CheckForNull + private List enabledAgentProtocols; /** * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols}. @@ -1095,11 +1096,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve // idempotent, so don't care if we do this concurrently, should all get same result Set result = new TreeSet<>(); Set disabled = new TreeSet<>(); - for (String p : StringUtils.split(StringUtils.defaultIfBlank(disabledAgentProtocols, ""), ",")) { + for (String p : Util.fixNull(disabledAgentProtocols)) { disabled.add(p.trim()); } Set enabled = new TreeSet<>(); - for (String p : StringUtils.split(StringUtils.defaultIfBlank(enabledAgentProtocols, ""), ",")) { + for (String p : Util.fixNull(enabledAgentProtocols)) { enabled.add(p.trim()); } for (AgentProtocol p : AgentProtocol.all()) { @@ -1138,8 +1139,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } } } - disabledAgentProtocols = fixEmpty(StringUtils.join(disabled, ", ")); - enabledAgentProtocols = fixEmpty(StringUtils.join(enabled, ", ")); + disabledAgentProtocols = disabled.isEmpty() ? null : new ArrayList<>(disabled); + enabledAgentProtocols = enabled.isEmpty() ? null : new ArrayList<>(enabled); agentProtocols = null; } @@ -4904,6 +4905,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve // for backward compatibility with <1.75, recognize the tag name "view" as well. XSTREAM.alias("view", ListView.class); XSTREAM.alias("listView", ListView.class); + XSTREAM.addImplicitCollection(Jenkins.class, "disabledAgentProtocols", "disabledAgentProtocol", String.class); + XSTREAM.addImplicitCollection(Jenkins.class, "enabledAgentProtocols", "enabledAgentProtocol", String.class); XSTREAM2.addCriticalField(Jenkins.class, "securityRealm"); XSTREAM2.addCriticalField(Jenkins.class, "authorizationStrategy"); // this seems to be necessary to force registration of converter early enough diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java index 8615b54c00..d0759dbd6b 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java @@ -2,6 +2,7 @@ package jenkins.slaves; import hudson.AbortException; import hudson.Extension; +import hudson.Util; import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; @@ -60,6 +61,14 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol { @Inject NioChannelSelector hub; + /** + * {@inheritDoc} + */ + @Override + public boolean isOptIn() { + return OPT_IN; + } + @Override public String getName() { return "JNLP-connect"; @@ -164,4 +173,14 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol { * This secret value is used as a seed for agents. */ public static final HMACConfidentialKey SLAVE_SECRET = new HMACConfidentialKey(JnlpSlaveAgentProtocol.class,"secret"); + + /** + * A/B test turning off this protocol by default. + */ + private static final boolean OPT_IN; + + static { + byte hash = Util.fromHexString(Jenkins.getInstance().getLegacyInstanceId())[0]; + OPT_IN = (hash % 10) == 0; + } } diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java index 81ec159372..1a2bb649dc 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java @@ -27,6 +27,14 @@ public class JnlpSlaveAgentProtocol2 extends JnlpSlaveAgentProtocol { return "JNLP2-connect"; } + /** + * {@inheritDoc} + */ + @Override + public boolean isOptIn() { + return false; + } + /** * {@inheritDoc} */ diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 80f755ae20..5086d9191a 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -32,14 +32,29 @@ import jenkins.util.SystemProperties; * @author Akshay Dayal * @since 1.XXX */ +// TODO @Deprecated once JENKINS-36871 is merged @Extension public class JnlpSlaveAgentProtocol3 extends AgentProtocol { @Inject NioChannelSelector hub; + /** + * Allow experimental {@link AgentProtocol} implementations to declare being opt-in. + * + * @return {@code true} if the protocol requires explicit opt-in. + * @since FIXME + */ + @Override + public boolean isOptIn() { + return !ENABLED; + } + @Override public String getName() { - return ENABLED ? "JNLP3-connect" : null; + // we only want to force the protocol off for users that have explicitly banned it via system property + // everyone on the A/B test will just have the opt-in flag toggled + // TODO strip all this out and hardcode OptIn==TRUE once JENKINS-36871 is merged + return forceEnabled != Boolean.FALSE ? "JNLP3-connect" : null; } /** @@ -132,12 +147,12 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { */ @Restricted(NoExternalUse.class) public static boolean ENABLED; + private static final Boolean forceEnabled; static { - String propName = JnlpSlaveAgentProtocol3.class.getName() + ".enabled"; - String propertyString = SystemProperties.getString(propName); - if (propertyString != null) - ENABLED = SystemProperties.getBoolean(propName); + forceEnabled = SystemProperties.optBoolean(JnlpSlaveAgentProtocol3.class.getName() + ".enabled"); + if (forceEnabled != null) + ENABLED = forceEnabled; else { byte hash = Util.fromHexString(Jenkins.getActiveInstance().getLegacyInstanceId())[0]; ENABLED = (hash%10)==0; diff --git a/core/src/main/java/jenkins/util/SystemProperties.java b/core/src/main/java/jenkins/util/SystemProperties.java index 12643ff5a5..afdda622b4 100644 --- a/core/src/main/java/jenkins/util/SystemProperties.java +++ b/core/src/main/java/jenkins/util/SystemProperties.java @@ -204,6 +204,21 @@ public class SystemProperties implements ServletContextListener { } return def; } + + /** + * Returns {@link Boolean#TRUE} if the named system property exists and is equal to the string {@code "true} + * (ignoring case), returns {@link Boolean#FALSE} if the system property exists and doesn't equal {@code "true} + * otherwise returns {@code null} if the named system property does not exist. + * + * @param name the system property name. + * @return {@link Boolean#TRUE}, {@link Boolean#FALSE} or {@code null} + * @since FIXME + */ + @CheckForNull + public static Boolean optBoolean(String name) { + String v = getString(name); + return v == null ? null : Boolean.parseBoolean(v); + } /** * Determines the integer value of the system property with the From f02c6c99e8be9095eb04dbd4deb6dc2136fc774a Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 29 Jul 2016 11:50:26 +0100 Subject: [PATCH 70/89] [JENKINS-37032] Document the logic for the preference recording --- core/src/main/java/jenkins/model/Jenkins.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 88e5470fb0..76006d673f 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -1128,6 +1128,25 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve for (AgentProtocol p : AgentProtocol.all()) { String name = p.getName(); if (name != null && !p.isRequired()) { + // we want to record the protocols where the admin has made a concious decision + // thus, if a protocol is opt-in, we record the admin enabling it + // if a protocol is opt-out, we record the admin disabling it + // We should not transition rapidly from opt-in -> opt-out -> opt-in + // the scenario we want to have work is: + // 1. We introduce a new protocol, it starts off as opt-in. Some admins decide to test and opt-in + // 2. We decide that the protocol is ready for general use. It gets marked as opt-out. Any admins + // that took part in early testing now have their config switched to not mention the new protocol + // at all when they save their config as the protocol is now opt-out. Any admins that want to + // disable it can do so and will have their preference recorded. + // 3. We decide that the protocol needs to be retired. It gets switched back to opt-in. At this point + // the initial opt-in admins, assuming they visited an upgrade to a master with step 2, will + // have the protocol disabled for them. This is what we want. If they didn't upgrade to a master + // with step 2, well there is not much we can do to differentiate them from somebody who is upgrading + // from a previous step 3 master and had needed to keep the protocol turned on. + // + // What we should never do is flip-flop: opt-in -> opt-out -> opt-in -> opt-out as that will basically + // clear any preference that an admin has set, but this should be ok as we only ever will be + // adding new protocols and retiring old ones. if (p.isOptIn()) { if (protocols.contains(name)) { enabled.add(name); From 35e0deaed5e8bbe0d1928404db8df347d1b74dfb Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 29 Jul 2016 13:07:53 +0100 Subject: [PATCH 71/89] [JENKINS-37032] More documentation of the logic for the preference recording --- core/src/main/java/jenkins/AgentProtocol.java | 14 ++++++++++++++ core/src/main/java/jenkins/model/Jenkins.java | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java index 0e3374b0e1..a9eca520d7 100644 --- a/core/src/main/java/jenkins/AgentProtocol.java +++ b/core/src/main/java/jenkins/AgentProtocol.java @@ -7,6 +7,8 @@ import hudson.TcpSlaveAgentListener; import java.io.IOException; import java.net.Socket; +import java.util.Set; +import jenkins.model.Jenkins; /** * Pluggable Jenkins TCP agent protocol handler called from {@link TcpSlaveAgentListener}. @@ -23,8 +25,20 @@ import java.net.Socket; public abstract class AgentProtocol implements ExtensionPoint { /** * Allow experimental {@link AgentProtocol} implementations to declare being opt-in. + * Note that {@link Jenkins#setAgentProtocols(Set)} only records the protocols where the admin has made a + * conscious decision thus: + *
    + *
  • if a protocol is opt-in, it records the admin enabling it
  • + *
  • if a protocol is opt-out, it records the admin disabling it
  • + *
+ * Implementations should not transition rapidly from {@code opt-in -> opt-out -> opt-in}. + * Implementations should never flip-flop: {@code opt-in -> opt-out -> opt-in -> opt-out} as that will basically + * clear any preference that an admin has set. This latter restriction should be ok as we only ever will be + * adding new protocols and retiring old ones. + * * @return {@code true} if the protocol requires explicit opt-in. * @since FIXME + * @see Jenkins#setAgentProtocols(Set) */ public boolean isOptIn() { return false; diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 76006d673f..3c95db8cca 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -1128,7 +1128,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve for (AgentProtocol p : AgentProtocol.all()) { String name = p.getName(); if (name != null && !p.isRequired()) { - // we want to record the protocols where the admin has made a concious decision + // we want to record the protocols where the admin has made a conscious decision // thus, if a protocol is opt-in, we record the admin enabling it // if a protocol is opt-out, we record the admin disabling it // We should not transition rapidly from opt-in -> opt-out -> opt-in From 86e14ddffc5e306a62514caca023732f4e62d824 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 29 Jul 2016 13:18:20 +0100 Subject: [PATCH 72/89] Noting merge of JENKINS-37032, also fixing @since FIXME tags --- changelog.html | 5 ++++- core/src/main/java/hudson/PluginManager.java | 2 +- .../main/java/hudson/TcpSlaveAgentListener.java | 8 ++------ .../hudson/model/DescriptorVisibilityFilter.java | 2 +- core/src/main/java/hudson/model/Fingerprint.java | 2 +- core/src/main/java/jenkins/AgentProtocol.java | 6 +++--- core/src/main/java/jenkins/model/Jenkins.java | 15 +++++++++------ .../jenkins/slaves/JnlpSlaveAgentProtocol3.java | 5 +---- .../main/java/jenkins/util/SystemProperties.java | 2 +- 9 files changed, 23 insertions(+), 24 deletions(-) diff --git a/changelog.html b/changelog.html index 802d6d1345..61efd81fec 100644 --- a/changelog.html +++ b/changelog.html @@ -67,7 +67,10 @@ Upcoming changes (issue 36996)
  • If an older version of a detached plugin is already installed it does not get upgraded - (issue 37041) + (issue 37041) +
  • + Allow admins to control the enabled agent protocols on their instance from the global security settings screen. + (issue 37032)
  • diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index de0cb06b2a..92416858e7 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1298,7 +1298,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * the plugin will only take effect after the reboot. * See {@link UpdateCenter#isRestartRequiredForCompletion()} * @return The install job list. - * @since FIXME + * @since 2.0 */ @Restricted(NoExternalUse.class) public List> install(@Nonnull Collection plugins, boolean dynamicLoad) { diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 238dd51296..c01f711406 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -126,7 +126,7 @@ public final class TcpSlaveAgentListener extends Thread { * clients can avoid creating additional work for the server attempting to connect with unsupported protocols. * * @return a comma separated list of the enabled {@link AgentProtocol#getName()} implementations - * @since FIXME + * @since 2.16 */ public String getAgentProtocolNames() { return StringUtils.join(Jenkins.getInstance().getAgentProtocols(), ", "); @@ -262,11 +262,7 @@ public final class TcpSlaveAgentListener extends Thread { } /** - * Allow essential {@link AgentProtocol} implementations (basically {@link PingAgentProtocol}) - * to be always enabled. - * - * @return {@code true} if the protocol can never be disabled. - * @since FIXME + * {@inheritDoc} */ @Override public boolean isRequired() { diff --git a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java index 838e3a0caf..f092a8bb10 100644 --- a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java +++ b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java @@ -35,7 +35,7 @@ public abstract class DescriptorVisibilityFilter implements ExtensionPoint { * @return true to allow the descriptor to be visible. false to hide it. * If any of the installed {@link DescriptorVisibilityFilter} returns false, * the descriptor is not shown. - * @since FIXME + * @since 2.12 */ public boolean filterType(@Nonnull Class contextClass, @Nonnull Descriptor descriptor) { return true; diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index c2ab05a678..2e33bdf2d0 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -1468,7 +1468,7 @@ public class Fingerprint implements ModelObject, Saveable { * Provides the XStream instance this class is using for serialization. * * @return the XStream instance - * @since FIXME + * @since 1.655 */ @Nonnull public static XStream2 getXStream() { diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java index a9eca520d7..93140b71c1 100644 --- a/core/src/main/java/jenkins/AgentProtocol.java +++ b/core/src/main/java/jenkins/AgentProtocol.java @@ -37,7 +37,7 @@ public abstract class AgentProtocol implements ExtensionPoint { * adding new protocols and retiring old ones. * * @return {@code true} if the protocol requires explicit opt-in. - * @since FIXME + * @since 2.16 * @see Jenkins#setAgentProtocols(Set) */ public boolean isOptIn() { @@ -48,7 +48,7 @@ public abstract class AgentProtocol implements ExtensionPoint { * to be always enabled. * * @return {@code true} if the protocol can never be disabled. - * @since FIXME + * @since 2.16 */ public boolean isRequired() { return false; @@ -68,7 +68,7 @@ public abstract class AgentProtocol implements ExtensionPoint { * Returns the human readable protocol display name. * * @return the human readable protocol display name. - * @since FIXME + * @since 2.16 */ public String getDisplayName() { return getName(); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 22492d89eb..83e3184894 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -602,7 +602,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * The TCP agent protocols that are explicitly disabled (we store the disabled ones so that newer protocols * are enabled by default). Will be {@code null} instead of empty to simplify XML format. * - * @since FIXME + * @since 2.16 */ @CheckForNull private List disabledAgentProtocols; @@ -611,15 +611,18 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * The TCP agent protocols that are {@link AgentProtocol#isOptIn()} and explicitly enabled. * Will be {@code null} instead of empty to simplify XML format. * - * @since FIXME + * @since 2.16 */ @CheckForNull private List enabledAgentProtocols; /** - * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols}. + * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols} and + * {@link #enabledAgentProtocols}. * - * @since FIXME + * @since 2.16 + * @see #setAgentProtocols(Set) + * @see #getAgentProtocols() */ private transient Set agentProtocols; @@ -1089,7 +1092,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * Returns the enabled agent protocols. * * @return the enabled agent protocols. - * @since FIXME + * @since 2.16 */ public Set getAgentProtocols() { if (agentProtocols == null) { @@ -1120,7 +1123,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * Sets the enabled agent protocols. * * @param protocols the enabled agent protocols. - * @since FIXME + * @since 2.16 */ public void setAgentProtocols(Set protocols) { Set disabled = new TreeSet<>(); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 5086d9191a..e1618aa719 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -39,10 +39,7 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { NioChannelSelector hub; /** - * Allow experimental {@link AgentProtocol} implementations to declare being opt-in. - * - * @return {@code true} if the protocol requires explicit opt-in. - * @since FIXME + * {@inheritDoc} */ @Override public boolean isOptIn() { diff --git a/core/src/main/java/jenkins/util/SystemProperties.java b/core/src/main/java/jenkins/util/SystemProperties.java index afdda622b4..b90f23781c 100644 --- a/core/src/main/java/jenkins/util/SystemProperties.java +++ b/core/src/main/java/jenkins/util/SystemProperties.java @@ -212,7 +212,7 @@ public class SystemProperties implements ServletContextListener { * * @param name the system property name. * @return {@link Boolean#TRUE}, {@link Boolean#FALSE} or {@code null} - * @since FIXME + * @since 2.16 */ @CheckForNull public static Boolean optBoolean(String name) { From 43ddbdfc1cd651e0430289766637a11717494e88 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Fri, 25 Mar 2016 18:23:25 +0100 Subject: [PATCH 73/89] [JENKINS-21486] Fix plugin dependencies resolution * Check that dependencies are enabled. A disabled optional dependency will not prevent a plugin from loading. * Check versions of dependencies declared by a plugin before loading it. If any dependency (even optional) is older than what is required, then the plugin isn't loaded. This should prevent use cases where a plugin is loaded but one of its dependencies is too old so that : * its @Extension annotated classes cannot be loaded, causing the full Jenkins to blow up with crapload of exceptions which are tedious to investigate to understand the root cause. * NoSuchMethodError and the likes at runtime even though boot has completed. Version check (for setups where version list is manually crafted but yet works) can be disabled by starting Jenkins with -Dhudson.PluginWrapper.dependenciesVersionCheck.enabled=true Minor fixes done while implementing this change : * Fix version parsing in PluginWrapper.Dependency * Dynamic plugin load didn't check for disabled flag --- core/src/main/java/hudson/PluginManager.java | 3 +- core/src/main/java/hudson/PluginWrapper.java | 91 ++++++++++++++++-- .../test/java/hudson/PluginManagerTest.java | 74 ++++++++++++++ .../test/java/hudson/PluginManagerUtil.java | 7 ++ .../test/java/hudson/PluginWrapperTest.java | 26 +++++ .../test/resources/plugins/dependee-0.0.2.hpi | Bin 0 -> 4671 bytes .../test/resources/plugins/depender-0.0.2.hpi | Bin 0 -> 5500 bytes .../plugins/mandatory-depender-0.0.2.hpi | Bin 0 -> 5587 bytes 8 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 test/src/test/java/hudson/PluginWrapperTest.java create mode 100644 test/src/test/resources/plugins/dependee-0.0.2.hpi create mode 100644 test/src/test/resources/plugins/depender-0.0.2.hpi create mode 100644 test/src/test/resources/plugins/mandatory-depender-0.0.2.hpi diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 92416858e7..941dc0cdc2 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -844,7 +844,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas // so existing plugins can't be depending on this newly deployed one. plugins.add(p); - activePlugins.add(p); + if (p.isActive()) + activePlugins.add(p); synchronized (((UberClassLoader) uberClassLoader).loaded) { ((UberClassLoader) uberClassLoader).loaded.clear(); } diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index f61b96e7cf..31feb91c66 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -88,6 +88,12 @@ import javax.annotation.Nonnull; */ @ExportedBean public class PluginWrapper implements Comparable, ModelObject { + /** + * A plugin won't be loaded unless his declared dependencies are present and match the required minimal version. + * This can be set to false to disable the version check (legacy behaviour) + */ + private static final boolean ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK = Boolean.parseBoolean(System.getProperty(PluginWrapper.class.getName()+"." + "dependenciesVersionCheck.enabled", "true")); + /** * {@link PluginManager} to which this belongs to. */ @@ -229,7 +235,7 @@ public class PluginWrapper implements Comparable, ModelObject { @Override public String toString() { - return shortName + " (" + version + ")"; + return shortName + " (" + version + ") " + (optional ? "optional" : ""); } } @@ -394,6 +400,21 @@ public class PluginWrapper implements Comparable, ModelObject { return "???"; } + /** + * Returns the required Jenkins core version of this plugin. + * @return the required Jenkins core version of this plugin. + * @since XXX + */ + @Exported + public @CheckForNull String getRequiredCoreVersion() { + String v = manifest.getMainAttributes().getValue("Jenkins-Version"); + if (v!= null) return v; + + v = manifest.getMainAttributes().getValue("Hudson-Version"); + if (v!= null) return v; + return null; + } + /** * Returns the version number of this plugin */ @@ -524,19 +545,71 @@ public class PluginWrapper implements Comparable, ModelObject { * thrown if one or several mandatory dependencies doesn't exists. */ /*package*/ void resolvePluginDependencies() throws IOException { - List missingDependencies = new ArrayList<>(); + if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK) { + String requiredCoreVersion = getRequiredCoreVersion(); + if (requiredCoreVersion == null) { + LOGGER.warning(shortName + " doesn't declare required core version."); + } else { + if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { + throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); + } + } + } + List missingDependencies = new ArrayList(); + List obsoleteDependencies = new ArrayList(); + List disabledDependencies = new ArrayList(); // make sure dependencies exist for (Dependency d : dependencies) { - if (parent.getPlugin(d.shortName) == null) - missingDependencies.add(d); - } - if (!missingDependencies.isEmpty()) - throw new MissingDependencyException(this.shortName, missingDependencies); + PluginWrapper dependency = parent.getPlugin(d.shortName); + if (dependency == null) { + missingDependencies.add(d.toString()); + } else { + if (dependency.isActive()) { + if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { + obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + } + } else { + disabledDependencies.add(d.toString()); + } + } + } // add the optional dependencies that exists for (Dependency d : optionalDependencies) { - if (parent.getPlugin(d.shortName) != null) - dependencies.add(d); + PluginWrapper dependency = parent.getPlugin(d.shortName); + if (dependency != null && dependency.isActive()) { + if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { + obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + } else { + dependencies.add(d); + } + } + } + StringBuilder messageBuilder = new StringBuilder(); + if (!missingDependencies.isEmpty()) { + boolean plural = missingDependencies.size() > 1; + messageBuilder.append(plural ? "Dependencies " : "Dependency ") + .append(Util.join(missingDependencies, ", ")) + .append(" ").append(plural ? "don't" : "doesn't") + .append(" exist. "); + } + if (!disabledDependencies.isEmpty()) { + boolean plural = disabledDependencies.size() > 1; + messageBuilder.append(plural ? "Dependencies " : "Dependency ") + .append(Util.join(missingDependencies, ", ")) + .append(" ").append(plural ? "are" : "is") + .append(" disabled. "); + } + if (!obsoleteDependencies.isEmpty()) { + boolean plural = obsoleteDependencies.size() > 1; + messageBuilder.append(plural ? "Dependencies " : "Dependency ") + .append(Util.join(obsoleteDependencies, ", ")) + .append(" ").append(plural ? "are" : "is") + .append(" older than required."); + } + String message = messageBuilder.toString(); + if (!message.isEmpty()) { + throw new IOException(message); } } diff --git a/test/src/test/java/hudson/PluginManagerTest.java b/test/src/test/java/hudson/PluginManagerTest.java index 42847950cd..e7bb69de1b 100644 --- a/test/src/test/java/hudson/PluginManagerTest.java +++ b/test/src/test/java/hudson/PluginManagerTest.java @@ -47,6 +47,7 @@ import net.sf.json.JSONObject; import org.apache.commons.io.FileUtils; import org.apache.tools.ant.filters.StringInputStream; import static org.junit.Assert.*; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -339,6 +340,75 @@ public class PluginManagerTest { assertTrue(r.jenkins.getExtensionList("org.jenkinsci.plugins.dependencytest.dependee.DependeeExtensionPoint").isEmpty()); } + @Issue("JENKINS-21486") + @Test public void installPluginWithObsoleteDependencyFails() throws Exception { + // Load dependee 0.0.1. + { + dynamicLoad("dependee.hpi"); + } + + // Load mandatory-depender 0.0.2, depending on dependee 0.0.2 + try { + dynamicLoad("mandatory-depender-0.0.2.hpi"); + fail("Should not have worked"); + } catch (IOException e) { + // Expected + } + } + + @Issue("JENKINS-21486") + @Test public void installPluginWithDisabledOptionalDependencySucceeds() throws Exception { + // Load dependee 0.0.2. + { + dynamicLoadAndDisable("dependee-0.0.2.hpi"); + } + + // Load depender 0.0.2, depending optionally on dependee 0.0.2 + { + dynamicLoad("depender-0.0.2.hpi"); + } + + // dependee is not loaded so we cannot list any extension for it. + try { + r.jenkins.getExtensionList("org.jenkinsci.plugins.dependencytest.dependee.DependeeExtensionPoint"); + fail(); + } catch( ClassNotFoundException _ ){ + } + } + + @Issue("JENKINS-21486") + @Test public void installPluginWithDisabledDependencyFails() throws Exception { + // Load dependee 0.0.2. + { + dynamicLoadAndDisable("dependee-0.0.2.hpi"); + } + + // Load mandatory-depender 0.0.2, depending on dependee 0.0.2 + try { + dynamicLoad("mandatory-depender-0.0.2.hpi"); + fail("Should not have worked"); + } catch (IOException e) { + // Expected + } + } + + + @Issue("JENKINS-21486") + @Test public void installPluginWithObsoleteOptionalDependencyFails() throws Exception { + // Load dependee 0.0.1. + { + dynamicLoad("dependee.hpi"); + } + + // Load depender 0.0.2, depending optionally on dependee 0.0.2 + try { + dynamicLoad("depender-0.0.2.hpi"); + fail("Should not have worked"); + } catch (IOException e) { + // Expected + } + } + @Issue("JENKINS-12753") @WithPlugin("tasks.jpi") @Test public void dynamicLoadRestartRequiredException() throws Exception { @@ -378,6 +448,10 @@ public class PluginManagerTest { PluginManagerUtil.dynamicLoad(plugin, r.jenkins); } + private void dynamicLoadAndDisable(String plugin) throws IOException, InterruptedException, RestartRequiredException { + PluginManagerUtil.dynamicLoad(plugin, r.jenkins, true); + } + @Test public void uploadDependencyResolution() throws Exception { PersistedList sites = r.jenkins.getUpdateCenter().getSites(); sites.clear(); diff --git a/test/src/test/java/hudson/PluginManagerUtil.java b/test/src/test/java/hudson/PluginManagerUtil.java index e67b9b31f9..2263622b4c 100644 --- a/test/src/test/java/hudson/PluginManagerUtil.java +++ b/test/src/test/java/hudson/PluginManagerUtil.java @@ -48,9 +48,16 @@ public class PluginManagerUtil { } public static void dynamicLoad(String plugin, Jenkins jenkins) throws IOException, InterruptedException, RestartRequiredException { + dynamicLoad(plugin, jenkins, false); + } + + public static void dynamicLoad(String plugin, Jenkins jenkins, boolean disable) throws IOException, InterruptedException, RestartRequiredException { URL src = PluginManagerTest.class.getClassLoader().getResource("plugins/" + plugin); File dest = new File(jenkins.getRootDir(), "plugins/" + plugin); FileUtils.copyURLToFile(src, dest); + if (disable) { + new File(dest.getPath() + ".disabled").createNewFile(); + } jenkins.pluginManager.dynamicLoad(dest); } } diff --git a/test/src/test/java/hudson/PluginWrapperTest.java b/test/src/test/java/hudson/PluginWrapperTest.java new file mode 100644 index 0000000000..7529c53194 --- /dev/null +++ b/test/src/test/java/hudson/PluginWrapperTest.java @@ -0,0 +1,26 @@ +package hudson; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PluginWrapperTest { + + @Test + public void dependencyTest() { + String version = "plugin:0.0.2"; + PluginWrapper.Dependency dependency = new PluginWrapper.Dependency(version); + assertEquals("plugin", dependency.shortName); + assertEquals("0.0.2", dependency.version); + assertEquals(false, dependency.optional); + } + + @Test + public void optionalDependencyTest() { + String version = "plugin:0.0.2;resolution:=optional"; + PluginWrapper.Dependency dependency = new PluginWrapper.Dependency(version); + assertEquals("plugin", dependency.shortName); + assertEquals("0.0.2", dependency.version); + assertEquals(true, dependency.optional); + } +} diff --git a/test/src/test/resources/plugins/dependee-0.0.2.hpi b/test/src/test/resources/plugins/dependee-0.0.2.hpi new file mode 100644 index 0000000000000000000000000000000000000000..79525b08464fdfe9ef41c5e7f18b4ab3afb78705 GIT binary patch literal 4671 zcmb_g3p|r;8z0NLL|*P$B8u($U$DORKtrj&}_`kn{d*p|0nefRJ89QNCF{jdA}U)O!z{|)Z~pQZ|f zMw-jMwWBZNHx=;d>fmLIb#`+!$B&yS!x*4hg1OJ|2>5k{91Mn-Xy$6`=IrR;>4kH3 z+?VXNlkU&vikJ@dUElgUq5fd>iP)zc>j;#=4}RMe(?4})Jk*%X&x$y zQlWUVvu=Pzy!&OLdu8MEv`76KpLrZTZ?#lpB7X)9U9xn=&chk1?2m0{N6f#bV;oFz zCbwMlohlcl#(N#vFp%_qUaOCNMo9p|lhL|&F8O?Sk-AC~26f;vhwl=e`bT0Z!@+&i zg~76BCYA(uWZfYS4qbV3f57H+|Hc?&99wVGhT`)v!MEtBt%++)km$4+IU!lFQL@JA z9DOK*cV7p)Nt8-kX-u#RA_Y*X09BL#X=|hmYpJ|RyFqyqPDL1m624=KK&Wqc1Owy{ zBAFUQoWj@mZ+*i^5j#MXDKM6NHwFO+1IPq`9289hsI(~{ZGIGTJOUuX9@`kG@TYnh z5OXnj>B_(+g0qUk1p_2f^fl*t%(X7n`Z|n{Nu8-{(|u3aRSyp}k3kO)PeiJjPFil3 z0eTfqci)v@n6RM6f`GU%dFTcX9C%4)QIvcjDvUw}2skP&AdCcrV*`NzG8Ha?b|J#} ziUzokFM-#38E8;Vh%+qAk_5vdI_$@o2XLb$m5ncRt6i;;XAIx?;`QAJ@u%8z9{F6lYyE&Gx)JFb})Bcg4HkP zS~=_fDx@JzD>=o73{VZM2c-{MuBGI^%PVFvhl_a1IV#x$>85FJ zg|&Xqyfy1}z0I>|=hzX?=fHX0El8`ZF9MB(HbGCBMS4oL-0(Z^_pj-H98*4=x6INI zHMfFZu@pGmOimq{qj@6sAN;^$QTBVd>VhRYxq|oG5_xA@*3|UfB@fS-oJW>To;TD* zvq0uI|KFH*h@t^xDv?6QQ;1~RSRO9oE8J===B&I)l-Fy2q0FY8x7D-Nh>?&MH}Vb5H0VGBbp}iBz(676Em=$P=3Z zFsv9zY(0U%-QmOyTUwy*~Dx z&KwpRC|ZxBq#p3NF*M87XNzI1Q^lHhANQ9p)AJnYGMxkWx%zt*zgy>Nyz@-mho{=d|36$qka2i%ForG%ghI<-ZXx23AYZ37zC0AP;pVABvx{%`az57_K}q} zTMCQ|4dsXSE6%|EYUGAET>N<-(@v+aqw(Jnk%Fyd#q7jKhFk3D=OZ!cA!&xkFea)A zh@PmIx;orkL7jGK7Ut!Po`8Apoqi*vUC4DgMb2(KP56y@K*^X{m1GuC)KrXxy;?IQ z|0sH;>K83F>gKYfMRJ7;Yl8Lk^r z5H3sl*q#Y4Pt*ZI19k&sQIC=~hFYqGF}UBs1~uBzu|Y)K_=d+xZ1``-hg#|nL5g~a z5L+2GK!{oU;kxPXN5OP`g2P}AKaCwm2_4O`X9Kt)G|E3LT;FtCJ^#E}_lXCp)3lx@ z9Ir4UYqwv`F8FKjQa8{1nbT&IJKpqi?yso7U=^Z4?|rpyG5WDjpb~YN{ zr8@NadLZ>bT-rmc+18g&^xg<1?$wLFwzwilQ1Wcy(mz+aKUBJ;;Gf~ErdOS>xzgm^ z*ZGe71MTOxaem>ROzSVt>e05l`fzPwM^$uI4qFasUA-dpnL6#oiH!)(;vx5*WsFO< zlxB(ng5~YWw+yP?*VKb6G{?!yYhiY(Z0?1BOfGHlZhDL|YMGV|kgUV26xA5p=QUHa zQ4~gTH-|(o*M-w`5<^fhtba!5|r$)a7q{uP9p+TY(uGPjRnH-$*K*S zFGz`+d03j3rqY5DdCO1OmT+xys$FNtbh6%Dw@5fWl%8?f&!$&V>+#l>I_`9jnde@z zJmy#P3W}DB#CEYWmudM4+xTV~HF2A@i z^yBPAJ-myYJOci0jSVgn92&4bTj(iTvDi(v=7x-+kw;$u-bHQ`6u1~cTKz#nbZkI@ zy}mp zgq|&oC0hs!qvg?8gMD0*?g_G3%!_QXD+bD!0VFCQnbKDhTT=Rpu?J;fPp%1J9wj&z z5n?|{2untmlz!sKp$gLU{8(gJLQrxZl6puy55A&!9uw7(7(rT1K+O;Z(uB4o z6e7YZk#?S3^xe|VAPe6mOuijra?$@2?17?s8Jpktg%IUf%!O>JB4i_v&iLtAAAD`#_JfG+Cd7k$iY~i!TVbD5f8)5b3 zpRXTc;J4jIXLAEi;8vJ^%+4HRZMo6O8EI!3k^H-D z4N4(?7(OmN4!1Ll9Dv8oq#4Qx_HEd9;)#AU*t+0561xCgU;vKf6EK79lK+$?V1xZYlo>Eq z{4jOruYe#Q#syEbWXrkLTHRL#3SsPg0v(?>NPj_;N$#-%(K2nMusY>M} z=ajafNeWH|EN%eYf}F!ir0E&qAM8uQSi zI80hbjAnn+F4ZB+sQhB>k+mzCn)nVW!bC^+@^yROwW4j#Orrr6;|BxJYt`1EQ>tEU9@G_%ujrdNb{unkO^?IP$f2n2_K%LvRwakg z*J9h+3SU}8OXZj~s+WdOs#cVGtk|DQjVbJ%SCkg#;(J8#Pu2%k?^ymv#=%8<%2r_Z zG;|ofo|GUKJpIkJGs4uyh@AIaSpP%ne1qDu;(1yXYGc`>{TbJlH01~MM|PcTe>0a| z|5GjoV+a$1KEQ->6)R@Uc*rLxWlTNeNp{Y@x2n1TFB`VoWX<>A$ zX>%hM71#?ubUQ{^2BC#ek2@7Se~TI1z3KI>2&4BqZ>VQn$HP_@cwb1^BJZPY=!0fi z%R8My`qWikjb0t!n(n;(p`T9spk#cHIYVyF{zT^<Au?);WnRyK*Gm(^k z%A?_w*Bvp9jFPyUCzdibZ%aC6;BIN*;&zGkJ<(o_x#>ldet#Fa;o)5C8cD~cE8VV` z28}lNMPJz)Nwshsr=bk1jo;Cz?UJnW9M#l-^FnQvx8e}`j(NAD^Ydc?xQoOF<nK&X0|M)t)Xk8oCDV% zxNf>FKu=0>I5}kU@(%I`iAM*_*PCZ7Kvk?Lj8NG9E@AKRpb^7|iKQ9(&4mqA zx3criq0jnyRn_WUNpjE94vB7Ee7CvzR`{HvVRbO#QUxt$DuJ_1W_2j;x^W7R*;Ycu2%BVi&<`lCUy^-)=rLIVGv}Fn`N3+}6iRGd z!D*F5$xZf8SKUqyV4OMneiNQS?bC>>qugt)_i#ur9}j%)c|rX~bw_Yr`lX#_iJ9gn zAMP2d4Gpi^tz>$)o6fkI*7p~t@m=UYXoO2#+rAE$feEcDeYvL4>f>5WhNW1zVZf42 zxndm_adE=AEJFu6+D3GCU@bu;xT@oU(24sxpLRwY=-+*;fZvE?NnOeKHX8O1Bs{Xf zD7b>rnBMuY0Y2a}2#}CrLD=Ht0;&C|IToRRSG!+Ya#=%&87VA@BZw_Id^Gn{#Cf#h z(9?S-ffS`yx&Aw8k|GVHGmCnU`Tm($khkMuoZ&42wUCEPJPI2}@}ka{73zwMdUS zo|t^K1=0Sj%H(zW0b!N$$~`IFO2>wiwjbMC^1_ztWB*ZT@L;6;qMV$^a!D$x!y0GY z4fhedWpyf}mYN;fCf)3Z-K|)bZ7yeb|4c%fk`%+=Yjm3kIj;zH!|Tp9O4p5^`U0oZ z+bo%lJ}NYK_x=c)lo2xP`Xj%k0}RzWQNIzJBq27eF0@t708ouoJm1 zV4KaOVp{)T>k`by*xE9qQw8Xn#?82X5lpnWK%!uC;*Gq(Wa6b=yec3_(8pgEL^Rkd zGdd74g9?GPgBP2Zc5t!F1YrLP3E|buI|OqzXNmCN>gP1`jKsVq@J@bQ6O6#$&e*Y= zJ_m9U_=g;5$eLQvD<}@W(VS?rM=o{_@(X`kQor#F|L6$zeHGu9)Zw31A+V$b4P|U^ zWJbUEWnEuKG_L!nijTL$b5+ifX63z0R^kSw9)Q2IlWwX(0F z6o1QoyKZI`n+^zEG`q^OCL$+h~w zw`jJxN#(f;#JBtKhYb}*9m=F&^xAyaMO~V^m4+9vvSjJG#wRM%@znO^SoX2?dlpi6 zIgSMlImRt`YUd64#q=XJ!mN0WpUUNJ`a~h|vSe*T+n5zS;^B$PnS6ff^TlafE#cdh zmxf@Ue*17P^?vLA;}Q7@s45jHO1UUpybq7$^z)=5T z3|Fidmf(sdxCHuONj_^p8vR>HPYl5o<3scg{7U6bBU#Qn8JE~71pr(Hctn_x-~jak zU`Mccd|-)5n_|fzSKB>B8@YS(W)JV9jZKt{AE4xI?ma|XL=>UX=VmJLGxZ8A)X-d} zuzS>2H4Z@U0n(e9X=q`zG9Em9T}ea&3=!aiC6Is!G25qU&bE_3TUVFA3nI8%_mBGE z6UN3IQkHm+O!CvJJBDQNfu{4f%4$oNG=Q?@FpO$;jr4cM#@|iyp&q zLXF0goNcP=L{6Jh94~vyb7m3#D%@hPxV=lXv6NfF>y1m{F9Yt4|4~(-l#eHM3&pA* zD@*-yXs>I+z+g~+&KrX#7?tkZ) zlc`gqyyQeVI&`n7ar@ag46to8Wu6Mu;)KahRNRGJPS*EGy=)iS>Lu?v-R$dI+2sevYOfhQryC8an zV0SfUvje+0{CI==b!&CCY5rrq~)H`>jYab@=x#7SCT zbY}8s?J+aC9Xre}Bg@SeeB1jZ9xLCyug&&tOv#3)b#DTqU>bN1|NJPRMF4x#yu#Z^wIeFZ;Pc_j}A99MQ<+m9O62h_lcL z5y>2_%gvhJUof{*Gb+Ev%qU2Is&2sf=``*)uCwMI=E>wH)V|KKjHIT3Oc)COq}qE= z*o<^yp{V?>34CgcPyHNQPpv3Bv_yqVvmztlLe~kg7==8kry}AXVK+581>F&gP30B_ zhh9(B1}V&Z5whO@$@J1hq)5j=7eeMZGSsT_vP&J(=}y0H$l}(w@;sXiSwx~J&8Brv z^H}mdHqV!_+S@9Jh}5xv>kw1fz}6dpBm4n}tSotWoC|ORVo9(7Pkb3!FR?*|2Qiz~ zaJRttk>ZXQ@K%mE9B&-7dE6stUY;XCBUVY%erhr3Ve4Z=!99V!{9Fq|P2E>|rMd1! zI6L97H}WW%-N;}>jX;Msxe&MQS=X08g%Or)HS&j!KMIx(PCSgV|M)Q9tzRV5;&rd+ zyZX=!47=A$3&lF^!TFIGOD979T&~>}5ihg)Q~IwUY1f_-lX8j%+StT_PE-xx!S;0!$iAP*;zjga3V{6`gdP?Cs`(wZIjyIx z{+q>PJN+Q`zVx<%URSry%5*M*7o}2-VpQAt`5%{N$LEA$hVwXt)qcD5-#qxMz>yu z^Su&?Z^&o~J-fFv_vWS9k=+8PUR;;|kX}3*>O>NLtPEpi6;$-#w98`sn_N6{q<7r;wWPp#i7u6}LW(+-tR)T3PjurM?P2Asd> zSULZQMeR@5!UQV_9rMi(W3H+v-!}m`pV28a*q9rUZUU;>3>38bvZB0DNtw@URNCC| zi%dvYOmB1pF`vrlK&(LRVhjOVaITCh#u<%~0)zyNy|lo&g}QRX4c4zg!E}mY#Acjg z=-5e2uz$)-!>9sd3r$xcjeYg;{!bMc8fr#e7`sQhE^@#>-KgGN*-O$9R<~SWf4029 zgysOQZ<_z9-5ND2R||ieZR)Fqf3$o1J_Y^u0riXhw-mH&TiMM~Sverg`Z=mya>K`e xBJ0=gj_#7>6lLrX=;T{@SGnZJtjgrbAUVBXut2Tf0P<`Geu{xG=HUbX`X569LH_^% literal 0 HcmV?d00001 From 646969b690797bb289a558104109ec1fc92b38cf Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 11:09:04 +0200 Subject: [PATCH 74/89] [JENKINS-21486] Refactoring --- core/src/main/java/hudson/PluginWrapper.java | 33 ++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 31feb91c66..b81dbd92a5 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -28,7 +28,6 @@ import com.google.common.collect.ImmutableSet; import hudson.PluginManager.PluginInstanceStore; import hudson.model.Api; import hudson.model.ModelObject; -import jenkins.MissingDependencyException; import jenkins.YesNoMaybe; import jenkins.model.Jenkins; import hudson.model.UpdateCenter; @@ -550,26 +549,24 @@ public class PluginWrapper implements Comparable, ModelObject { if (requiredCoreVersion == null) { LOGGER.warning(shortName + " doesn't declare required core version."); } else { - if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { - throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); - } + checkRequiredCoreVersion(requiredCoreVersion); } } - List missingDependencies = new ArrayList(); - List obsoleteDependencies = new ArrayList(); - List disabledDependencies = new ArrayList(); + List missingDependencies = new ArrayList<>(); + List obsoleteDependencies = new ArrayList<>(); + List disabledDependencies = new ArrayList<>(); // make sure dependencies exist for (Dependency d : dependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { - missingDependencies.add(d.toString()); + missingDependencies.add(d); } else { if (dependency.isActive()) { - if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { - obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + if (isDependencyObsolete(d, dependency)) { + obsoleteDependencies.add(d); } } else { - disabledDependencies.add(d.toString()); + disabledDependencies.add(d); } } @@ -578,8 +575,8 @@ public class PluginWrapper implements Comparable, ModelObject { for (Dependency d : optionalDependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency != null && dependency.isActive()) { - if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { - obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + if (isDependencyObsolete(d, dependency)) { + obsoleteDependencies.add(d); } else { dependencies.add(d); } @@ -613,6 +610,16 @@ public class PluginWrapper implements Comparable, ModelObject { } } + private void checkRequiredCoreVersion(String requiredCoreVersion) throws IOException { + if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { + throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); + } + } + + private boolean isDependencyObsolete(Dependency d, PluginWrapper dependency) { + return ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version)); + } + /** * If the plugin has {@link #getUpdateInfo() an update}, * returns the {@link hudson.model.UpdateSite.Plugin} object. From aeb6ad951ee0305e390752eff4a36609b0dcee29 Mon Sep 17 00:00:00 2001 From: Felix Belzunce Arcos Date: Wed, 6 Apr 2016 21:39:01 +0200 Subject: [PATCH 75/89] [JENKINS-21485] AdministrativeMonitor for PluginWrapper --- core/src/main/java/hudson/PluginWrapper.java | 40 +++++++++++++++++++ .../main/resources/hudson/Messages.properties | 4 ++ .../message.jelly | 15 +++++++ 3 files changed, 59 insertions(+) create mode 100644 core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index b81dbd92a5..dfbc1b1806 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -26,6 +26,7 @@ package hudson; import com.google.common.collect.ImmutableSet; import hudson.PluginManager.PluginInstanceStore; +import hudson.model.AdministrativeMonitor; import hudson.model.Api; import hudson.model.ModelObject; import jenkins.YesNoMaybe; @@ -53,6 +54,8 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -560,13 +563,16 @@ public class PluginWrapper implements Comparable, ModelObject { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { missingDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_MissingDependency(getLongName(), d.shortName)); } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { obsoleteDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); } } else { disabledDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_DisabledDependency(getLongName(), dependency.getLongName())); } } @@ -577,6 +583,7 @@ public class PluginWrapper implements Comparable, ModelObject { if (dependency != null && dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { obsoleteDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); } else { dependencies.add(d); } @@ -612,6 +619,7 @@ public class PluginWrapper implements Comparable, ModelObject { private void checkRequiredCoreVersion(String requiredCoreVersion) throws IOException { if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_OutdatedCoreVersion(getLongName(), requiredCoreVersion)); throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); } } @@ -725,6 +733,38 @@ public class PluginWrapper implements Comparable, ModelObject { return false; } + @Extension + public final static PluginWrapperAdministrativeMonitor NOTICE = new PluginWrapperAdministrativeMonitor(); + + /** + * Administrative Monitor for failed plugins + */ + public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor { + public final List pluginError = new ArrayList<>(); + + void addErrorMessage(String error) { + pluginError.add(error); + } + + public boolean isActivated() { + return !pluginError.isEmpty(); + } + + /** + * Depending on whether the user said "dismiss" or "correct", send him to the right place. + */ + public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException { + if(req.hasParameter("correct")) { + rsp.sendRedirect(req.getContextPath()+"/pluginManager"); + + } + } + + public static PluginWrapperAdministrativeMonitor get() { + return AdministrativeMonitor.all().get(PluginWrapperAdministrativeMonitor.class); + } + } + // // // Action methods diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 038ecc0cd6..8bdde0139c 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -73,4 +73,8 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details +PluginWrapper.admonitor.OutdatedCoreVersion=Plugin {0} requires Jenkins {1} or later +PluginWrapper.admonitor.MissingDependency=Plugin {0} requires the missing plugin {1} +PluginWrapper.admonitor.DisabledDependency=Plugin {0} depends on the disabled {1} +PluginWrapper.admonitor.ObsoleteDependency=Plugin {0} requires {1} {2} or later TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly new file mode 100644 index 0000000000..91be4632b2 --- /dev/null +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -0,0 +1,15 @@ + + +
    + +
    + +
    + +
      + +
    • ${pluginError}
    • +
      +
    +
    +
    \ No newline at end of file From b8f26b34a29ab3e8d80e8c7be4df2232cee0169e Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 15:50:37 +0200 Subject: [PATCH 76/89] [JENKINS-21486] Simplify dependency errors model and serve consistent messages between console and administrative monitor --- core/src/main/java/hudson/PluginWrapper.java | 132 +++++++++--------- .../main/resources/hudson/Messages.properties | 9 +- .../message.jelly | 13 +- 3 files changed, 78 insertions(+), 76 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index dfbc1b1806..45b28f7614 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -34,24 +34,7 @@ import jenkins.model.Jenkins; import hudson.model.UpdateCenter; import hudson.model.UpdateSite; import hudson.util.VersionNumber; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.Closeable; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.jar.Manifest; -import java.util.logging.Logger; -import static java.util.logging.Level.WARNING; -import static org.apache.commons.io.FilenameUtils.getBaseName; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.LogFactory; +import org.jvnet.localizer.ResourceBundleHolder; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.StaplerRequest; @@ -60,11 +43,32 @@ import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; -import java.util.Enumeration; -import java.util.jar.JarFile; -import java.util.logging.Level; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.LogFactory; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +import static org.apache.commons.io.FilenameUtils.getBaseName; /** * Represents a Jenkins plug-in and associated control information @@ -150,6 +154,12 @@ public class PluginWrapper implements Comparable, ModelObject { private final List dependencies; private final List optionalDependencies; + public List getDependencyErrors() { + return Collections.unmodifiableList(dependencyErrors); + } + + private final transient List dependencyErrors = new ArrayList<>(); + /** * Is this plugin bundled in jenkins.war? */ @@ -552,27 +562,28 @@ public class PluginWrapper implements Comparable, ModelObject { if (requiredCoreVersion == null) { LOGGER.warning(shortName + " doesn't declare required core version."); } else { - checkRequiredCoreVersion(requiredCoreVersion); + VersionNumber actualVersion = Jenkins.getVersion(); + if (actualVersion.isOlderThan(new VersionNumber(requiredCoreVersion))) { + dependencyErrors.add(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion)); + } } } - List missingDependencies = new ArrayList<>(); - List obsoleteDependencies = new ArrayList<>(); - List disabledDependencies = new ArrayList<>(); // make sure dependencies exist for (Dependency d : dependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { - missingDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_MissingDependency(getLongName(), d.shortName)); + dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { - obsoleteDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); + dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version)); } } else { - disabledDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_DisabledDependency(getLongName(), dependency.getLongName())); + if (isDependencyObsolete(d, dependency)) { + dependencyErrors.add(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version)); + } else { + dependencyErrors.add(Messages.PluginWrapper_disabled(dependency.getLongName())); + } } } @@ -582,45 +593,24 @@ public class PluginWrapper implements Comparable, ModelObject { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency != null && dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { - obsoleteDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); + dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version)); } else { dependencies.add(d); } } } - StringBuilder messageBuilder = new StringBuilder(); - if (!missingDependencies.isEmpty()) { - boolean plural = missingDependencies.size() > 1; - messageBuilder.append(plural ? "Dependencies " : "Dependency ") - .append(Util.join(missingDependencies, ", ")) - .append(" ").append(plural ? "don't" : "doesn't") - .append(" exist. "); - } - if (!disabledDependencies.isEmpty()) { - boolean plural = disabledDependencies.size() > 1; - messageBuilder.append(plural ? "Dependencies " : "Dependency ") - .append(Util.join(missingDependencies, ", ")) - .append(" ").append(plural ? "are" : "is") - .append(" disabled. "); - } - if (!obsoleteDependencies.isEmpty()) { - boolean plural = obsoleteDependencies.size() > 1; - messageBuilder.append(plural ? "Dependencies " : "Dependency ") - .append(Util.join(obsoleteDependencies, ", ")) - .append(" ").append(plural ? "are" : "is") - .append(" older than required."); - } - String message = messageBuilder.toString(); - if (!message.isEmpty()) { - throw new IOException(message); - } - } - - private void checkRequiredCoreVersion(String requiredCoreVersion) throws IOException { - if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_OutdatedCoreVersion(getLongName(), requiredCoreVersion)); - throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); + if (!dependencyErrors.isEmpty()) { + NOTICE.addPlugin(this); + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.append("Failed to load ").append(getLongName()).append(System.lineSeparator()); + for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) { + String dependencyError = iterator.next(); + messageBuilder.append(" - ").append(dependencyError); + if (iterator.hasNext()) { + messageBuilder.append(System.lineSeparator()); + } + } + throw new IOException(messageBuilder.toString()); } } @@ -740,14 +730,18 @@ public class PluginWrapper implements Comparable, ModelObject { * Administrative Monitor for failed plugins */ public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor { - public final List pluginError = new ArrayList<>(); + private final Set plugins = new HashSet<>(); - void addErrorMessage(String error) { - pluginError.add(error); + void addPlugin(PluginWrapper plugin) { + plugins.add(plugin); } public boolean isActivated() { - return !pluginError.isEmpty(); + return !plugins.isEmpty(); + } + + public Collection getPlugins() { + return plugins; } /** diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 8bdde0139c..afbd50ab0a 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -73,8 +73,9 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details -PluginWrapper.admonitor.OutdatedCoreVersion=Plugin {0} requires Jenkins {1} or later -PluginWrapper.admonitor.MissingDependency=Plugin {0} requires the missing plugin {1} -PluginWrapper.admonitor.DisabledDependency=Plugin {0} depends on the disabled {1} -PluginWrapper.admonitor.ObsoleteDependency=Plugin {0} requires {1} {2} or later +PluginWrapper.missing=Plugin "{0}" ({1}) is missing. To fix, install version {1} or later. +PluginWrapper.disabledAndObsolete=Plugin "{0}" ({1}) is disabled and older than required. To fix, install version {2} or later and enable it. +PluginWrapper.disabled=Plugin "{0}" is disabled. To fix, enable it. +PluginWrapper.obsolete=Plugin "{0}" ({1}) is older than required. To fix, install version {2} or later. +PluginWrapper.obsoleteCore=You must update Jenkins from {0} to version {1} or later to run this plugin. TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly index 91be4632b2..d5855735cf 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -6,10 +6,17 @@ + There are dependency errors loading some plugins:
      - -
    • ${pluginError}
    • + +
    • ${plugin.longName} +
        + +
      • ${d}
      • +
        +
      +
    - \ No newline at end of file + From 99fa5f7a49ae06063c5306de877e1d5af3a56464 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 19:07:44 +0200 Subject: [PATCH 77/89] [JENKINS-21486] Fix :ant from daniel and separate failed plugin from missing --- core/src/main/java/hudson/PluginWrapper.java | 22 ++++++++++++++----- .../main/resources/hudson/Messages.properties | 1 + 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 45b28f7614..7b1e938daa 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -58,9 +58,11 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -247,7 +249,7 @@ public class PluginWrapper implements Comparable, ModelObject { @Override public String toString() { - return shortName + " (" + version + ") " + (optional ? "optional" : ""); + return shortName + " (" + version + ")" + (optional ? " optional" : ""); } } @@ -572,7 +574,13 @@ public class PluginWrapper implements Comparable, ModelObject { for (Dependency d : dependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { - dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); + PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName); + if (failedDependency != null) { + dependencyErrors.add(Messages.PluginWrapper_failed_to_load(failedDependency.getLongName(), d.version)); + break; + } else { + dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); + } } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { @@ -730,10 +738,10 @@ public class PluginWrapper implements Comparable, ModelObject { * Administrative Monitor for failed plugins */ public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor { - private final Set plugins = new HashSet<>(); + private final Map plugins = new HashMap<>(); void addPlugin(PluginWrapper plugin) { - plugins.add(plugin); + plugins.put(plugin.shortName, plugin); } public boolean isActivated() { @@ -741,7 +749,11 @@ public class PluginWrapper implements Comparable, ModelObject { } public Collection getPlugins() { - return plugins; + return plugins.values(); + } + + public PluginWrapper getPlugin(String shortName) { + return plugins.get(shortName); } /** diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index afbd50ab0a..4484823892 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -74,6 +74,7 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details PluginWrapper.missing=Plugin "{0}" ({1}) is missing. To fix, install version {1} or later. +PluginWrapper.failed_to_load=Plugin "{0}" ({1}) failed to load. Fix this plugin first. PluginWrapper.disabledAndObsolete=Plugin "{0}" ({1}) is disabled and older than required. To fix, install version {2} or later and enable it. PluginWrapper.disabled=Plugin "{0}" is disabled. To fix, enable it. PluginWrapper.obsolete=Plugin "{0}" ({1}) is older than required. To fix, install version {2} or later. From 6bf02d514d80e806f108290fbea06ac6f6064798 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 19:19:03 +0200 Subject: [PATCH 78/89] [JENKINS-21486] Fix up messages and show plugin version --- core/src/main/java/hudson/PluginWrapper.java | 4 ++-- core/src/main/resources/hudson/Messages.properties | 13 +++++++------ .../message.jelly | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 7b1e938daa..b97e977ede 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -576,7 +576,7 @@ public class PluginWrapper implements Comparable, ModelObject { if (dependency == null) { PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName); if (failedDependency != null) { - dependencyErrors.add(Messages.PluginWrapper_failed_to_load(failedDependency.getLongName(), d.version)); + dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), d.version)); break; } else { dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); @@ -610,7 +610,7 @@ public class PluginWrapper implements Comparable, ModelObject { if (!dependencyErrors.isEmpty()) { NOTICE.addPlugin(this); StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.append("Failed to load ").append(getLongName()).append(System.lineSeparator()); + messageBuilder.append(Messages.PluginWrapper_failed_to_load_plugin(getLongName(), getVersion())).append(System.lineSeparator()); for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) { String dependencyError = iterator.next(); messageBuilder.append(" - ").append(dependencyError); diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 4484823892..ec7f6002b4 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -73,10 +73,11 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details -PluginWrapper.missing=Plugin "{0}" ({1}) is missing. To fix, install version {1} or later. -PluginWrapper.failed_to_load=Plugin "{0}" ({1}) failed to load. Fix this plugin first. -PluginWrapper.disabledAndObsolete=Plugin "{0}" ({1}) is disabled and older than required. To fix, install version {2} or later and enable it. -PluginWrapper.disabled=Plugin "{0}" is disabled. To fix, enable it. -PluginWrapper.obsolete=Plugin "{0}" ({1}) is older than required. To fix, install version {2} or later. -PluginWrapper.obsoleteCore=You must update Jenkins from {0} to version {1} or later to run this plugin. +PluginWrapper.missing={0} v{1} is missing. To fix, install v{1} or later. +PluginWrapper.failed_to_load_plugin={0} v{1} failed to load. +PluginWrapper.failed_to_load_dependency={0} v{1} failed to load. Fix this plugin first. +PluginWrapper.disabledAndObsolete={0} v{1} is disabled and older than required. To fix, install v{2} or later and enable it. +PluginWrapper.disabled={0} is disabled. To fix, enable it. +PluginWrapper.obsolete={0} v{1} is older than required. To fix, install v{2} or later. +PluginWrapper.obsoleteCore=You must update Jenkins from v{0} to v{1} or later to run this plugin. TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly index d5855735cf..54fe072660 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -9,7 +9,7 @@ There are dependency errors loading some plugins:
      -
    • ${plugin.longName} +
    • ${plugin.longName} v${plugin.version}
      • ${d}
      • From d8f3bfc8ed0f88e5bff757c0eec921d6c135d5b4 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 21:30:31 +0200 Subject: [PATCH 79/89] [JENKINS-21486] Cycle monitor should display plugin long name and version --- core/src/main/java/hudson/PluginManager.java | 8 ++++---- .../PluginCycleDependenciesMonitor/message.jelly | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 941dc0cdc2..b25bbb63ae 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1868,14 +1868,14 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas private transient volatile boolean isActive = false; - private transient volatile List pluginsWithCycle; + private transient volatile List pluginsWithCycle; public boolean isActivated() { if(pluginsWithCycle == null){ - pluginsWithCycle = new ArrayList(); + pluginsWithCycle = new ArrayList<>(); for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) { if(p.hasCycleDependency()){ - pluginsWithCycle.add(p.getShortName()); + pluginsWithCycle.add(p); isActive = true; } } @@ -1883,7 +1883,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas return isActive; } - public List getPluginsWithCycle() { + public List getPluginsWithCycle() { return pluginsWithCycle; } } diff --git a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly index 3a7110fdca..1c80b2d66a 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly @@ -28,7 +28,7 @@ THE SOFTWARE. ${%PluginCycles}
          -
        • +
        From 8e3c9dadc1b09406ab6e77af33518ee3168470d4 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 21:35:54 +0200 Subject: [PATCH 80/89] [JENKINS-21486] Mention both long name, short name and version in console --- core/src/main/java/hudson/PluginManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index b25bbb63ae..910d560a8b 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -505,7 +505,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas // schedule execution of loading plugins for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) { - g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() { + g.followedBy().notFatal().attains(PLUGINS_PREPARED).add(String.format("Loading plugin %s v%s (%s)", p.getLongName(), p.getVersion(), p.getShortName()), new Executable() { public void run(Reactor session) throws Exception { try { p.resolvePluginDependencies(); From dff8e806b9466652a351dd7fb7f742b6698140db Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Fri, 29 Jul 2016 07:04:35 +0200 Subject: [PATCH 81/89] [JENKINS-21486] Only consider plugins detached before the current version --- test/src/test/java/hudson/model/UsageStatisticsTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/model/UsageStatisticsTest.java b/test/src/test/java/hudson/model/UsageStatisticsTest.java index 63b6a14279..a02ee18d78 100644 --- a/test/src/test/java/hudson/model/UsageStatisticsTest.java +++ b/test/src/test/java/hudson/model/UsageStatisticsTest.java @@ -105,7 +105,9 @@ public class UsageStatisticsTest { List plugins = sortPlugins((List) o.get("plugins")); Set detached = new TreeSet<>(); for (ClassicPluginStrategy.DetachedPlugin p: ClassicPluginStrategy.getDetachedPlugins()) { - detached.add(p.getShortName()); + if (p.getSplitWhen().isOlderThan(Jenkins.getVersion())) { + detached.add(p.getShortName()); + } } Set keys = new TreeSet<>(); keys.add("name"); From 972437dd06ecd4b5e3d2ca751aa4da77187dcf33 Mon Sep 17 00:00:00 2001 From: Jakub Cechacek Date: Sat, 30 Jul 2016 14:55:44 +0200 Subject: [PATCH 82/89] Display delete button only when build is not locked (#2483) --- .../hudson/model/Run/confirmDelete.jelly | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/resources/hudson/model/Run/confirmDelete.jelly b/core/src/main/resources/hudson/model/Run/confirmDelete.jelly index 41c42b3eb0..c48788d423 100644 --- a/core/src/main/resources/hudson/model/Run/confirmDelete.jelly +++ b/core/src/main/resources/hudson/model/Run/confirmDelete.jelly @@ -24,20 +24,23 @@ THE SOFTWARE. - + - - + + -

        ${%Warning}: ${msg}

        -
        +

        ${%Warning}: ${msg}

        + -
        - ${%Are you sure about deleting the build?} - - + +
        + ${%Are you sure about deleting the build?} + + +
        -
        -
        + +
        From e02419c61a562d991d8a068a34f357b2342747ca Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 31 Jul 2016 17:23:11 -0700 Subject: [PATCH 83/89] [maven-release-plugin] prepare release jenkins-2.16 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 36c2b0ec06..ce3c6ff623 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.16-SNAPSHOT + 2.16 cli diff --git a/core/pom.xml b/core/pom.xml index c2539d94b8..db3f62feb8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16-SNAPSHOT + 2.16 jenkins-core diff --git a/pom.xml b/pom.xml index f5eee45f9f..fe8173f186 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16-SNAPSHOT + 2.16 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.16 diff --git a/test/pom.xml b/test/pom.xml index 9693291958..2216d2826b 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16-SNAPSHOT + 2.16 test diff --git a/war/pom.xml b/war/pom.xml index 3579e55dc9..de34f1b087 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16-SNAPSHOT + 2.16 jenkins-war From fa5179ac32a9ace459031be2df92c8b115057dba Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 31 Jul 2016 17:23:11 -0700 Subject: [PATCH 84/89] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index ce3c6ff623..8f179e4b8c 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.16 + 2.17-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index db3f62feb8..17e17e1591 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16 + 2.17-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index fe8173f186..50beb6f824 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16 + 2.17-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.16 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 2216d2826b..b49ef5f918 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16 + 2.17-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index de34f1b087..975f838c15 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.16 + 2.17-SNAPSHOT jenkins-war From cc3df0b1cb5d0758cf4c607f25ed7417b43df4dc Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 31 Jul 2016 17:33:13 -0700 Subject: [PATCH 85/89] updated changelog for release --- changelog.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.html b/changelog.html index 61efd81fec..bdce888953 100644 --- a/changelog.html +++ b/changelog.html @@ -55,6 +55,11 @@ Upcoming changes +

        What's new in 2.16 (2016/07/31)

        • Decouple bouncycastle libraries from Jenkins into bouncycastle-api plugin @@ -73,7 +78,6 @@ Upcoming changes (issue 37032)
        -

        What's new in 2.15 (2016/07/24)

        • From 662b3ef73493ed67c243893b3087ec01ab810f34 Mon Sep 17 00:00:00 2001 From: Pierre Dal-Pra Date: Mon, 1 Aug 2016 13:10:40 +0200 Subject: [PATCH 86/89] Fix quoting in undefined parameters warning log (#2479) --- core/src/main/java/hudson/model/ParametersAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/ParametersAction.java b/core/src/main/java/hudson/model/ParametersAction.java index d3ff1ef803..50a0b8184b 100644 --- a/core/src/main/java/hudson/model/ParametersAction.java +++ b/core/src/main/java/hudson/model/ParametersAction.java @@ -316,7 +316,7 @@ public class ParametersAction implements RunAction2, Iterable, Q if (this.parameterDefinitionNames.contains(v.getName()) || isSafeParameter(v.getName())) { filteredParameters.add(v); } else { - LOGGER.log(Level.WARNING, "Skipped parameter `{0}` as it is undefined on `{1}`. Set `-D{2}`=true to allow " + LOGGER.log(Level.WARNING, "Skipped parameter `{0}` as it is undefined on `{1}`. Set `-D{2}=true` to allow " + "undefined parameters to be injected as environment variables or `-D{3}=[comma-separated list]` to whitelist specific parameter names, " + "even though it represents a security breach", new Object [] { v.getName(), run.getParent().getFullName(), KEEP_UNDEFINED_PARAMETERS_SYSTEM_PROPERTY_NAME, SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME }); From 2e904d10bd17dfdd8ea0aa60b2d72a76fe85a5df Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 1 Aug 2016 14:51:11 +0200 Subject: [PATCH 87/89] Noting #2396, #2460, #2469, #2471, #2472, #2478, #2480, #2483, #2484, #2487 --- changelog.html | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/changelog.html b/changelog.html index bdce888953..3218981092 100644 --- a/changelog.html +++ b/changelog.html @@ -61,22 +61,47 @@ Upcoming changes

          What's new in 2.16 (2016/07/31)

            +
          • + Fix plugin dependency resolution. Jenkins will now refuse to load plugins with unsatisfied dependencies, which resulted in difficult to diagnose problems. This may result in errors on startup if your instance has an invalid plugin configuration., check the Jenkins log for details.
          • - Decouple bouncycastle libraries from Jenkins into bouncycastle-api plugin + Decouple bouncycastle libraries from Jenkins into bouncycastle-api plugin. (issue 36923)
          • - Upgrade to instance-identity module 2.1 - (issue 36923) + Upgrade to instance-identity module 2.1. + (issue 36922)
          • - Hide the Java Web Start launcher when the TCP agent port is disabled + Hide the Java Web Start launcher when the TCP agent port is disabled. (issue 36996) -
          • - If an older version of a detached plugin is already installed it does not get upgraded - (issue 37041)
          • Allow admins to control the enabled agent protocols on their instance from the global security settings screen. (issue 37032) -
          • +
          • + Make sure that the All view is created. + (issue 36908) +
          • + Remove trailing space from Hudson.DisplayName in Spanish, which resulted in problems with Blue Ocean. + (issue 36940) +
          • + Honor non-default update sites in setup wizard. + (issue 34882) +
          • + Display delete button only when build is not locked. + (pull 2483) +
          • + Use build start times instead of build scheduled times in build timeline widget. + (issue 36732) +
          • + Ensure that detached plugins are always at least their minimum version. + (issue 37041) +
          • + Internal: Move CLI commands wait-node-online/wait-node-offline from core to CLI module. + (issue 34915) +
          • + Internal: Allow accessing instance identity from core. + (issue 36871) +
          • + Internal: Fix the default value handling of ArtifactArchiver.excludes. + (issue 29922)

          What's new in 2.15 (2016/07/24)

            From 9d75aa1d790184b55226979fd9a427c5951263f2 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 1 Aug 2016 16:36:59 +0200 Subject: [PATCH 88/89] Fix typos in URL --- changelog.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.html b/changelog.html index 3218981092..a3dcc675e3 100644 --- a/changelog.html +++ b/changelog.html @@ -74,7 +74,7 @@ Upcoming changes (issue 36996)
          • Allow admins to control the enabled agent protocols on their instance from the global security settings screen. - (issue 37032) + (issue 37032)
          • Make sure that the All view is created. (issue 36908) @@ -95,13 +95,13 @@ Upcoming changes (issue 37041)
          • Internal: Move CLI commands wait-node-online/wait-node-offline from core to CLI module. - (issue 34915) + (issue 34915)
          • Internal: Allow accessing instance identity from core. - (issue 36871) + (issue 36871)
          • Internal: Fix the default value handling of ArtifactArchiver.excludes. - (issue 29922) + (issue 29922)

          What's new in 2.15 (2016/07/24)

            From d8cae8221e5b5ef3b5276fb53879547169a02504 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 1 Aug 2016 18:20:27 +0300 Subject: [PATCH 89/89] [FIXED JENKINS-36715] - Invoke FindBugs during the Jenkins core build (#2458) * Move FindBugs call to the parent POM, always run it and generate XMLs * Update FindBugs to 3.0.4 * Filter away Stream encoding issues * Rework FindBugs definitions according to comments from @jtnord * Generalize configuration, findbugs:findbugs now takes it. * Add TODOs to FindBugs excludes file * Remove explicit failOnError * Fix the typo spotted by @jtnord --- cli/pom.xml | 4 +++ core/pom.xml | 24 ++++++++--------- pom.xml | 27 ++++++++++++++++--- .../findbugs/findbugs-excludes.xml | 4 +++ 4 files changed, 43 insertions(+), 16 deletions(-) rename core/src/findbugs-filter.xml => src/findbugs/findbugs-excludes.xml (69%) diff --git a/cli/pom.xml b/cli/pom.xml index 8f179e4b8c..21ba2f46cf 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -92,6 +92,10 @@ + + org.codehaus.mojo + findbugs-maven-plugin + diff --git a/core/pom.xml b/core/pom.xml index 17e17e1591..db5de4ddbe 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -42,6 +42,8 @@ THE SOFTWARE. 1.243 2.5.6.SEC03 2.4.7 + + false @@ -803,6 +805,10 @@ THE SOFTWARE. + + org.codehaus.mojo + findbugs-maven-plugin + @@ -878,20 +884,12 @@ THE SOFTWARE. + findbugs - - - - org.codehaus.mojo - findbugs-maven-plugin - - Max - High - src/findbugs-filter.xml - - - - + + + true + diff --git a/pom.xml b/pom.xml index 50beb6f824..f08880f729 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,8 @@ THE SOFTWARE. 1.4.1 0.11 ${skipTests} - 3.0.1 + 3.0.4 + true 1.2 7 @@ -500,6 +501,24 @@ THE SOFTWARE. org.codehaus.mojo findbugs-maven-plugin + ${findbugs-maven-plugin.version} + + Max + High + + ../src/findbugs/findbugs-excludes.xml + true + false + + + + findbugs + + check + + verify + + org.apache.maven.plugins @@ -735,6 +754,8 @@ THE SOFTWARE. + + @@ -773,7 +794,7 @@ THE SOFTWARE. metrics - + org.codehaus.mojo @@ -783,7 +804,7 @@ THE SOFTWARE. - + debug diff --git a/core/src/findbugs-filter.xml b/src/findbugs/findbugs-excludes.xml similarity index 69% rename from core/src/findbugs-filter.xml rename to src/findbugs/findbugs-excludes.xml index 751d3fa8a5..04dd15b6b2 100644 --- a/core/src/findbugs-filter.xml +++ b/src/findbugs/findbugs-excludes.xml @@ -4,13 +4,17 @@ + + + +