From d3441de2e2400e37e1f16969dcb2975545c7da1c Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Tue, 25 Sep 2018 13:14:46 +0200 Subject: [PATCH 01/20] [JENKINS-27177] Add disable plugin command and tests --- .../java/hudson/cli/DisablePluginCommand.java | 135 ++++++++++++++ .../resources/hudson/cli/Messages.properties | 9 + .../hudson/cli/DisablePluginCommandTest.java | 170 ++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 core/src/main/java/hudson/cli/DisablePluginCommand.java create mode 100644 test/src/test/java/hudson/cli/DisablePluginCommandTest.java diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java new file mode 100644 index 0000000000..a88ff6c5e7 --- /dev/null +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -0,0 +1,135 @@ +/* + * The MIT License + * + * Copyright (c) 2018 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.cli; + +import hudson.Extension; +import hudson.PluginManager; +import hudson.PluginWrapper; +import jenkins.model.Jenkins; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import java.util.Set; + +/** + * Disable one or more installed plugins. If the plugin has an enabled dependant then it can't be disabled, the process + * continues, but the exit code is 16, instead of 0. Disabling an already disabled plugin does nothing. It only restart + * if, at least, one plugin has been disabled and the restart option was set. + * + * @since 2.136 + */ +@Extension +public class DisablePluginCommand extends CLICommand { + + @Argument(metaVar = "plugins", required = true, usage = "Plugins to be disabled.") + private List pluginNames; + + @Option(name = "-restart", usage = "Restart Jenkins after disabling plugins.") + private boolean restart; + + @Override + public String getShortDescription() { + return Messages.DisablePluginCommand_ShortDescription(); + } + + @Override + protected void printUsageSummary(PrintStream stderr) { + stderr.println(Messages.DisablePluginCommand_PrintUsageSummary()); + } + + @Override + protected int run() throws Exception { + Jenkins jenkins = Jenkins.get(); + jenkins.checkPermission(Jenkins.ADMINISTER); + + PluginManager manager = jenkins.getPluginManager(); + int result = 0; + boolean somePluginDisabled = false; // to ensure we restart when it needs to + for (String pluginName : pluginNames) { + switch (disablePlugin(manager, pluginName)) { + case DISABLED: + if(!somePluginDisabled) somePluginDisabled = true; + break; + case NOT_DISABLED_DEPENDANTS: + if(result != 16) result = 16; // Custom error code to indicate one plugin, at least, can't be disabled + } + } + + if (restart && somePluginDisabled) { + jenkins.safeRestart(); + } + + return result; + } + + /** + * Try to disable a plugin. + * @param manager The PluginManager. + * @param shortName The name of the plugin to disable. + * @return The result of the disabling of this plugin. See {@link DISABLING_STATUS} + * @throws IOException An exception disabling the plugin. See {@link PluginWrapper#disable()} + */ + private DISABLING_STATUS disablePlugin(PluginManager manager, String shortName) throws IOException { + PluginWrapper plugin = manager.getPlugin(shortName); + if (plugin == null) { + throw new IllegalArgumentException(Messages.DisablePluginCommand_NoSuchPlugin(shortName)); // exit with 3 + } + + if (!plugin.isEnabled()) { + stdout.format(Messages.DisablePluginCommand_Already_Disabled(shortName)); + stdout.println(); + return DISABLING_STATUS.ALREADY_DISABLED; + } + + Set dependants = plugin.getDependants(); + for (String dependant : dependants) { + PluginWrapper dependantPlugin = manager.getPlugin(dependant); + if (dependantPlugin != null && dependantPlugin.isEnabled()) { + // TO-DO: stdout or stderr? the process continues but as the result code is not 0, it looks like stderr + stderr.format(Messages.DisablePluginCommand_Plugin_Has_Dependant(shortName, dependant)); + stderr.println(); + return DISABLING_STATUS.NOT_DISABLED_DEPENDANTS; + } + } + + plugin.disable(); + stdout.format(Messages.DisablePluginCommand_Plugin_Disabled(plugin.getShortName())); + stdout.println(); + + return DISABLING_STATUS.DISABLED; + } + + /** + * An enum to hold the status of a disabling action. To do it more reader-friendly. + */ + private enum DISABLING_STATUS { + DISABLED, + ALREADY_DISABLED, + NOT_DISABLED_DEPENDANTS + } +} diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index df4e3f5125..77363f8954 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -113,3 +113,12 @@ WaitNodeOfflineCommand.ShortDescription=Wait for a node to become offline. CliProtocol.displayName=Jenkins CLI Protocol/1 (unencrypted) CliProtocol2.displayName=Jenkins CLI Protocol/2 (transport encryption) + +DisablePluginCommand.ShortDescription=\ +Disables one or more installed plugins. +DisablePluginCommand.Already.Disabled=The plugin {0} was already disabled. +DisablePluginCommand.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}), so it can't be disabled. +DisablePluginCommand.Plugin.Disabled=Plugin {0} disabled. +DisablePluginCommand.NoSuchPlugin=No such plugin found with the name {0}. +DisablePluginCommand.PrintUsageSummary=\ +Disable the plugins with the given short names. Plugins are disabled in order, so make sure to include plugins with dependencies before their dependencies. diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java new file mode 100644 index 0000000000..eda6970cb9 --- /dev/null +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -0,0 +1,170 @@ +/* + * The MIT License + * + * Copyright (c) 2018 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.cli; + +import hudson.PluginWrapper; +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.WithPlugin; + +import java.io.IOException; + +import static hudson.cli.CLICommandInvoker.Matcher.failedWith; +import static hudson.cli.CLICommandInvoker.Matcher.succeeded; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.*; + +public class DisablePluginCommandTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @Issue("JENKINS-27177") + @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) + public void disablePluginWithOptionalDepender() { + assertThat(disablePlugins("-restart", "dependee"), failedWith(16)); + assertPluginEnabled("dependee"); + assertJenkinsNotInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) + public void disablePluginWithMandatoryDepender() { + assertThat(disablePlugins("-restart", "dependee"), failedWith(16)); + assertPluginEnabled("dependee"); + assertJenkinsNotInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) + public void disableDependentPluginsWrongOrder() { + assertThat(disablePlugins("-restart", "dependee", "mandatory-depender"), failedWith(16)); + assertPluginDisabled("mandatory-depender"); + assertJenkinsInQuietMode(); // one plugin was disabled (mandatory-depender) + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) + public void disableDependentPluginsRightOrder() { + assertThat(disablePlugins("-restart", "mandatory-depender", "dependee"), succeeded()); + + assertPluginDisabled("dependee"); + assertPluginDisabled("mandatory-depender"); + assertJenkinsInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin("dependee-0.0.2.hpi") + public void disablePluginWithoutDepender() { + assertThat(disablePlugins("-restart", "dependee"), succeeded()); + assertPluginDisabled("dependee"); + assertJenkinsInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin("dependee-0.0.2.hpi") + public void disablePluginWithoutDependerNorRestart() { + assertThat(disablePlugins("dependee"), succeeded()); + assertPluginDisabled("dependee"); + assertJenkinsNotInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin("dependee-0.0.2.hpi") + public void disableInvalidPlugin() { + assertThat(disablePlugins("-restart", "wrongname"), failedWith(3)); + assertJenkinsNotInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin("dependee-0.0.2.hpi") + public void disableAlreadyDisabledPluginNoRestart() throws IOException { + disablePlugin("dependee"); + + assertPluginDisabled("dependee"); + assertThat(disablePlugins("-restart", "dependee"), succeeded()); + assertPluginDisabled("dependee"); + assertJenkinsNotInQuietMode(); + } + + @Test + @Issue("JENKINS-27177") + @WithPlugin({"variant.hpi", "depender-0.0.2.hpi", "mandatory-depender-0.0.2.hpi", "plugin-first.hpi", "dependee-0.0.2.hpi", }) + public void disablePluginsWithError16() { + assertThat(disablePlugins("-restart", "variant", "dependee", "depender", "plugin-first", "mandatory-depender"), failedWith(16)); + assertPluginDisabled("variant"); + assertPluginEnabled("dependee"); + assertPluginDisabled("depender"); + assertPluginDisabled("plugin-first"); + assertPluginDisabled("mandatory-depender"); + assertJenkinsInQuietMode(); // some plugins were disabled + } + + private void disablePlugin(String name) throws IOException { + PluginWrapper plugin = j.getPluginManager().getPlugin(name); + assertThat(plugin, is(notNullValue())); + plugin.disable(); + } + + /** + * Disable a list of plugins using the CLI command. + * @param args Arguments to pass to the command. + * @return Result of the command. 0 if succeed, 16 if some plugin couldn't be disabled due to dependant plugins. + */ + private CLICommandInvoker.Result disablePlugins(String... args) { + return new CLICommandInvoker(j, new DisablePluginCommand()).invokeWithArgs(args); + } + + private void assertPluginDisabled(String name) { + PluginWrapper plugin = j.getPluginManager().getPlugin(name); + assertThat(plugin, is(notNullValue())); + assertFalse(plugin.isEnabled()); + } + + private void assertPluginEnabled(String name) { + PluginWrapper plugin = j.getPluginManager().getPlugin(name); + assertThat(plugin, is(notNullValue())); + assertTrue(plugin.isEnabled()); + } + + private void assertJenkinsInQuietMode() { + QuietDownCommandTest.assertJenkinsInQuietMode(j); + } + + private void assertJenkinsNotInQuietMode() { + QuietDownCommandTest.assertJenkinsNotInQuietMode(j); + } +} From 43a1611f48d20bd99319370dfb25f48bd9fbf903 Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Tue, 25 Sep 2018 15:08:24 +0200 Subject: [PATCH 02/20] [JENKINS-27177] Add check to avoid restart in windows and a message fix. --- .../test/java/hudson/cli/DisablePluginCommandTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index eda6970cb9..f4bec458fd 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -25,6 +25,7 @@ package hudson.cli; import hudson.PluginWrapper; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -66,6 +67,7 @@ public class DisablePluginCommandTest { @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) public void disableDependentPluginsWrongOrder() { + assumeNotWindows(); assertThat(disablePlugins("-restart", "dependee", "mandatory-depender"), failedWith(16)); assertPluginDisabled("mandatory-depender"); assertJenkinsInQuietMode(); // one plugin was disabled (mandatory-depender) @@ -75,8 +77,8 @@ public class DisablePluginCommandTest { @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) public void disableDependentPluginsRightOrder() { + assumeNotWindows(); assertThat(disablePlugins("-restart", "mandatory-depender", "dependee"), succeeded()); - assertPluginDisabled("dependee"); assertPluginDisabled("mandatory-depender"); assertJenkinsInQuietMode(); @@ -86,6 +88,7 @@ public class DisablePluginCommandTest { @Issue("JENKINS-27177") @WithPlugin("dependee-0.0.2.hpi") public void disablePluginWithoutDepender() { + assumeNotWindows(); assertThat(disablePlugins("-restart", "dependee"), succeeded()); assertPluginDisabled("dependee"); assertJenkinsInQuietMode(); @@ -124,6 +127,7 @@ public class DisablePluginCommandTest { @Issue("JENKINS-27177") @WithPlugin({"variant.hpi", "depender-0.0.2.hpi", "mandatory-depender-0.0.2.hpi", "plugin-first.hpi", "dependee-0.0.2.hpi", }) public void disablePluginsWithError16() { + assumeNotWindows(); assertThat(disablePlugins("-restart", "variant", "dependee", "depender", "plugin-first", "mandatory-depender"), failedWith(16)); assertPluginDisabled("variant"); assertPluginEnabled("dependee"); @@ -167,4 +171,8 @@ public class DisablePluginCommandTest { private void assertJenkinsNotInQuietMode() { QuietDownCommandTest.assertJenkinsNotInQuietMode(j); } + + private void assumeNotWindows() { + Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows")); + } } From 1062f9abd86271bfec678d4f1f103639d11873ca Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Tue, 25 Sep 2018 15:26:40 +0200 Subject: [PATCH 03/20] [JENKINS-27177] Some polishment. --- .../java/hudson/cli/DisablePluginCommand.java | 16 ++++++++-------- .../resources/hudson/cli/Messages.properties | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index a88ff6c5e7..a0eca19f0b 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -41,7 +41,7 @@ import java.util.Set; * continues, but the exit code is 16, instead of 0. Disabling an already disabled plugin does nothing. It only restart * if, at least, one plugin has been disabled and the restart option was set. * - * @since 2.136 + * @since TODO */ @Extension public class DisablePluginCommand extends CLICommand { @@ -59,6 +59,7 @@ public class DisablePluginCommand extends CLICommand { @Override protected void printUsageSummary(PrintStream stderr) { + super.printUsageSummary(stderr); stderr.println(Messages.DisablePluginCommand_PrintUsageSummary()); } @@ -91,10 +92,10 @@ public class DisablePluginCommand extends CLICommand { * Try to disable a plugin. * @param manager The PluginManager. * @param shortName The name of the plugin to disable. - * @return The result of the disabling of this plugin. See {@link DISABLING_STATUS} + * @return The result of the disabling of this plugin. See {@link DisablingStatus} * @throws IOException An exception disabling the plugin. See {@link PluginWrapper#disable()} */ - private DISABLING_STATUS disablePlugin(PluginManager manager, String shortName) throws IOException { + private DisablingStatus disablePlugin(PluginManager manager, String shortName) throws IOException { PluginWrapper plugin = manager.getPlugin(shortName); if (plugin == null) { throw new IllegalArgumentException(Messages.DisablePluginCommand_NoSuchPlugin(shortName)); // exit with 3 @@ -103,17 +104,16 @@ public class DisablePluginCommand extends CLICommand { if (!plugin.isEnabled()) { stdout.format(Messages.DisablePluginCommand_Already_Disabled(shortName)); stdout.println(); - return DISABLING_STATUS.ALREADY_DISABLED; + return DisablingStatus.ALREADY_DISABLED; } Set dependants = plugin.getDependants(); for (String dependant : dependants) { PluginWrapper dependantPlugin = manager.getPlugin(dependant); if (dependantPlugin != null && dependantPlugin.isEnabled()) { - // TO-DO: stdout or stderr? the process continues but as the result code is not 0, it looks like stderr stderr.format(Messages.DisablePluginCommand_Plugin_Has_Dependant(shortName, dependant)); stderr.println(); - return DISABLING_STATUS.NOT_DISABLED_DEPENDANTS; + return DisablingStatus.NOT_DISABLED_DEPENDANTS; } } @@ -121,13 +121,13 @@ public class DisablePluginCommand extends CLICommand { stdout.format(Messages.DisablePluginCommand_Plugin_Disabled(plugin.getShortName())); stdout.println(); - return DISABLING_STATUS.DISABLED; + return DisablingStatus.DISABLED; } /** * An enum to hold the status of a disabling action. To do it more reader-friendly. */ - private enum DISABLING_STATUS { + private enum DisablingStatus { DISABLED, ALREADY_DISABLED, NOT_DISABLED_DEPENDANTS diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index 77363f8954..73a6609eab 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -117,7 +117,7 @@ CliProtocol2.displayName=Jenkins CLI Protocol/2 (transport encryption) DisablePluginCommand.ShortDescription=\ Disables one or more installed plugins. DisablePluginCommand.Already.Disabled=The plugin {0} was already disabled. -DisablePluginCommand.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}), so it can't be disabled. +DisablePluginCommand.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}), so it cannot be disabled. DisablePluginCommand.Plugin.Disabled=Plugin {0} disabled. DisablePluginCommand.NoSuchPlugin=No such plugin found with the name {0}. DisablePluginCommand.PrintUsageSummary=\ From 18be1960d145293084cfab48bfffb1ab65e147fc Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Tue, 25 Sep 2018 17:49:07 +0200 Subject: [PATCH 04/20] [JENKINS-27177] Change windows check. --- test/src/test/java/hudson/cli/DisablePluginCommandTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index f4bec458fd..afac7929d0 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -24,6 +24,7 @@ package hudson.cli; +import hudson.Functions; import hudson.PluginWrapper; import org.junit.Assume; import org.junit.Rule; @@ -173,6 +174,6 @@ public class DisablePluginCommandTest { } private void assumeNotWindows() { - Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows")); + Assume.assumeFalse(Functions.isWindows()); } } From fe8d0d5e7c31bc938c90708c895780ce0be80fbd Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Mon, 15 Oct 2018 10:06:21 +0200 Subject: [PATCH 05/20] [JENKINS-27177] Add strategies to disable plugins. Move process to core Move the process to core to be reusable, not only for the cli command. Add optionalDependants list to PluginWrapper to process them easily. Replace (depreacate) the disable method without checks by this one with strategies. Improve messages and tests --- .gitignore | 3 + core/src/main/java/hudson/PluginManager.java | 143 +++++----- core/src/main/java/hudson/PluginWrapper.java | 227 +++++++++++++++- .../java/hudson/cli/DisablePluginCommand.java | 215 +++++++++++---- .../main/resources/hudson/Messages.properties | 4 + .../resources/hudson/Messages_es.properties | 4 + .../resources/hudson/cli/Messages.properties | 10 +- .../hudson/cli/Messages_es.properties | 7 +- .../hudson/cli/DisablePluginCommandTest.java | 255 ++++++++++++++++-- 9 files changed, 686 insertions(+), 182 deletions(-) diff --git a/.gitignore b/.gitignore index 3cde4d575d..5c8b1b7ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ out .project build +# VSCode workspace file +*.code-workspace + # vim *~ *.swp diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index be83e07456..ce41655415 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -25,47 +25,29 @@ package hudson; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import hudson.security.ACLContext; -import jenkins.ExtensionRefreshException; -import jenkins.util.SystemProperties; import hudson.PluginWrapper.Dependency; import hudson.init.InitMilestone; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; -import hudson.model.AbstractItem; -import hudson.model.AbstractModelObject; -import hudson.model.AdministrativeMonitor; -import hudson.model.Api; -import hudson.model.Descriptor; -import hudson.model.Failure; -import hudson.model.ItemGroupMixIn; -import hudson.model.UpdateCenter; -import hudson.model.UpdateSite; +import hudson.model.*; import hudson.model.UpdateCenter.DownloadJob; import hudson.model.UpdateCenter.InstallationJob; import hudson.security.ACL; +import hudson.security.ACLContext; import hudson.security.Permission; import hudson.security.PermissionScope; -import hudson.util.CyclicGraphDetector; +import hudson.util.*; import hudson.util.CyclicGraphDetector.CycleDetectedException; -import hudson.util.PersistedList; -import hudson.util.Service; -import hudson.util.VersionNumber; -import hudson.util.XStream2; -import jenkins.ClassLoaderReflectionToolkit; -import jenkins.InitReactorRunner; -import jenkins.MissingDependencyException; -import jenkins.RestartRequiredException; -import jenkins.YesNoMaybe; +import jenkins.*; import jenkins.install.InstallState; import jenkins.install.InstallUtil; import jenkins.model.Jenkins; +import jenkins.security.CustomClassFilter; +import jenkins.util.SystemProperties; import jenkins.util.io.OnMaster; import jenkins.util.xml.RestrictiveEntityResolver; - import net.sf.json.JSONArray; import net.sf.json.JSONObject; - import org.acegisecurity.Authentication; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; @@ -78,23 +60,19 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.jenkinsci.Symbol; import org.jenkinsci.bytecode.Transformer; -import org.jvnet.hudson.reactor.Executable; -import org.jvnet.hudson.reactor.Reactor; -import org.jvnet.hudson.reactor.ReactorException; -import org.jvnet.hudson.reactor.TaskBuilder; -import org.jvnet.hudson.reactor.TaskGraphBuilder; +import org.jvnet.hudson.reactor.*; +import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; -import org.kohsuke.stapler.HttpRedirect; -import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.*; import org.kohsuke.stapler.HttpResponses; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerOverridable; -import org.kohsuke.stapler.StaplerProxy; -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; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -102,60 +80,24 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; -import java.io.Closeable; -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.lang.ref.WeakReference; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -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.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; +import java.net.*; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import java.util.function.Supplier; +import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; import static hudson.init.InitMilestone.*; -import hudson.model.DownloadService; -import hudson.util.FormValidation; -import java.io.ByteArrayInputStream; -import java.net.JarURLConnection; -import java.net.URLConnection; -import java.util.ServiceLoader; -import java.util.function.Supplier; -import java.util.jar.JarEntry; - -import static java.util.logging.Level.FINE; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.SEVERE; -import static java.util.logging.Level.WARNING; -import jenkins.security.CustomClassFilter; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; +import static java.util.logging.Level.*; /** * Manages {@link PluginWrapper}s. @@ -935,8 +877,15 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Restricted(NoExternalUse.class) public synchronized void resolveDependantPlugins() { for (PluginWrapper plugin : plugins) { + // Set of optional dependants plugins of plugin + Set optionalDependants = new HashSet<>(); Set dependants = new HashSet<>(); for (PluginWrapper possibleDependant : plugins) { + // No need to check if plugin is dependant of itself + if(possibleDependant.getShortName().equals(plugin.getShortName())) { + continue; + } + // The plugin could have just been deleted. If so, it doesn't // count as a dependant. if (possibleDependant.isDeleted()) { @@ -946,10 +895,20 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas for (Dependency dependency : dependencies) { if (dependency.shortName.equals(plugin.getShortName())) { dependants.add(possibleDependant.getShortName()); + + // If, in addition, the dependency is optional, add to the optionalDependants list + if (dependency.optional) { + optionalDependants.add(possibleDependant.getShortName()); + } + + // already know possibleDependant depends on plugin, no need to continue with the rest of + // dependencies. We continue with the next possibleDependant + break; } } } plugin.setDependants(dependants); + plugin.setOptionalDependants(optionalDependants); } } @@ -1829,6 +1788,34 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas return new MetadataCache(); } + /** + * Disable a list of plugins using a strategy for its dependants plugins. + * @param strategy the strategy regarding how the dependant plugins are processed + * @param plugins the list of plugins + * @return the list of results for every plugin and its dependant plugins. + * @throws IOException see {@link PluginWrapper#disable()} + */ + public List disablePlugins(PluginWrapper.PluginDisableStrategy strategy, List plugins) throws IOException { + // Where we store the results of each plugin disablement + List results = new ArrayList<>(plugins.size()); + + // Disable all plugins passed + for (String pluginName : plugins) { + PluginWrapper plugin = this.getPlugin(pluginName); + + if (plugin == null) { + PluginWrapper.PluginDisableResult result = new PluginWrapper.PluginDisableResult(pluginName); + result.setMessage(Messages.PluginWrapper_NoSuchPlugin(pluginName)); + result.setStatus(PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN); + results.add(result); + } else { + results.add(plugin.disable(strategy)); + } + } + + return results; + } + @Restricted(NoExternalUse.class) // table.jelly public static final class MetadataCache { private final Map data = new HashMap<>(); diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 363d588f0a..28dccf1f6a 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -25,7 +25,9 @@ package hudson; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import hudson.PluginManager.PluginInstanceStore; +import hudson.cli.DisablePluginCommand; import hudson.model.AdministrativeMonitor; import hudson.model.Api; import hudson.model.ModelObject; @@ -56,16 +58,7 @@ import java.io.File; 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.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -200,6 +193,11 @@ public class PluginWrapper implements Comparable, ModelObject { */ private Set dependants = Collections.emptySet(); + /** + * List of plugins that depend optionally on this plugin. + */ + private Set optionalDependants = Collections.emptySet(); + /** * The core can depend on a plugin if it is bundled. Sometimes it's the only thing that * depends on the plugin e.g. UI support library bundle plugin. @@ -214,6 +212,14 @@ public class PluginWrapper implements Comparable, ModelObject { this.dependants = dependants; } + /** + * Set the list of components that depend optionally on this plugin. + * @param optionalDependants The list of components that depend optionally on this plugin. + */ + public void setOptionalDependants(@Nonnull Set optionalDependants) { + this.optionalDependants = optionalDependants; + } + /** * Get the list of components that depend on this plugin. * @return The list of components that depend on this plugin. @@ -226,6 +232,14 @@ public class PluginWrapper implements Comparable, ModelObject { } } + /** + * Get the list of components that depend optionally on this plugin. + * @return The list of components that depend optionally on this plugin. + */ + public @Nonnull Set getOptionalDependants() { + return optionalDependants; + } + /** * Does this plugin have anything that depends on it. * @return {@code true} if something (Jenkins core, or another plugin) depends on this @@ -234,7 +248,17 @@ public class PluginWrapper implements Comparable, ModelObject { public boolean hasDependants() { return (isBundled || !dependants.isEmpty()); } - + + /** + * Does this plugin have anything that depends optionally on it. + * @return {@code true} if something (Jenkins core, or another plugin) depends optionally on this + * plugin, otherwise {@code false}. + */ + public boolean hasOptionalDependants() { + return !optionalDependants.isEmpty(); + } + + /** * Does this plugin depend on any other plugins. * @return {@code true} if this plugin depends on other plugins, otherwise {@code false}. @@ -519,9 +543,19 @@ public class PluginWrapper implements Comparable, ModelObject { } /** - * Disables this plugin next time Jenkins runs. + * Disables this plugin next time Jenkins runs. As it doesn't check anything, it's recommended to use the method + * {@link #disable(PluginDisableStrategy)} */ + @Deprecated //see https://issues.jenkins-ci.org/browse/JENKINS-27177 public void disable() throws IOException { + disableWithoutCheck(); + } + + /** + * Disable a plugin wihout checking any dependency. Only add the disable file. + * @throws IOException + */ + private void disableWithoutCheck() throws IOException { // creates an empty file try (OutputStream os = Files.newOutputStream(disableFile.toPath())) { os.close(); @@ -530,6 +564,95 @@ public class PluginWrapper implements Comparable, ModelObject { } } + /** + * Disable this plugin using a strategy. + * @param strategy strategy to use + * @return an object representing the result of the disablement of this plugin and its dependants plugins. + * @throws IOException If this plugin or one dependant plugin raise this exception during its disablement. It could + * be raised because the disable file cannot be created. + */ + @Nonnull + public PluginDisableResult disable(@Nonnull PluginDisableStrategy strategy) throws IOException { + PluginDisableResult result = new PluginDisableResult(shortName); + + if (!this.isEnabled()) { + result.setMessage(Messages.PluginWrapper_Already_Disabled(shortName)); + result.setStatus(PluginDisableStatus.ALREADY_DISABLED); + return result; + } + + // Act as a flag indicating if this plugin, finally, can be disabled. If there is a not-disabled-dependant + // plugin, this one couldn't be disabled. + String aDependantNotDisabled = null; + + // List of dependants plugins to 'check'. 'Check' means disable for mandatory or all strategies, or review if + // this dependant-mandatory plugin is enabled in order to return an error for the NONE strategy. + Set dependantsToCheck; + switch (strategy) { + case MANDATORY: + // As of getDependants has all the dependants, we get the difference between them and only the optionals + dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); + break; + case ALL: + // getDependants returns all the dependant plugins, mandatory or optional. + dependantsToCheck = this.getDependants(); + break; + case NONE: + default: + // with NONE, the process only fail if mandatory dependant plugins exists + dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); + } + + // Review all the dependants and add to the plugin result what happened with its dependants + for (String dependant : dependantsToCheck) { + PluginWrapper dependantPlugin = parent.getPlugin(dependant); + + // The dependant plugin doesn't exist, add an error to the report + if (dependantPlugin == null) { + PluginDisableResult dependantStatus = new PluginDisableResult(dependant); + dependantStatus.setStatus(PluginDisableStatus.NO_SUCH_PLUGIN); + dependantStatus.setMessage(Messages.PluginWrapper_NoSuchPlugin(dependant)); + result.addDependantDisableStatus(dependantStatus); + + // If the strategy is none and there is some enabled dependant plugin, the plugin cannot be disabled. If + // this dependant plugin is not enabled, continue searching for one enabled. + } else if (strategy.equals(PluginDisableStrategy.NONE)) { + if (dependantPlugin.isEnabled()) { + aDependantNotDisabled = dependant; + break; // in this case, we don't need to continue with the rest of its dependants + } + + // If the strategy is not none and this dependant plugin is not enabled, add it as already disabled + } else if (!dependantPlugin.isEnabled()) { + PluginDisableResult dependantStatus = new PluginDisableResult(dependant); + dependantStatus.setStatus(PluginDisableStatus.ALREADY_DISABLED); + dependantStatus.setMessage(Messages.PluginWrapper_Already_Disabled(dependant)); + result.addDependantDisableStatus(dependantStatus); + + // If the strategy is not none and this dependant plugin is enabled, disable it + } else { + // As there is no cycles in the plugin dependencies, the recursion shouldn't be infinite. The + // strategy used is the same for its dependants plugins + result.addDependantDisableStatus(dependantPlugin.disable(strategy)); + } + } + + // If there is no enabled-dependant plugin, disable this plugin and add it to the result + if (aDependantNotDisabled == null) { + this.disableWithoutCheck(); + result.setMessage(Messages.PluginWrapper_Plugin_Disabled(shortName)); + result.setStatus(PluginDisableStatus.DISABLED); + + // if there is yet some not disabled dependant plugin (only possible with none strategy), this plugin cannot + // be disabled. + } else { + result.setMessage(Messages.PluginWrapper_Plugin_Has_Dependant(shortName, aDependantNotDisabled, strategy)); + result.setStatus(PluginDisableStatus.NOT_DISABLED_DEPENDANTS); + } + + return result; + } + /** * Returns true if this plugin is enabled for this session. */ @@ -829,6 +952,86 @@ public class PluginWrapper implements Comparable, ModelObject { } } + /** + * The result of the disablement of a plugin and its dependants plugins. + */ + public static class PluginDisableResult { + private String plugin; + private PluginDisableStatus status; + private String message; + private Set dependantsDisableStatus = new HashSet<>(); + + public PluginDisableResult(String plugin) { + this.plugin = plugin; + } + + public String getPlugin() { + return plugin; + } + + public PluginDisableStatus getStatus() { + return status; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PluginDisableResult that = (PluginDisableResult) o; + return Objects.equals(plugin, that.plugin); + } + + @Override + public int hashCode() { + return Objects.hash(plugin); + } + + public void setStatus(PluginDisableStatus status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Set getDependantsDisableStatus() { + return dependantsDisableStatus; + } + + public void addDependantDisableStatus(PluginDisableResult dependantDisableStatus) { + dependantsDisableStatus.add(dependantDisableStatus); + } + + } + + /** + * An enum to hold the status of a disabling action against a plugin. To do it more reader-friendly. + */ + public static enum PluginDisableStatus { + NO_SUCH_PLUGIN, + DISABLED, + ALREADY_DISABLED, + NOT_DISABLED_DEPENDANTS + } + + /** + * The strategies defined for disabling a plugin. + */ + public static enum PluginDisableStrategy { + NONE, + MANDATORY, + ALL; + + @Override + public String toString() { + return this.name().toLowerCase(); + } + } + // // // Action methods diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index a0eca19f0b..8e221c9e42 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -25,38 +25,48 @@ package hudson.cli; import hudson.Extension; -import hudson.PluginManager; import hudson.PluginWrapper; +import hudson.lifecycle.RestartNotSupportedException; import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import java.io.IOException; import java.io.PrintStream; import java.util.List; -import java.util.Set; /** - * Disable one or more installed plugins. If the plugin has an enabled dependant then it can't be disabled, the process - * continues, but the exit code is 16, instead of 0. Disabling an already disabled plugin does nothing. It only restart - * if, at least, one plugin has been disabled and the restart option was set. - * + * Disable one or more installed plugins. * @since TODO */ @Extension public class DisablePluginCommand extends CLICommand { - @Argument(metaVar = "plugins", required = true, usage = "Plugins to be disabled.") + @Argument(metaVar = "plugin1 plugin2 plugin3", required = true, usage = "Plugins to be disabled.") private List pluginNames; - @Option(name = "-restart", usage = "Restart Jenkins after disabling plugins.") + @Option(name = "-restart", aliases = "-r", usage = "Restart Jenkins after disabling plugins.") private boolean restart; + @Option(name = "-strategy", aliases = "-s", metaVar = "strategy", usage = "How to process the dependant plugins. \n" + + "- none: if a mandatory dependant plugin exists and it is enabled, the plugin cannot be disabled (default value).\n" + + "- mandatory: all mandatory dependant plugins are also disabled, optional dependant plugins remain enabled.\n" + + "- all: all dependant plugins are also disabled, no matter if its dependency is optional or mandatory.") + private String strategy = PluginWrapper.PluginDisableStrategy.NONE.toString(); + + @Option(name = "-quiet", aliases = "-q", usage = "Be quiet, print only the error messages") + private boolean quiet; + + private static final int INDENT_SPACE = 3; + @Override public String getShortDescription() { return Messages.DisablePluginCommand_ShortDescription(); } + // package-private access to be able to use it in the tests + static final int RETURN_CODE_NOT_DISABLED_DEPENDANTS = 16; + static final int RETURN_CODE_NO_SUCH_PLUGIN = 17; + @Override protected void printUsageSummary(PrintStream stderr) { super.printUsageSummary(stderr); @@ -68,68 +78,161 @@ public class DisablePluginCommand extends CLICommand { Jenkins jenkins = Jenkins.get(); jenkins.checkPermission(Jenkins.ADMINISTER); - PluginManager manager = jenkins.getPluginManager(); - int result = 0; - boolean somePluginDisabled = false; // to ensure we restart when it needs to - for (String pluginName : pluginNames) { - switch (disablePlugin(manager, pluginName)) { - case DISABLED: - if(!somePluginDisabled) somePluginDisabled = true; + // get the strategy as an enum + PluginWrapper.PluginDisableStrategy strategyToUse; + try { + strategyToUse = PluginWrapper.PluginDisableStrategy.valueOf(strategy.toUpperCase()); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException(hudson.cli.Messages.DisablePluginCommand_NoSuchStrategy(strategy, String.format("%s, %s, %s", PluginWrapper.PluginDisableStrategy.NONE, PluginWrapper.PluginDisableStrategy.MANDATORY, PluginWrapper.PluginDisableStrategy.ALL))); + } + + // disable... + List results = jenkins.pluginManager.disablePlugins(strategyToUse, pluginNames); + + // print results ... + printResults(results); + + // restart if it was required and it's necessary (some plugin was disabled in this execution) + restartIfNecessary(results); + + // return the appropriate error code + return getResultCode(results); + } + + /** + * Print the result of all the process + * @param results the list of results for the disablement of each plugin + */ + private void printResults( List results) { + for (PluginWrapper.PluginDisableResult oneResult : results) { + printResult(oneResult, 0); + } + } + + /** + * Print indented the arguments with the format passed beginning with the indent passed. + * @param indent number of spaces at the beginning. + * @param format format as in {@link String#format(String, Object...)} + * @param arguments arguments to print as in {@link String#format(String, Object...)} + */ + private void printIndented(int indent, String format, String... arguments) { + if (indent == 0) { + stdout.format(format + "%n", (Object[]) arguments); + } else { + String f = "%" + indent + "s" + format + "%n"; + String[] newArgs = new String[arguments.length + 1]; + newArgs[0] = " "; int i = 1; + for (String arg: arguments ) { + newArgs[i] = arg; + i++; + } + stdout.format(f, newArgs); + } + } + + /** + * Print the result of a plugin disablement with the indent passed. + * @param oneResult the result of the disablement of a plugin. + * @param indent the initial indent. + */ + private void printResult(PluginWrapper.PluginDisableResult oneResult, int indent) { + PluginWrapper.PluginDisableStatus status = oneResult.getStatus(); + if(quiet && (PluginWrapper.PluginDisableStatus.DISABLED.equals(status) || PluginWrapper.PluginDisableStatus.ALREADY_DISABLED.equals(status))) { + return; + } + + printIndented(indent, Messages.DisablePluginCommand_StatusMessage(oneResult.getPlugin(), oneResult.getStatus(), oneResult.getMessage())); + if(oneResult.getDependantsDisableStatus().size() > 0) { + indent = (indent == 0) ? INDENT_SPACE : indent*2; + for (PluginWrapper.PluginDisableResult oneDependantResult : oneResult.getDependantsDisableStatus()) { + printResult(oneDependantResult, indent); + } + } + } + + /** + * Restart if at least one plugin was disabled in this process. + * @param results the list of results after the disablement of the plugins. + */ + private void restartIfNecessary(List results) throws RestartNotSupportedException { + if (restart) { + for (PluginWrapper.PluginDisableResult oneResult : results) { + if (restartIfNecessary(oneResult)) { break; - case NOT_DISABLED_DEPENDANTS: - if(result != 16) result = 16; // Custom error code to indicate one plugin, at least, can't be disabled + } + } + } + } + + /** + * Restart if this particular result of the disablement of a plugin and its dependant plugins (depending on the + * strategy used) has a plugin disablexd. + * @param oneResult the result of a plugin (and its dependants). + * @return true if it end up in restarting jenkins. + */ + private boolean restartIfNecessary(PluginWrapper.PluginDisableResult oneResult) throws RestartNotSupportedException { + PluginWrapper.PluginDisableStatus status = oneResult.getStatus(); + if(PluginWrapper.PluginDisableStatus.DISABLED.equals(status)) { + Jenkins.get().safeRestart(); + return true; + } + + if(oneResult.getDependantsDisableStatus().size() > 0) { + for (PluginWrapper.PluginDisableResult oneDependantResult : oneResult.getDependantsDisableStatus()) { + if (restartIfNecessary(oneDependantResult)) { + return true; + } } } - if (restart && somePluginDisabled) { - jenkins.safeRestart(); + return false; + } + + + /** + * Calculate the result code of the full process based in what went on during the process + * @param results he list of results for the disablement of each plugin + * @return the status code. 0 if all plugins disabled. {@link #RETURN_CODE_NOT_DISABLED_DEPENDANTS} if some + * dependant plugin is not disabled (with strategy NONE), {@link #RETURN_CODE_NO_SUCH_PLUGIN} if some passed + * plugin doesn't exist. Whatever happens first. + */ + private int getResultCode(List results) { + int result = 0; + for (PluginWrapper.PluginDisableResult oneResult : results) { + result = getResultCode(oneResult); + if (result != 0) { + break; + } } return result; } /** - * Try to disable a plugin. - * @param manager The PluginManager. - * @param shortName The name of the plugin to disable. - * @return The result of the disabling of this plugin. See {@link DisablingStatus} - * @throws IOException An exception disabling the plugin. See {@link PluginWrapper#disable()} + * Calculate the result code of the disablement of one plugin based in what went on during the process of this one + * and its dependant plugins. + * @param result the result of the disablement of this plugin + * @return the status code */ - private DisablingStatus disablePlugin(PluginManager manager, String shortName) throws IOException { - PluginWrapper plugin = manager.getPlugin(shortName); - if (plugin == null) { - throw new IllegalArgumentException(Messages.DisablePluginCommand_NoSuchPlugin(shortName)); // exit with 3 + private int getResultCode(PluginWrapper.PluginDisableResult result) { + int returnCode = 0; + switch (result.getStatus()){ + case NOT_DISABLED_DEPENDANTS: + returnCode = RETURN_CODE_NOT_DISABLED_DEPENDANTS; + break; + case NO_SUCH_PLUGIN: + returnCode = RETURN_CODE_NO_SUCH_PLUGIN; } - if (!plugin.isEnabled()) { - stdout.format(Messages.DisablePluginCommand_Already_Disabled(shortName)); - stdout.println(); - return DisablingStatus.ALREADY_DISABLED; - } - - Set dependants = plugin.getDependants(); - for (String dependant : dependants) { - PluginWrapper dependantPlugin = manager.getPlugin(dependant); - if (dependantPlugin != null && dependantPlugin.isEnabled()) { - stderr.format(Messages.DisablePluginCommand_Plugin_Has_Dependant(shortName, dependant)); - stderr.println(); - return DisablingStatus.NOT_DISABLED_DEPENDANTS; + if (returnCode == 0) { + for (PluginWrapper.PluginDisableResult oneDependantResult : result.getDependantsDisableStatus()) { + returnCode = getResultCode(oneDependantResult); + if (returnCode != 0) { + break; + } } } - plugin.disable(); - stdout.format(Messages.DisablePluginCommand_Plugin_Disabled(plugin.getShortName())); - stdout.println(); - - return DisablingStatus.DISABLED; - } - - /** - * An enum to hold the status of a disabling action. To do it more reader-friendly. - */ - private enum DisablingStatus { - DISABLED, - ALREADY_DISABLED, - NOT_DISABLED_DEPENDANTS + return returnCode; } } diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 80ecb8db83..78b8e37f83 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -83,6 +83,10 @@ 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. PluginWrapper.PluginWrapperAdministrativeMonitor.DisplayName=Plugins Failed To Load +PluginWrapper.Already.Disabled=The plugin {0} was already disabled +PluginWrapper.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}) and the disable strategy is {2}, so it cannot be disabled +PluginWrapper.Plugin.Disabled=Plugin {0} disabled +PluginWrapper.NoSuchPlugin=No such plugin found with the name {0} TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/Messages_es.properties b/core/src/main/resources/hudson/Messages_es.properties index 9ad1d7eeb3..c787fed9c3 100644 --- a/core/src/main/resources/hudson/Messages_es.properties +++ b/core/src/main/resources/hudson/Messages_es.properties @@ -55,3 +55,7 @@ PluginManager.PluginDoesntSupportDynamicLoad.RestartRequired=El plugin {0} no so PluginManager.PortNotANumber=El puerto no es un número AboutJenkins.Description=Eche un vistazo a la información sobre la versión y la licencia. PluginManager.PortNotInRange=El puerto no está en el rango de {0} hasta {1} +PluginWrapper.Already.Disabled=El plugin {0} ya estaba deshabilitado +PluginWrapper.Plugin.Has.Dependant=El plugin {0} tiene, al menos, un plugin dependiente ({1}) y la estrategia de deshabilitación es {2}, así que no puede ser deshabilitado +PluginWrapper.Plugin.Disabled=Plugin {0} deshabilitado +PluginWrapper.NoSuchPlugin=No se encuentra un plugin con el nombre {0} diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index 73a6609eab..f502ad9331 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -115,10 +115,8 @@ CliProtocol.displayName=Jenkins CLI Protocol/1 (unencrypted) CliProtocol2.displayName=Jenkins CLI Protocol/2 (transport encryption) DisablePluginCommand.ShortDescription=\ -Disables one or more installed plugins. -DisablePluginCommand.Already.Disabled=The plugin {0} was already disabled. -DisablePluginCommand.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}), so it cannot be disabled. -DisablePluginCommand.Plugin.Disabled=Plugin {0} disabled. -DisablePluginCommand.NoSuchPlugin=No such plugin found with the name {0}. +Disable one or more installed plugins. +DisablePluginCommand.NoSuchStrategy=This strategy ({0}) does not exist. Allowed strategies are {1} DisablePluginCommand.PrintUsageSummary=\ -Disable the plugins with the given short names. Plugins are disabled in order, so make sure to include plugins with dependencies before their dependencies. +Disable the plugins with the given short names. You can define how to proceed with the dependant plugins and if a restart after should be done. You can also set the quiet mode to avoid extra info in the console. +DisablePluginCommand.StatusMessage=Disabling {0}: {1} ({2}) \ No newline at end of file diff --git a/core/src/main/resources/hudson/cli/Messages_es.properties b/core/src/main/resources/hudson/cli/Messages_es.properties index 11525579ca..7a7794b508 100644 --- a/core/src/main/resources/hudson/cli/Messages_es.properties +++ b/core/src/main/resources/hudson/cli/Messages_es.properties @@ -59,4 +59,9 @@ CancelQuietDownCommand.ShortDescription=Cancelar el efecto del comando "quiet-do 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 - +DisablePluginCommand.ShortDescription=\ +Deshabilita uno o más plugins instalados. +DisablePluginCommand.NoSuchStrategy=Esta estrategia ({0}) no existe. Las estrategias permitidas son {1} +DisablePluginCommand.PrintUsageSummary=\ +Deshabilita los plugins con los nombre cortos dados. Puede definir cómo proceder con los plugins dependientes y si se realiza un reinicio posterior. +DisablePluginCommand.StatusMessage=Deshabilitando {0}: {1} ({2}) \ No newline at end of file diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index afac7929d0..aba2819963 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -1,3 +1,4 @@ + /* * The MIT License * @@ -26,6 +27,7 @@ package hudson.cli; import hudson.Functions; import hudson.PluginWrapper; +import org.apache.commons.lang.StringUtils; import org.junit.Assume; import org.junit.Rule; import org.junit.Test; @@ -34,9 +36,12 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.WithPlugin; import java.io.IOException; +import java.util.function.BiPredicate; import static hudson.cli.CLICommandInvoker.Matcher.failedWith; import static hudson.cli.CLICommandInvoker.Matcher.succeeded; +import static hudson.cli.DisablePluginCommand.RETURN_CODE_NOT_DISABLED_DEPENDANTS; +import static hudson.cli.DisablePluginCommand.RETURN_CODE_NO_SUCH_PLUGIN; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.*; @@ -46,90 +51,129 @@ public class DisablePluginCommandTest { @Rule public JenkinsRule j = new JenkinsRule(); + /** + * Can disable a plugin with an optional dependent plugin. + * With strategy none. + */ @Test @Issue("JENKINS-27177") @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void disablePluginWithOptionalDepender() { - assertThat(disablePlugins("-restart", "dependee"), failedWith(16)); - assertPluginEnabled("dependee"); - assertJenkinsNotInQuietMode(); + public void canDisablePluginWithOptionalDependerStNone() { + assertThat(disablePluginsCLiCommand("-strategy", "NONE", "dependee"), succeeded()); + assertPluginDisabled("dependee"); } @Test @Issue("JENKINS-27177") - @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void disablePluginWithMandatoryDepender() { - assertThat(disablePlugins("-restart", "dependee"), failedWith(16)); - assertPluginEnabled("dependee"); - assertJenkinsNotInQuietMode(); + @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) + public void canDisablePluginWithDependantsDisabledStNone() throws IOException { + disablePlugin("mandatory-depender"); + CLICommandInvoker.Result result = disablePluginsCLiCommand("-strategy", "NONE", "dependee"); + + assertThat(result, succeeded()); + assertEquals("Disabling only dependee", 1, StringUtils.countMatches(result.stdout(), "Disabling")); + assertPluginDisabled("dependee"); } + /** + * Can't disable a plugin with a mandatory dependent plugin. + * With default strategy (none). + */ @Test @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void disableDependentPluginsWrongOrder() { - assumeNotWindows(); - assertThat(disablePlugins("-restart", "dependee", "mandatory-depender"), failedWith(16)); + public void cannotDisablePluginWithMandatoryDependerStNone() { + assertThat(disablePluginsCLiCommand("dependee"), failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); + assertPluginEnabled("dependee"); + } + + /** + * Can't disable a plugin with a mandatory dependent plugin before its dependent plugin. + * With default strategy (none). + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) + public void cannotDisableDependentPluginWrongOrderStNone() { + assertThat(disablePluginsCLiCommand("dependee", "mandatory-depender"), failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); assertPluginDisabled("mandatory-depender"); - assertJenkinsInQuietMode(); // one plugin was disabled (mandatory-depender) + assertPluginEnabled("dependee"); } + /** + * Can disable a plugin with a mandatory dependent plugin after being disabled the mandatory dependent plugin. With + * default strategy (none). + */ @Test @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void disableDependentPluginsRightOrder() { - assumeNotWindows(); - assertThat(disablePlugins("-restart", "mandatory-depender", "dependee"), succeeded()); + public void canDisableDependentPluginsRightOrderStNone() { + assertThat(disablePluginsCLiCommand("mandatory-depender", "dependee"), succeeded()); assertPluginDisabled("dependee"); assertPluginDisabled("mandatory-depender"); - assertJenkinsInQuietMode(); } + /** + * Can disable a plugin without dependents plugins and Jenkins restart after it if -restart argument is passed. + */ @Test @Issue("JENKINS-27177") @WithPlugin("dependee-0.0.2.hpi") - public void disablePluginWithoutDepender() { + public void restartAfterDisable() { assumeNotWindows(); - assertThat(disablePlugins("-restart", "dependee"), succeeded()); + assertThat(disablePluginsCLiCommand("-restart", "dependee"), succeeded()); assertPluginDisabled("dependee"); assertJenkinsInQuietMode(); } + /** + * Can disable a plugin without dependents plugins and Jenkins doesn't restart after it if -restart is not passed. + */ @Test @Issue("JENKINS-27177") @WithPlugin("dependee-0.0.2.hpi") - public void disablePluginWithoutDependerNorRestart() { - assertThat(disablePlugins("dependee"), succeeded()); + public void notRestartAfterDisablePluginWithoutArgumentRestart() { + assertThat(disablePluginsCLiCommand("dependee"), succeeded()); assertPluginDisabled("dependee"); assertJenkinsNotInQuietMode(); } + /** + * A non-existing plugin returns with a {@link DisablePluginCommand#RETURN_CODE_NO_SUCH_PLUGIN} status code. + */ @Test @Issue("JENKINS-27177") @WithPlugin("dependee-0.0.2.hpi") - public void disableInvalidPlugin() { - assertThat(disablePlugins("-restart", "wrongname"), failedWith(3)); - assertJenkinsNotInQuietMode(); + public void returnCodeDisableInvalidPlugin() { + assertThat(disablePluginsCLiCommand("wrongname"), failedWith(RETURN_CODE_NO_SUCH_PLUGIN)); } + /** + * A plugin already disabled returns 0 and jenkins doesn't restart even though you passed the -restart argument. + * @throws IOException See {@link PluginWrapper#disable()}. + */ @Test @Issue("JENKINS-27177") @WithPlugin("dependee-0.0.2.hpi") - public void disableAlreadyDisabledPluginNoRestart() throws IOException { + public void disableAlreadyDisabledPluginNotRestart() throws IOException { + // Disable before the command call disablePlugin("dependee"); assertPluginDisabled("dependee"); - assertThat(disablePlugins("-restart", "dependee"), succeeded()); + assertThat(disablePluginsCLiCommand("-restart", "dependee"), succeeded()); assertPluginDisabled("dependee"); assertJenkinsNotInQuietMode(); } + /** + * If some plugins are disabled, Jenkins will restart even though the status code isn't 0 (is 16). + */ @Test @Issue("JENKINS-27177") @WithPlugin({"variant.hpi", "depender-0.0.2.hpi", "mandatory-depender-0.0.2.hpi", "plugin-first.hpi", "dependee-0.0.2.hpi", }) - public void disablePluginsWithError16() { + public void restartAfterDisablePluginsAndErrors() { assumeNotWindows(); - assertThat(disablePlugins("-restart", "variant", "dependee", "depender", "plugin-first", "mandatory-depender"), failedWith(16)); + assertThat(disablePluginsCLiCommand("-restart", "variant", "dependee", "depender", "plugin-first", "mandatory-depender"), failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); assertPluginDisabled("variant"); assertPluginEnabled("dependee"); assertPluginDisabled("depender"); @@ -138,6 +182,159 @@ public class DisablePluginCommandTest { assertJenkinsInQuietMode(); // some plugins were disabled } + /** + * All the dependant plugins, mandatory or optional, are disabled using -strategy all. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"variant.hpi", "depender-0.0.2.hpi", "mandatory-depender-0.0.2.hpi", "plugin-first.hpi", "dependee-0.0.2.hpi", }) + public void disablePluginsStrategyAll() { + assertPluginEnabled("dependee"); + assertPluginEnabled("depender"); + assertPluginEnabled("mandatory-depender"); + assertThat(disablePluginsCLiCommand("-strategy", "all", "variant", "dependee", "plugin-first"), succeeded()); + assertPluginDisabled("variant"); + assertPluginDisabled("dependee"); + assertPluginDisabled("depender"); + assertPluginDisabled("plugin-first"); + assertPluginDisabled("mandatory-depender"); + } + + /** + * Only the mandatory dependant plugins are disabled using -strategy mandatory. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"variant.hpi", "depender-0.0.2.hpi", "mandatory-depender-0.0.2.hpi", "plugin-first.hpi", "dependee-0.0.2.hpi", }) + public void disablePluginsStrategyMandatory() { + assertThat(disablePluginsCLiCommand("-strategy", "mandatory", "variant", "dependee", "plugin-first"), succeeded()); + assertPluginDisabled("variant"); + assertPluginDisabled("dependee"); + assertPluginEnabled("depender"); + assertPluginDisabled("plugin-first"); + assertPluginDisabled("mandatory-depender"); + } + + /** + * A plugin already disabled because it's a dependent plugin of one previously disabled appear two times in the log + * with different messages. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi", }) + public void disablePluginsMessageAlreadyDisabled() { + CLICommandInvoker.Result result = disablePluginsCLiCommand("-strategy", "all", "dependee", "depender"); + assertThat(result, succeeded()); + + assertPluginDisabled("dependee"); + assertPluginDisabled("depender"); + + assertTrue("An occurrence of the depender plugin in the log says it was successfully disabled", checkResultWith(result, StringUtils::contains, "depender", PluginWrapper.PluginDisableStatus.DISABLED)); + assertTrue("An occurrence of the depender plugin in the log says it was already disabled", checkResultWith(result, StringUtils::contains, "depender", PluginWrapper.PluginDisableStatus.ALREADY_DISABLED)); + } + + /** + * The return code is the first error distinct of 0 found during the process. In this case dependant plugins not + * disabled. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) + public void returnCodeFirstErrorIsDependants() { + CLICommandInvoker.Result result = disablePluginsCLiCommand("dependee", "badplugin"); + assertThat(result, failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); + + assertPluginEnabled("dependee"); + } + + /** + * The return code is the first error distinct of 0 found during the process. In this case no such plugin. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) + public void returnCodeFirstErrorIsNoSuchPlugin() { + CLICommandInvoker.Result result = disablePluginsCLiCommand("badplugin", "dependee"); + assertThat(result, failedWith(RETURN_CODE_NO_SUCH_PLUGIN)); + + assertPluginEnabled("dependee"); + } + + /** + * In quiet mode, no message is printed if all plugins are disabled or were already disabled. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) + public void quietModeEmptyOutputSucceed() { + CLICommandInvoker.Result result = disablePluginsCLiCommand("-strategy", "all", "-quiet", "dependee"); + assertThat(result, succeeded()); + + assertPluginDisabled("dependee"); + assertPluginDisabled("depender"); + assertPluginDisabled("mandatory-depender"); + + assertTrue("No log in quiet mode if all plugins disabled", StringUtils.isEmpty(result.stdout())); + } + + /** + * In quiet mode, only the errors (no such plugin) are printed. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) + public void quietModeWithErrorNoSuch() { + CLICommandInvoker.Result result = disablePluginsCLiCommand("-quiet", "-strategy", "all", "dependee", "badplugin"); + assertThat(result, failedWith(RETURN_CODE_NO_SUCH_PLUGIN)); + + assertPluginDisabled("dependee"); + assertPluginDisabled("depender"); + assertPluginDisabled("mandatory-depender"); + + assertTrue("Only error NO_SUCH_PLUGIN in quiet mode", checkResultWith(result, StringUtils::startsWith, "badplugin", PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN)); + } + + /** + * In quiet mode, only the errors (dependants plugins) are printed. + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) + public void quietModeWithErrorDependants() { + CLICommandInvoker.Result result = disablePluginsCLiCommand("-quiet", "-strategy", "none", "dependee"); + assertThat(result, failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); + + assertPluginEnabled("dependee"); + assertPluginEnabled("depender"); + assertPluginEnabled("mandatory-depender"); + + assertTrue("Only error NOT_DISABLED_DEPENDANTS in quiet mode", checkResultWith(result, StringUtils::startsWith, "dependee", PluginWrapper.PluginDisableStatus.NOT_DISABLED_DEPENDANTS)); + } + + /** + * Helper method to check the output of a result with a specific method allowing two arguments ( + * StringUtils::startsWith or StringUtils::contents). This method avoid to have it hardcoded the messages. We avoid + * having to compose the descriptive text of the message by using a stop flag to ignore the last characters. + * This method supposes that the descriptive text is at the last of the string. + * @param result the result of the command. + * @param method a method with two string arguments to check against + * @param plugin the plugin printed outSetting the plugin and status as parameters, the method gets + * the string printed using the + * @param status the status printed out + * @return true if the output has been checked against the method using the plugin and status args + */ + private boolean checkResultWith(CLICommandInvoker.Result result, BiPredicate method, String plugin, PluginWrapper.PluginDisableStatus status) { + String noMatterFollowingChars = "/!$stop"; + String outExpected = Messages.DisablePluginCommand_StatusMessage(plugin, status, noMatterFollowingChars); + outExpected = StringUtils.substringBefore(outExpected, noMatterFollowingChars); + return method.test(result.stdout(), outExpected); + } + + /** + * Disable a plugin using the {@link PluginWrapper#disable()} method. + * @param name the name of the plugin. + * @throws IOException if the disablement cannot be made. + */ private void disablePlugin(String name) throws IOException { PluginWrapper plugin = j.getPluginManager().getPlugin(name); assertThat(plugin, is(notNullValue())); @@ -149,7 +346,7 @@ public class DisablePluginCommandTest { * @param args Arguments to pass to the command. * @return Result of the command. 0 if succeed, 16 if some plugin couldn't be disabled due to dependant plugins. */ - private CLICommandInvoker.Result disablePlugins(String... args) { + private CLICommandInvoker.Result disablePluginsCLiCommand(String... args) { return new CLICommandInvoker(j, new DisablePluginCommand()).invokeWithArgs(args); } From a609502609bee8640a1261e618fea1bff76ac64b Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Mon, 15 Oct 2018 11:53:17 +0200 Subject: [PATCH 06/20] [JENKINS-27177] Minor fixes --- .../java/hudson/cli/DisablePluginCommand.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index 8e221c9e42..0b2d005e56 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -92,7 +92,7 @@ public class DisablePluginCommand extends CLICommand { // print results ... printResults(results); - // restart if it was required and it's necessary (some plugin was disabled in this execution) + // restart if it was required and it's necessary (at least one plugin was disabled in this execution) restartIfNecessary(results); // return the appropriate error code @@ -103,7 +103,7 @@ public class DisablePluginCommand extends CLICommand { * Print the result of all the process * @param results the list of results for the disablement of each plugin */ - private void printResults( List results) { + private void printResults(List results) { for (PluginWrapper.PluginDisableResult oneResult : results) { printResult(oneResult, 0); } @@ -121,8 +121,9 @@ public class DisablePluginCommand extends CLICommand { } else { String f = "%" + indent + "s" + format + "%n"; String[] newArgs = new String[arguments.length + 1]; - newArgs[0] = " "; int i = 1; - for (String arg: arguments ) { + newArgs[0] = " "; + int i = 1; + for (String arg: arguments) { newArgs[i] = arg; i++; } @@ -137,13 +138,13 @@ public class DisablePluginCommand extends CLICommand { */ private void printResult(PluginWrapper.PluginDisableResult oneResult, int indent) { PluginWrapper.PluginDisableStatus status = oneResult.getStatus(); - if(quiet && (PluginWrapper.PluginDisableStatus.DISABLED.equals(status) || PluginWrapper.PluginDisableStatus.ALREADY_DISABLED.equals(status))) { + if (quiet && (PluginWrapper.PluginDisableStatus.DISABLED.equals(status) || PluginWrapper.PluginDisableStatus.ALREADY_DISABLED.equals(status))) { return; } printIndented(indent, Messages.DisablePluginCommand_StatusMessage(oneResult.getPlugin(), oneResult.getStatus(), oneResult.getMessage())); - if(oneResult.getDependantsDisableStatus().size() > 0) { - indent = (indent == 0) ? INDENT_SPACE : indent*2; + if (oneResult.getDependantsDisableStatus().size() > 0) { + indent += INDENT_SPACE; for (PluginWrapper.PluginDisableResult oneDependantResult : oneResult.getDependantsDisableStatus()) { printResult(oneDependantResult, indent); } @@ -172,12 +173,12 @@ public class DisablePluginCommand extends CLICommand { */ private boolean restartIfNecessary(PluginWrapper.PluginDisableResult oneResult) throws RestartNotSupportedException { PluginWrapper.PluginDisableStatus status = oneResult.getStatus(); - if(PluginWrapper.PluginDisableStatus.DISABLED.equals(status)) { + if (PluginWrapper.PluginDisableStatus.DISABLED.equals(status)) { Jenkins.get().safeRestart(); return true; } - if(oneResult.getDependantsDisableStatus().size() > 0) { + if (oneResult.getDependantsDisableStatus().size() > 0) { for (PluginWrapper.PluginDisableResult oneDependantResult : oneResult.getDependantsDisableStatus()) { if (restartIfNecessary(oneDependantResult)) { return true; From 1aa7dbb818ed263af97e9dca7d765b9e18379335 Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Mon, 15 Oct 2018 11:56:27 +0200 Subject: [PATCH 07/20] [JENKINS-27177] Minor fixes --- core/src/main/java/hudson/PluginManager.java | 72 ++++++++++++++++--- core/src/main/java/hudson/PluginWrapper.java | 67 ++++++++++------- .../resources/hudson/cli/Messages.properties | 2 +- .../hudson/cli/Messages_es.properties | 2 +- .../hudson/cli/DisablePluginCommandTest.java | 10 +-- 5 files changed, 109 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index ce41655415..87575a44c2 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -29,16 +29,35 @@ import hudson.PluginWrapper.Dependency; import hudson.init.InitMilestone; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; -import hudson.model.*; +import hudson.model.AbstractItem; +import hudson.model.AbstractModelObject; +import hudson.model.AdministrativeMonitor; +import hudson.model.Api; +import hudson.model.Descriptor; +import hudson.model.DownloadService; +import hudson.model.Failure; +import hudson.model.ItemGroupMixIn; +import hudson.model.UpdateCenter; import hudson.model.UpdateCenter.DownloadJob; import hudson.model.UpdateCenter.InstallationJob; +import hudson.model.UpdateSite; import hudson.security.ACL; import hudson.security.ACLContext; import hudson.security.Permission; import hudson.security.PermissionScope; -import hudson.util.*; +import hudson.util.CyclicGraphDetector; import hudson.util.CyclicGraphDetector.CycleDetectedException; -import jenkins.*; +import hudson.util.FormValidation; +import hudson.util.PersistedList; +import hudson.util.Service; +import hudson.util.VersionNumber; +import hudson.util.XStream2; +import jenkins.ClassLoaderReflectionToolkit; +import jenkins.ExtensionRefreshException; +import jenkins.InitReactorRunner; +import jenkins.MissingDependencyException; +import jenkins.RestartRequiredException; +import jenkins.YesNoMaybe; import jenkins.install.InstallState; import jenkins.install.InstallUtil; import jenkins.model.Jenkins; @@ -60,12 +79,22 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.jenkinsci.Symbol; import org.jenkinsci.bytecode.Transformer; -import org.jvnet.hudson.reactor.*; +import org.jvnet.hudson.reactor.Executable; +import org.jvnet.hudson.reactor.Reactor; +import org.jvnet.hudson.reactor.ReactorException; +import org.jvnet.hudson.reactor.TaskBuilder; +import org.jvnet.hudson.reactor.TaskGraphBuilder; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.*; +import org.kohsuke.stapler.HttpRedirect; +import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerOverridable; +import org.kohsuke.stapler.StaplerProxy; +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; @@ -80,11 +109,34 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Method; -import java.net.*; -import java.util.*; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.util.ArrayList; +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.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -1789,10 +1841,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } /** - * Disable a list of plugins using a strategy for its dependants plugins. + * Disable a list of plugins using a strategy for their dependants plugins. * @param strategy the strategy regarding how the dependant plugins are processed * @param plugins the list of plugins - * @return the list of results for every plugin and its dependant plugins. + * @return the list of results for every plugin and their dependant plugins. * @throws IOException see {@link PluginWrapper#disable()} */ public List disablePlugins(PluginWrapper.PluginDisableStrategy strategy, List plugins) throws IOException { diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 28dccf1f6a..26fc870847 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -27,17 +27,16 @@ package hudson; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import hudson.PluginManager.PluginInstanceStore; -import hudson.cli.DisablePluginCommand; import hudson.model.AdministrativeMonitor; import hudson.model.Api; import hudson.model.ModelObject; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import jenkins.YesNoMaybe; -import jenkins.model.Jenkins; import hudson.model.UpdateCenter; import hudson.model.UpdateSite; import hudson.util.VersionNumber; +import jenkins.YesNoMaybe; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.LogFactory; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -49,8 +48,6 @@ import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.LogFactory; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.Closeable; @@ -58,7 +55,20 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URL; -import java.util.*; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.util.ArrayList; +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.Objects; +import java.util.Set; import java.util.function.Predicate; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -233,7 +243,6 @@ public class PluginWrapper implements Comparable, ModelObject { } /** - * Get the list of components that depend optionally on this plugin. * @return The list of components that depend optionally on this plugin. */ public @Nonnull Set getOptionalDependants() { @@ -571,8 +580,7 @@ public class PluginWrapper implements Comparable, ModelObject { * @throws IOException If this plugin or one dependant plugin raise this exception during its disablement. It could * be raised because the disable file cannot be created. */ - @Nonnull - public PluginDisableResult disable(@Nonnull PluginDisableStrategy strategy) throws IOException { + public @Nonnull PluginDisableResult disable(@Nonnull PluginDisableStrategy strategy) throws IOException { PluginDisableResult result = new PluginDisableResult(shortName); if (!this.isEnabled()) { @@ -587,21 +595,7 @@ public class PluginWrapper implements Comparable, ModelObject { // List of dependants plugins to 'check'. 'Check' means disable for mandatory or all strategies, or review if // this dependant-mandatory plugin is enabled in order to return an error for the NONE strategy. - Set dependantsToCheck; - switch (strategy) { - case MANDATORY: - // As of getDependants has all the dependants, we get the difference between them and only the optionals - dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); - break; - case ALL: - // getDependants returns all the dependant plugins, mandatory or optional. - dependantsToCheck = this.getDependants(); - break; - case NONE: - default: - // with NONE, the process only fail if mandatory dependant plugins exists - dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); - } + Set dependantsToCheck = dependantsToCheck(strategy); // Review all the dependants and add to the plugin result what happened with its dependants for (String dependant : dependantsToCheck) { @@ -653,6 +647,25 @@ public class PluginWrapper implements Comparable, ModelObject { return result; } + private Set dependantsToCheck(PluginDisableStrategy strategy) { + Set dependantsToCheck; + switch (strategy) { + case MANDATORY: + // As of getDependants has all the dependants, we get the difference between them and only the optionals + dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); + break; + case ALL: + // getDependants returns all the dependant plugins, mandatory or optional. + dependantsToCheck = this.getDependants(); + break; + case NONE: + default: + // with NONE, the process only fail if mandatory dependant plugins exists + dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); + } + return dependantsToCheck; + } + /** * Returns true if this plugin is enabled for this session. */ @@ -1011,7 +1024,7 @@ public class PluginWrapper implements Comparable, ModelObject { /** * An enum to hold the status of a disabling action against a plugin. To do it more reader-friendly. */ - public static enum PluginDisableStatus { + public enum PluginDisableStatus { NO_SUCH_PLUGIN, DISABLED, ALREADY_DISABLED, diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index f502ad9331..f8a6f487e1 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -119,4 +119,4 @@ Disable one or more installed plugins. DisablePluginCommand.NoSuchStrategy=This strategy ({0}) does not exist. Allowed strategies are {1} DisablePluginCommand.PrintUsageSummary=\ Disable the plugins with the given short names. You can define how to proceed with the dependant plugins and if a restart after should be done. You can also set the quiet mode to avoid extra info in the console. -DisablePluginCommand.StatusMessage=Disabling {0}: {1} ({2}) \ No newline at end of file +DisablePluginCommand.StatusMessage=Disabling {0}: {1} ({2}) diff --git a/core/src/main/resources/hudson/cli/Messages_es.properties b/core/src/main/resources/hudson/cli/Messages_es.properties index 7a7794b508..d8f7b4d7cc 100644 --- a/core/src/main/resources/hudson/cli/Messages_es.properties +++ b/core/src/main/resources/hudson/cli/Messages_es.properties @@ -64,4 +64,4 @@ Deshabilita uno o m DisablePluginCommand.NoSuchStrategy=Esta estrategia ({0}) no existe. Las estrategias permitidas son {1} DisablePluginCommand.PrintUsageSummary=\ Deshabilita los plugins con los nombre cortos dados. Puede definir cómo proceder con los plugins dependientes y si se realiza un reinicio posterior. -DisablePluginCommand.StatusMessage=Deshabilitando {0}: {1} ({2}) \ No newline at end of file +DisablePluginCommand.StatusMessage=Deshabilitando {0}: {1} ({2}) diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index aba2819963..11360eddb0 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -58,7 +58,7 @@ public class DisablePluginCommandTest { @Test @Issue("JENKINS-27177") @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void canDisablePluginWithOptionalDependerStNone() { + public void canDisablePluginWithOptionalDependerStrategyNone() { assertThat(disablePluginsCLiCommand("-strategy", "NONE", "dependee"), succeeded()); assertPluginDisabled("dependee"); } @@ -66,7 +66,7 @@ public class DisablePluginCommandTest { @Test @Issue("JENKINS-27177") @WithPlugin({"depender-0.0.2.hpi", "dependee-0.0.2.hpi", "mandatory-depender-0.0.2.hpi"}) - public void canDisablePluginWithDependantsDisabledStNone() throws IOException { + public void canDisablePluginWithDependantsDisabledStrategyNone() throws IOException { disablePlugin("mandatory-depender"); CLICommandInvoker.Result result = disablePluginsCLiCommand("-strategy", "NONE", "dependee"); @@ -82,7 +82,7 @@ public class DisablePluginCommandTest { @Test @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void cannotDisablePluginWithMandatoryDependerStNone() { + public void cannotDisablePluginWithMandatoryDependerStrategyNone() { assertThat(disablePluginsCLiCommand("dependee"), failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); assertPluginEnabled("dependee"); } @@ -94,7 +94,7 @@ public class DisablePluginCommandTest { @Test @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void cannotDisableDependentPluginWrongOrderStNone() { + public void cannotDisableDependentPluginWrongOrderStrategyNone() { assertThat(disablePluginsCLiCommand("dependee", "mandatory-depender"), failedWith(RETURN_CODE_NOT_DISABLED_DEPENDANTS)); assertPluginDisabled("mandatory-depender"); assertPluginEnabled("dependee"); @@ -107,7 +107,7 @@ public class DisablePluginCommandTest { @Test @Issue("JENKINS-27177") @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) - public void canDisableDependentPluginsRightOrderStNone() { + public void canDisableDependentPluginsRightOrderStrategyNone() { assertThat(disablePluginsCLiCommand("mandatory-depender", "dependee"), succeeded()); assertPluginDisabled("dependee"); assertPluginDisabled("mandatory-depender"); From 8f239f6ee18085fe445a3c265e8c6e2bdfe197c6 Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Tue, 16 Oct 2018 11:47:23 +0200 Subject: [PATCH 08/20] [JENKINS-27177] Add new status error_disabling and minor fixes When there is some problem disabling a plugin (the disabling file cannot be created) it should be indicated and also reflected in the dependee plugin. --- core/src/main/java/hudson/PluginManager.java | 2 +- core/src/main/java/hudson/PluginWrapper.java | 62 ++++++++++++------- .../main/resources/hudson/Messages.properties | 2 +- .../hudson/cli/DisablePluginCommandTest.java | 2 +- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 87575a44c2..c83e38bad6 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1847,7 +1847,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * @return the list of results for every plugin and their dependant plugins. * @throws IOException see {@link PluginWrapper#disable()} */ - public List disablePlugins(PluginWrapper.PluginDisableStrategy strategy, List plugins) throws IOException { + public @NonNull List disablePlugins(PluginWrapper.PluginDisableStrategy strategy, List plugins) throws IOException { // Where we store the results of each plugin disablement List results = new ArrayList<>(plugins.size()); diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 26fc870847..818b91d289 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -76,6 +76,11 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import static hudson.PluginWrapper.PluginDisableStatus.ALREADY_DISABLED; +import static hudson.PluginWrapper.PluginDisableStatus.DISABLED; +import static hudson.PluginWrapper.PluginDisableStatus.ERROR_DISABLING; +import static hudson.PluginWrapper.PluginDisableStatus.NOT_DISABLED_DEPENDANTS; +import static hudson.PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN; import static java.util.logging.Level.WARNING; import static org.apache.commons.io.FilenameUtils.getBaseName; @@ -577,15 +582,13 @@ public class PluginWrapper implements Comparable, ModelObject { * Disable this plugin using a strategy. * @param strategy strategy to use * @return an object representing the result of the disablement of this plugin and its dependants plugins. - * @throws IOException If this plugin or one dependant plugin raise this exception during its disablement. It could - * be raised because the disable file cannot be created. */ - public @Nonnull PluginDisableResult disable(@Nonnull PluginDisableStrategy strategy) throws IOException { + public @Nonnull PluginDisableResult disable(@Nonnull PluginDisableStrategy strategy) { PluginDisableResult result = new PluginDisableResult(shortName); if (!this.isEnabled()) { result.setMessage(Messages.PluginWrapper_Already_Disabled(shortName)); - result.setStatus(PluginDisableStatus.ALREADY_DISABLED); + result.setStatus(ALREADY_DISABLED); return result; } @@ -603,9 +606,7 @@ public class PluginWrapper implements Comparable, ModelObject { // The dependant plugin doesn't exist, add an error to the report if (dependantPlugin == null) { - PluginDisableResult dependantStatus = new PluginDisableResult(dependant); - dependantStatus.setStatus(PluginDisableStatus.NO_SUCH_PLUGIN); - dependantStatus.setMessage(Messages.PluginWrapper_NoSuchPlugin(dependant)); + PluginDisableResult dependantStatus = new PluginDisableResult(dependant, NO_SUCH_PLUGIN, Messages.PluginWrapper_NoSuchPlugin(dependant)); result.addDependantDisableStatus(dependantStatus); // If the strategy is none and there is some enabled dependant plugin, the plugin cannot be disabled. If @@ -618,30 +619,41 @@ public class PluginWrapper implements Comparable, ModelObject { // If the strategy is not none and this dependant plugin is not enabled, add it as already disabled } else if (!dependantPlugin.isEnabled()) { - PluginDisableResult dependantStatus = new PluginDisableResult(dependant); - dependantStatus.setStatus(PluginDisableStatus.ALREADY_DISABLED); - dependantStatus.setMessage(Messages.PluginWrapper_Already_Disabled(dependant)); + PluginDisableResult dependantStatus = new PluginDisableResult(dependant, ALREADY_DISABLED, Messages.PluginWrapper_Already_Disabled(dependant)); result.addDependantDisableStatus(dependantStatus); // If the strategy is not none and this dependant plugin is enabled, disable it } else { // As there is no cycles in the plugin dependencies, the recursion shouldn't be infinite. The // strategy used is the same for its dependants plugins - result.addDependantDisableStatus(dependantPlugin.disable(strategy)); + PluginDisableResult dependantResult = dependantPlugin.disable(strategy); + PluginDisableStatus dependantStatus = dependantResult.status; + + // If something wrong happened, flag this dependant plugin to set the plugin later as not-disabled due + // to its dependants plugins. + if (ERROR_DISABLING.equals(dependantStatus) || NOT_DISABLED_DEPENDANTS.equals(dependantStatus)) { + aDependantNotDisabled = dependant; + break; // we found a dependant plugin enabled, stop looking for dependant plugins to disable. + } + result.addDependantDisableStatus(dependantResult); } } // If there is no enabled-dependant plugin, disable this plugin and add it to the result if (aDependantNotDisabled == null) { - this.disableWithoutCheck(); - result.setMessage(Messages.PluginWrapper_Plugin_Disabled(shortName)); - result.setStatus(PluginDisableStatus.DISABLED); - + try { + this.disableWithoutCheck(); + result.setMessage(Messages.PluginWrapper_Plugin_Disabled(shortName)); + result.setStatus(DISABLED); + } catch (IOException io) { + result.setMessage(Messages.PluginWrapper_Error_Disabling(shortName, io.toString())); + result.setStatus(ERROR_DISABLING); + } // if there is yet some not disabled dependant plugin (only possible with none strategy), this plugin cannot // be disabled. } else { result.setMessage(Messages.PluginWrapper_Plugin_Has_Dependant(shortName, aDependantNotDisabled, strategy)); - result.setStatus(PluginDisableStatus.NOT_DISABLED_DEPENDANTS); + result.setStatus(NOT_DISABLED_DEPENDANTS); } return result; @@ -650,17 +662,14 @@ public class PluginWrapper implements Comparable, ModelObject { private Set dependantsToCheck(PluginDisableStrategy strategy) { Set dependantsToCheck; switch (strategy) { - case MANDATORY: - // As of getDependants has all the dependants, we get the difference between them and only the optionals - dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); - break; case ALL: // getDependants returns all the dependant plugins, mandatory or optional. dependantsToCheck = this.getDependants(); break; - case NONE: default: + // It includes MANDATORY, NONE: // with NONE, the process only fail if mandatory dependant plugins exists + // As of getDependants has all the dependants, we get the difference between them and only the optionals dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); } return dependantsToCheck; @@ -978,6 +987,12 @@ public class PluginWrapper implements Comparable, ModelObject { this.plugin = plugin; } + public PluginDisableResult(String plugin, PluginDisableStatus status, String message) { + this.plugin = plugin; + this.status = status; + this.message = message; + } + public String getPlugin() { return plugin; } @@ -1028,13 +1043,14 @@ public class PluginWrapper implements Comparable, ModelObject { NO_SUCH_PLUGIN, DISABLED, ALREADY_DISABLED, - NOT_DISABLED_DEPENDANTS + NOT_DISABLED_DEPENDANTS, + ERROR_DISABLING } /** * The strategies defined for disabling a plugin. */ - public static enum PluginDisableStrategy { + public enum PluginDisableStrategy { NONE, MANDATORY, ALL; diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 78b8e37f83..cad3dd11ee 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -87,6 +87,6 @@ PluginWrapper.Already.Disabled=The plugin {0} was already disabled PluginWrapper.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}) and the disable strategy is {2}, so it cannot be disabled PluginWrapper.Plugin.Disabled=Plugin {0} disabled PluginWrapper.NoSuchPlugin=No such plugin found with the name {0} - +PluginWrapper.Error.Disabling=There was an error disabling the {0} plugin. Error: {1} TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index 11360eddb0..f6f5f60807 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -179,7 +179,7 @@ public class DisablePluginCommandTest { assertPluginDisabled("depender"); assertPluginDisabled("plugin-first"); assertPluginDisabled("mandatory-depender"); - assertJenkinsInQuietMode(); // some plugins were disabled + assertJenkinsInQuietMode(); // some plugins were disabled, so it should be restarting } /** From 65faa1e6d82580c66cfba8672bfd115936c3750d Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Wed, 17 Oct 2018 18:31:45 +0200 Subject: [PATCH 09/20] [JENKINS-27177] Minor fixes. Change the way of an array copy and ensure non null args --- core/src/main/java/hudson/PluginManager.java | 2 +- .../src/main/java/hudson/cli/DisablePluginCommand.java | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c83e38bad6..9659ceb2c4 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1847,7 +1847,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * @return the list of results for every plugin and their dependant plugins. * @throws IOException see {@link PluginWrapper#disable()} */ - public @NonNull List disablePlugins(PluginWrapper.PluginDisableStrategy strategy, List plugins) throws IOException { + public @NonNull List disablePlugins(@NonNull PluginWrapper.PluginDisableStrategy strategy, @NonNull List plugins) throws IOException { // Where we store the results of each plugin disablement List results = new ArrayList<>(plugins.size()); diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index 0b2d005e56..168b2455f8 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -119,14 +119,10 @@ public class DisablePluginCommand extends CLICommand { if (indent == 0) { stdout.format(format + "%n", (Object[]) arguments); } else { - String f = "%" + indent + "s" + format + "%n"; String[] newArgs = new String[arguments.length + 1]; - newArgs[0] = " "; - int i = 1; - for (String arg: arguments) { - newArgs[i] = arg; - i++; - } + System.arraycopy(arguments, 0, newArgs, 1, arguments.length); + + String f = "%" + indent + "s" + format + "%n"; stdout.format(f, newArgs); } } From 8bc71b718137e8eb808dfb974e70ba590e034e36 Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Thu, 18 Oct 2018 11:11:10 +0200 Subject: [PATCH 10/20] [JENKINS-27177] Improve messages --- core/src/main/java/hudson/PluginManager.java | 5 +---- .../src/main/java/hudson/cli/DisablePluginCommand.java | 1 + core/src/main/resources/hudson/Messages.properties | 10 +++++----- core/src/main/resources/hudson/cli/Messages.properties | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 9659ceb2c4..eccf9337d7 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1856,10 +1856,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas PluginWrapper plugin = this.getPlugin(pluginName); if (plugin == null) { - PluginWrapper.PluginDisableResult result = new PluginWrapper.PluginDisableResult(pluginName); - result.setMessage(Messages.PluginWrapper_NoSuchPlugin(pluginName)); - result.setStatus(PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN); - results.add(result); + results.add(new PluginWrapper.PluginDisableResult(pluginName, PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN, Messages.PluginWrapper_NoSuchPlugin(pluginName))); } else { results.add(plugin.disable(strategy)); } diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index 168b2455f8..36d4b6bd8c 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -120,6 +120,7 @@ public class DisablePluginCommand extends CLICommand { stdout.format(format + "%n", (Object[]) arguments); } else { String[] newArgs = new String[arguments.length + 1]; + newArgs[0] = " "; System.arraycopy(arguments, 0, newArgs, 1, arguments.length); String f = "%" + indent + "s" + format + "%n"; diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index cad3dd11ee..57fa82605f 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -83,10 +83,10 @@ 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. PluginWrapper.PluginWrapperAdministrativeMonitor.DisplayName=Plugins Failed To Load -PluginWrapper.Already.Disabled=The plugin {0} was already disabled -PluginWrapper.Plugin.Has.Dependant=The plugin {0} has, at least, one dependant plugin ({1}) and the disable strategy is {2}, so it cannot be disabled -PluginWrapper.Plugin.Disabled=Plugin {0} disabled -PluginWrapper.NoSuchPlugin=No such plugin found with the name {0} -PluginWrapper.Error.Disabling=There was an error disabling the {0} plugin. Error: {1} +PluginWrapper.Already.Disabled=The plugin ''{0}'' was already disabled +PluginWrapper.Plugin.Has.Dependant=The plugin ''{0}'' has, at least, one dependant plugin ({1}) and the disable strategy is {2}, so it cannot be disabled +PluginWrapper.Plugin.Disabled=Plugin ''{0}'' disabled +PluginWrapper.NoSuchPlugin=No such plugin found with the name ''{0}'' +PluginWrapper.Error.Disabling=There was an error disabling the ''{0}'' plugin. Error: ''{1}'' TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index f8a6f487e1..85bbf56b6d 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -119,4 +119,4 @@ Disable one or more installed plugins. DisablePluginCommand.NoSuchStrategy=This strategy ({0}) does not exist. Allowed strategies are {1} DisablePluginCommand.PrintUsageSummary=\ Disable the plugins with the given short names. You can define how to proceed with the dependant plugins and if a restart after should be done. You can also set the quiet mode to avoid extra info in the console. -DisablePluginCommand.StatusMessage=Disabling {0}: {1} ({2}) +DisablePluginCommand.StatusMessage=Disabling ''{0}'': {1} ({2}) From ed1dc0c92a0b5ef3906c438c5d3738b869e032cb Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Thu, 18 Oct 2018 11:20:06 +0200 Subject: [PATCH 11/20] [JENKINS-27177] Specific test to demo right disablement with wrong order --- .../java/hudson/cli/DisablePluginCommandTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index f6f5f60807..8b96dbcfa1 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -100,6 +100,18 @@ public class DisablePluginCommandTest { assertPluginEnabled("dependee"); } + /** + * Can disable a plugin with a mandatory dependent plugin before its dependent plugin with strategy + */ + @Test + @Issue("JENKINS-27177") + @WithPlugin({"mandatory-depender-0.0.2.hpi", "dependee-0.0.2.hpi"}) + public void canDisableDependentPluginWrongOrderStrategyAll() { + assertThat(disablePluginsCLiCommand("dependee", "mandatory-depender", "-strategy", "all"), succeeded()); + assertPluginDisabled("mandatory-depender"); + assertPluginDisabled("dependee"); + } + /** * Can disable a plugin with a mandatory dependent plugin after being disabled the mandatory dependent plugin. With * default strategy (none). From 8cbfa5eb7a8ac92e9837e2ae418f64f137e0543e Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Mon, 22 Oct 2018 14:22:06 +0200 Subject: [PATCH 12/20] [JENKINS-49196] Shutdown in a new thread to avoid PrematureEOF Add a new thread to exit Jenkins for the shutdown cli command to avoid the Premature EOF IOException --- core/src/main/java/jenkins/model/Jenkins.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index f6a3d8386f..99c5d25e94 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -4266,18 +4266,29 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve @RequirePOST public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException { checkPermission(ADMINISTER); - LOGGER.severe(String.format("Shutting down VM as requested by %s from %s", - getAuthentication().getName(), req!=null?req.getRemoteAddr():"???")); - if (rsp!=null) { - rsp.setStatus(HttpServletResponse.SC_OK); - rsp.setContentType("text/plain"); - try (PrintWriter w = rsp.getWriter()) { - w.println("Shutting down"); - } - } + new Thread("exit thread") { + @Override + public void run() { + try { + ACL.impersonate(ACL.SYSTEM); + LOGGER.severe(String.format("Shutting down VM as requested by %s from %s", + getAuthentication().getName(), req!=null?req.getRemoteAddr():"???")); + if (rsp!=null) { + rsp.setStatus(HttpServletResponse.SC_OK); + rsp.setContentType("text/plain"); + try (PrintWriter w = rsp.getWriter()) { + w.println("Shutting down"); + } + } - cleanUp(); - System.exit(0); + cleanUp(); + System.exit(0); + + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Failed to shut down Jenkins", e); + } + } + }.start(); } From 4f1cec32a1c64a1dde9db3b67968150e7353528c Mon Sep 17 00:00:00 2001 From: Ramon Leon Date: Mon, 22 Oct 2018 16:35:43 +0200 Subject: [PATCH 13/20] [JENKINS-49196] Shutdown in a new thread to avoid PrematureEOF Add a new thread to exit Jenkins for the shutdown cli command to avoid the Premature EOF IOException --- core/src/main/java/jenkins/model/Jenkins.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 001e649987..9eb230016f 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -4283,8 +4283,22 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve @RequirePOST public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException { checkPermission(ADMINISTER); - LOGGER.info(String.format("Shutting down VM as requested by %s from %s", - getAuthentication().getName(), req!=null?req.getRemoteAddr():"???")); + new Thread("exit thread") { + @Override + public void run() { + try { + ACL.impersonate(ACL.SYSTEM); + LOGGER.info(String.format("Shutting down VM as requested by %s from %s", + getAuthentication().getName(), req != null ? req.getRemoteAddr() : "???")); + + cleanUp(); + System.exit(0); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Failed to shut down Jenkins", e); + } + } + }.start(); + if (rsp!=null) { rsp.setStatus(HttpServletResponse.SC_OK); rsp.setContentType("text/plain"); @@ -4292,12 +4306,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve w.println("Shutting down"); } } - - cleanUp(); - System.exit(0); } - /** * Shutdown the system safely. * @since 1.332 From 363e0ea433d651274ce75b808331c7f6f7b45c89 Mon Sep 17 00:00:00 2001 From: M Ramon Leon Date: Tue, 23 Oct 2018 12:16:34 +0200 Subject: [PATCH 14/20] [JENKINS-49196] Response first just in case the thread ends before It's not necessary because the clean up last a while, but just in case --- core/src/main/java/jenkins/model/Jenkins.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 9eb230016f..caa0821e6c 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -4283,6 +4283,14 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve @RequirePOST public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException { checkPermission(ADMINISTER); + if (rsp!=null) { + rsp.setStatus(HttpServletResponse.SC_OK); + rsp.setContentType("text/plain"); + try (PrintWriter w = rsp.getWriter()) { + w.println("Shutting down"); + } + } + new Thread("exit thread") { @Override public void run() { @@ -4298,14 +4306,6 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } } }.start(); - - if (rsp!=null) { - rsp.setStatus(HttpServletResponse.SC_OK); - rsp.setContentType("text/plain"); - try (PrintWriter w = rsp.getWriter()) { - w.println("Shutting down"); - } - } } /** From 498114e872b9404fa038ac1155909b388e07b597 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Nov 2018 16:07:08 -0500 Subject: [PATCH 15/20] Work around SUREFIRE-1588. --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 7f08f9b914..fb587c3640 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -254,7 +254,7 @@ THE SOFTWARE. maven-surefire-plugin - ${jacocoSurefireArgs} -Dfile.encoding=UTF-8 -Xmx1g + ${jacocoSurefireArgs} -Dfile.encoding=UTF-8 -Xmx1g -Djdk.net.URLClassPath.disableClassPathURLCheck=true true From a555dc07966a6a142e7f1c098719d8caf8aacd4c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 7 Nov 2018 16:44:48 -0500 Subject: [PATCH 16/20] windows-slave-installer 1.9.3 --- war/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/pom.xml b/war/pom.xml index 1ab587db93..7cd7dad40e 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -110,7 +110,7 @@ THE SOFTWARE. org.jenkins-ci.modules windows-slave-installer - 1.9.2 + 1.9.3 org.jenkins-ci.modules From 1cada2c679f09e6f1fa29514b01838971f6ed214 Mon Sep 17 00:00:00 2001 From: Zhao Xiaojie Date: Sat, 10 Nov 2018 13:40:24 +0800 Subject: [PATCH 17/20] Add languages plugin category for setupWizard (#3626) * Add languages plugin category for setupWizard * Dynamic choose localization plugin according user-agent settings. * Retrigger CI --- .../main/resources/jenkins/install/platform-plugins.json | 7 +++++++ war/src/main/js/api/pluginManager.js | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/core/src/main/resources/jenkins/install/platform-plugins.json b/core/src/main/resources/jenkins/install/platform-plugins.json index 5cd0067fb5..6228138059 100644 --- a/core/src/main/resources/jenkins/install/platform-plugins.json +++ b/core/src/main/resources/jenkins/install/platform-plugins.json @@ -101,5 +101,12 @@ { "name": "publish-over-ssh" }, { "name": "ssh" } ] + }, + { + "category":"Languages", + "plugins": [ + { "name": "locale"}, + { "name": "localization-zh-cn"} + ] } ] diff --git a/war/src/main/js/api/pluginManager.js b/war/src/main/js/api/pluginManager.js index 923c2c1ddb..8032828e5e 100644 --- a/war/src/main/js/api/pluginManager.js +++ b/war/src/main/js/api/pluginManager.js @@ -40,6 +40,12 @@ exports.init = function(handler) { plugins.names.push(pluginName); if (plugin.suggested) { plugins.recommendedPlugins.push(pluginName); + } else if (pluginCategory.category === "Languages") { + var language = window.navigator.userLanguage || window.navigator.language; + var code = language.toLocaleLowerCase(); + if (pluginName === ("localization-" + code)) { + plugins.recommendedPlugins.push(pluginName); + } } } } From 8b8bcea0d07cabceba8777613fe9d0502faa3f09 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 11 Nov 2018 11:23:13 -0800 Subject: [PATCH 18/20] [maven-release-plugin] prepare release jenkins-2.151 --- 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 670e416a26..c1ba9a56e7 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.151 cli diff --git a/core/pom.xml b/core/pom.xml index cbbf3f3061..62433108a7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.151 jenkins-core diff --git a/pom.xml b/pom.xml index 0ab3c86021..ed7e63cb2a 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.151 pom Jenkins main module @@ -59,7 +59,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 - ${scmTag} + jenkins-2.151 diff --git a/test/pom.xml b/test/pom.xml index fb587c3640..b4e10ccb64 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.151 jenkins-test diff --git a/war/pom.xml b/war/pom.xml index 7cd7dad40e..3982462359 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.151 jenkins-war From e3af0f3bb1d2f80a672e85e31d2dd63157033c16 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 11 Nov 2018 11:23:21 -0800 Subject: [PATCH 19/20] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 6 +++--- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index c1ba9a56e7..670e416a26 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - 2.151 + ${revision}${changelist} cli diff --git a/core/pom.xml b/core/pom.xml index 62433108a7..cbbf3f3061 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.151 + ${revision}${changelist} jenkins-core diff --git a/pom.xml b/pom.xml index ed7e63cb2a..47c16272a8 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.151 + ${revision}${changelist} pom Jenkins main module @@ -59,7 +59,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.151 + ${scmTag} @@ -75,7 +75,7 @@ THE SOFTWARE. - 2.151 + 2.152 -SNAPSHOT