diff --git a/changelog.html b/changelog.html
index de603e226e..22594442e8 100644
--- a/changelog.html
+++ b/changelog.html
@@ -55,7 +55,9 @@ Upcoming changes
- -
+
-
+ Plugin manager now supports uninstallation.
+ (issue 3070)
diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java
index 27d1b174a8..3e61e16660 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -26,6 +26,7 @@ package hudson;
import hudson.PluginManager.PluginInstanceStore;
import hudson.model.Api;
+import hudson.model.ModelObject;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import hudson.model.UpdateCenter;
@@ -49,6 +50,7 @@ import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
+import org.kohsuke.stapler.interceptor.RequirePOST;
import java.util.Enumeration;
import java.util.jar.JarFile;
@@ -76,7 +78,7 @@ import java.util.jar.JarFile;
* @author Kohsuke Kawaguchi
*/
@ExportedBean
-public class PluginWrapper implements Comparable {
+public class PluginWrapper implements Comparable, ModelObject {
/**
* {@link PluginManager} to which this belongs to.
*/
@@ -116,6 +118,11 @@ public class PluginWrapper implements Comparable {
*/
private final File pinFile;
+ /**
+ * A .jpi file, an exploded plugin directory, or a .jpl file.
+ */
+ private final File archive;
+
/**
* Short name of the plugin. The artifact Id of the plugin.
* This is also used in the URL within Jenkins, so it needs
@@ -200,6 +207,11 @@ public class PluginWrapper implements Comparable {
this.active = !disableFile.exists();
this.dependencies = dependencies;
this.optionalDependencies = optionalDependencies;
+ this.archive = archive;
+ }
+
+ public String getDisplayName() {
+ return getLongName();
}
public Api getApi() {
@@ -513,6 +525,16 @@ public class PluginWrapper implements Comparable {
return pinFile.exists();
}
+ /**
+ * Returns true if this plugin is deleted.
+ *
+ * The plugin continues to function in this session, but in the next session it'll disappear.
+ */
+ @Exported
+ public boolean isDeleted() {
+ return !archive.exists();
+ }
+
/**
* Sort by short name.
*/
@@ -559,30 +581,41 @@ public class PluginWrapper implements Comparable {
// Action methods
//
//
+ @RequirePOST
public HttpResponse doMakeEnabled() throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
enable();
return HttpResponses.ok();
}
+ @RequirePOST
public HttpResponse doMakeDisabled() throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
disable();
return HttpResponses.ok();
}
+ @RequirePOST
public HttpResponse doPin() throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
new FileOutputStream(pinFile).close();
return HttpResponses.ok();
}
+ @RequirePOST
public HttpResponse doUnpin() throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
pinFile.delete();
return HttpResponses.ok();
}
+ @RequirePOST
+ public HttpResponse doDoUninstall() throws IOException {
+ Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
+ archive.delete();
+ return HttpResponses.redirectViaContextPath("/pluginManager/installed"); // send back to plugin manager
+ }
+
private static final Logger LOGGER = Logger.getLogger(PluginWrapper.class.getName());
diff --git a/core/src/main/resources/hudson/PluginManager/installed.jelly b/core/src/main/resources/hudson/PluginManager/installed.jelly
index b8934ad904..0e6e53b5d6 100644
--- a/core/src/main/resources/hudson/PluginManager/installed.jelly
+++ b/core/src/main/resources/hudson/PluginManager/installed.jelly
@@ -42,9 +42,10 @@ THE SOFTWARE.
| ${%Enabled} |
${%Name} |
- ${%Version} |
- ${%Previously installed version} |
- ${%Pinned} |
+ ${%Version} |
+ ${%Previously installed version} |
+ ${%Pinned} |
+ ${%Uninstall} |
@@ -85,6 +86,18 @@ THE SOFTWARE.
+
+
+
+ ${%Uninstalltion pending}
+
+
+
+
+
+ |
@@ -121,6 +134,7 @@ THE SOFTWARE.
new Ajax.Request(btn.getAttribute('url')+"/make"+(btn.checked?"Enable":"Disable")+"d", {
+ method: "POST",
onFailure : function(req,o) {
$('needRestart').innerHTML = req.responseText;
}
@@ -145,6 +159,7 @@ THE SOFTWARE.
function unpin(button,shortName) {
new Ajax.Request("./plugin/"+shortName+"/unpin", {
+ method: "POST",
onFailure : function(t) {
alert('Failed to unpin:'+t.responseText);
},
diff --git a/core/src/main/resources/hudson/PluginWrapper/uninstall.groovy b/core/src/main/resources/hudson/PluginWrapper/uninstall.groovy
new file mode 100644
index 0000000000..182283a71f
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginWrapper/uninstall.groovy
@@ -0,0 +1,20 @@
+package hudson.PluginWrapper
+
+def l = namespace(lib.LayoutTagLib)
+def f = namespace(lib.FormTagLib)
+
+l.layout {
+ def title = "Uninstalling ${my.shortName} plugin"
+ l.header(title:title)
+ l.main_panel {
+ h1 {
+ img(src:"${imagesURL}/48x48/error.png",alt:"[!]",height:48,width:48)
+ text(" ")
+ text(title)
+ }
+ p { raw _("msg",my.shortName) }
+ f.form(method:"post",action:"doUninstall") {
+ f.submit(value:_("Yes"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/PluginWrapper/uninstall.properties b/core/src/main/resources/hudson/PluginWrapper/uninstall.properties
new file mode 100644
index 0000000000..fa00f953d6
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginWrapper/uninstall.properties
@@ -0,0 +1,2 @@
+msg=You are about to uninstall the {0} plugin. This will remove the plugin binary from your $JENKINS_HOME, \
+ but it'll the configuration files of the plugin.
\ No newline at end of file