提交 8ec3054c 编写于 作者: K kohsuke

Added downgrade support for the core and plugins.

```-----
Merged revisions 32975,33588,33697,34342 via svnmerge from 
https://www.dev.java.net/svn/hudson/branches/downgrade
```

.....
  r32975 | kohsuke | 2010-07-16 21:27:59 +0200 (Fri, 16 Jul 2010) | 1 line
  
  committed my partially modified version
........
  r33588 | dienomight | 2010-08-05 09:42:36 +0200 (Thu, 05 Aug 2010) | 13 lines
  
  Added getBackupVersion (UpdateCenter)
  downgrade button now shows version of backup war file
  
  Added HudsonDowngradeJob (UpdateCenter)
  downgrading process is works now as HudsonUpgradeJob
  
  Changed rewriteHudsonWar (Lifecycle) 
  backup file is deleted when downgrading
  
  hudson/model/Hudson/downgrade_success.jelly is no longer necessary
  
  Changed rewriteHudsonWar(WindowsServiceLifecycle)
  hudson run as windows service creates backup during upgrading
........
  r33697 | dienomight | 2010-08-11 17:09:48 +0200 (Wed, 11 Aug 2010) | 8 lines
  
  committed changes:
  "plugins downgrade functionality"
  added: 
  * PluginDowngradeJob in UpdateCenter
  * getCanDowngrade & getBackupPath & getBackupVersion in PluginWrapper
  * additional "previous version" column in installed.jelly (PluginManager) with downgrade button (when it's possible to downgrade)
  * doDowngrade in PluginManager
  * deployBackup in UpdateSite
........
  r34342 | dienomight | 2010-09-03 09:52:13 +0200 (Fri, 03 Sep 2010) | 12 lines
  
  Code improvements: 
  jelly files:
  *I put text inside ${%} so it would be available for translation.
  UpdateCenter: 
  *changed getCanDowngrade to isDowngradable
  *getBackupVersion use JarFile class to get version of .war backup
  PluginWrapper:
  *changed getBackupPath to getBackupFile
  *getBackupVersion use JarFile class to get version of plugin backup
  PluginManager:
  *changed condition in doDowngrade method
........


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@34543 71c3de6d-444a-0410-be80-ed276b4c234a
上级 cba29b60
......@@ -494,6 +494,7 @@ public abstract class PluginManager extends AbstractModelObject {
}
rsp.sendRedirect("../updateCenter/");
}
/**
* Bare-minimum configuration mechanism to change the update center.
......
......@@ -37,7 +37,6 @@ import java.io.OutputStream;
import java.io.Closeable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import java.util.logging.Logger;
......@@ -47,6 +46,9 @@ import org.apache.commons.logging.LogFactory;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import java.util.Enumeration;
import java.util.jar.JarFile;
/**
* Represents a Hudson plug-in and associated control information
* for Hudson to control {@link Plugin}.
......@@ -464,6 +466,37 @@ public class PluginWrapper implements Comparable<PluginWrapper> {
return shortName.compareToIgnoreCase(pw.shortName);
}
/**
* returns true if backup of previous version of plugin exists
*/
public boolean isDowngradable() {
return getBackupFile().exists();
}
/**
* Where is the backup file?
*/
public File getBackupFile() {
return new File(Hudson.getInstance().getRootDir(),"plugins/"+getShortName() + ".bak");
}
/**
* returns the version of the backed up plugin,
* or null if there's no back up.
*/
public String getBackupVersion() {
if (getBackupFile().exists()) {
try {
JarFile backupPlugin = new JarFile(getBackupFile());
return backupPlugin.getManifest().getMainAttributes().getValue("Plugin-Version");
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to get backup version ", e);
return null;
}
} else {
return null;
}
}
//
//
// Action methods
......
......@@ -139,7 +139,17 @@ public abstract class Lifecycle implements ExtensionPoint {
// but let's be defensive
if(dest==null) throw new IOException("hudson.war location is not known.");
// backing up the old hudson.war before it gets lost due to upgrading
// (newly downloaded hudson.war and 'backup' (hudson.war.tmp) are the same files
// unless we are trying to rewrite hudson.war by a backup itself
File bak = new File(dest.getPath() + ".bak");
if (!by.equals(bak))
FileUtils.copyFile(dest, bak);
FileUtils.copyFile(by, dest);
// we don't want to keep backup if we are downgrading
if (by.equals(bak)&&bak.exists())
bak.delete();
}
/**
......
......@@ -87,6 +87,17 @@ public class WindowsServiceLifecycle extends Lifecycle {
*/
@Override
public void rewriteHudsonWar(File by) throws IOException {
File dest = getHudsonWar();
// this should be impossible given the canRewriteHudsonWar method,
// but let's be defensive
if(dest==null) throw new IOException("hudson.war location is not known.");
// backing up the old hudson.war before its lost due to upgrading
// unless we are trying to rewrite hudson.war by a backup itself
File bak = new File(dest.getPath() + ".bak");
if (!by.equals(bak))
FileUtils.copyFile(dest, bak);
File rootDir = Hudson.getInstance().getRootDir();
File copyFiles = new File(rootDir,"hudson.copies");
......
......@@ -70,10 +70,12 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.acegisecurity.context.SecurityContextHolder;
/**
* Controls update center capability.
*
......@@ -282,6 +284,45 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
rsp.sendRedirect2(".");
}
/**
* Returns true if backup of hudson.war exists on the hard drive
*/
public boolean isDowngradable() {
return new File(Lifecycle.get().getHudsonWar() + ".bak").exists();
}
/**
* Performs hudson downgrade.
*/
public void doDowngrade(StaplerResponse rsp) throws IOException, ServletException {
requirePOST();
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
if(!isDowngradable()) {
sendError("Hudson downgrade is not possible, probably backup does not exist");
return;
}
HudsonDowngradeJob job = new HudsonDowngradeJob(getCoreSource(), Hudson.getAuthentication());
LOGGER.info("Scheduling the core downgrade");
addJob(job);
rsp.sendRedirect2(".");
}
/**
* Returns String with version of backup .war file,
* if the file does not exists returns null
*/
public String getBackupVersion()
{
try {
JarFile backupWar = new JarFile(new File(Lifecycle.get().getHudsonWar().getParentFile(), "hudson.war.bak"));
return backupWar.getManifest().getMainAttributes().getValue("Hudson-Version");
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to read backup version ", e);
return null;}
}
/*package*/ synchronized Future<UpdateCenterJob> addJob(UpdateCenterJob job) {
// the first job is always the connectivity check
if (sourcesUsed.add(job.site))
......@@ -892,6 +933,87 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
}
/**
* Represents the state of the downgrading activity of plugin.
*/
public final class PluginDowngradeJob extends DownloadJob {
/**
* What plugin are we trying to install?
*/
public final Plugin plugin;
private final PluginManager pm = Hudson.getInstance().getPluginManager();
public PluginDowngradeJob(Plugin plugin, UpdateSite site, Authentication auth) {
super(site, auth);
this.plugin = plugin;
}
protected URL getURL() throws MalformedURLException {
return new URL(plugin.url);
}
protected File getDestination() {
File baseDir = pm.rootDir;
return new File(baseDir, plugin.name + ".hpi");
}
protected File getBackup()
{
File baseDir = pm.rootDir;
return new File(baseDir, plugin.name + ".bak");
}
public String getName() {
return plugin.getDisplayName();
}
@Override
public void run() {
try {
LOGGER.info("Starting the downgrade of "+getName()+" on behalf of "+getUser().getName());
_run();
LOGGER.info("Downgrade successful: "+getName());
status = new Success();
onSuccess();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to downgrade "+getName(),e);
status = new Failure(e);
}
}
@Override
protected void _run() throws IOException {
File dst = getDestination();
File backup = getBackup();
config.install(this, backup, dst);
}
/**
* Called to overwrite
* current version with backup file
*/
@Override
protected void replace(File dst, File backup) throws IOException {
dst.delete(); // any failure up to here is no big deal
if(!backup.renameTo(dst)) {
throw new IOException("Failed to rename "+backup+" to "+dst);
}
}
protected void onSuccess() {
pm.pluginUploaded = true;
}
@Override
public String toString() {
return super.toString()+"[plugin="+plugin.title+"]";
}
}
/**
* Represents the state of the upgrade activity of Hudson core.
*/
......@@ -922,6 +1044,56 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
}
public final class HudsonDowngradeJob extends DownloadJob {
public HudsonDowngradeJob(UpdateSite site, Authentication auth) {
super(site, auth);
}
protected URL getURL() throws MalformedURLException {
return new URL(site.getData().core.url);
}
protected File getDestination() {
return Lifecycle.get().getHudsonWar();
}
public String getName() {
return "hudson.war";
}
protected void onSuccess() {
status = new Success();
}
@Override
public void run() {
try {
LOGGER.info("Starting the downgrade of "+getName()+" on behalf of "+getUser().getName());
_run();
LOGGER.info("Downgrading successful: "+getName());
status = new Success();
onSuccess();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to downgrade "+getName(),e);
status = new Failure(e);
}
}
@Override
protected void _run() throws IOException {
File backup = new File(Lifecycle.get().getHudsonWar() + ".bak");
File dst = getDestination();
config.install(this, backup, dst);
}
@Override
protected void replace(File dst, File src) throws IOException {
Lifecycle.get().rewriteHudsonWar(src);
}
}
public static final class PluginEntry implements Comparable<PluginEntry> {
public Plugin plugin;
public String category;
......
......@@ -35,6 +35,7 @@ import hudson.util.VersionNumber;
import static hudson.util.TimeUnit2.DAYS;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.jvnet.hudson.crypto.CertificateUtil;
......@@ -67,6 +68,7 @@ import java.security.cert.TrustAnchor;
import com.trilead.ssh2.crypto.Base64;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
......@@ -601,6 +603,14 @@ public class UpdateSite {
return uc.addJob(uc.new InstallationJob(this, UpdateSite.this, Hudson.getAuthentication()));
}
/**
* Schedules the downgrade of this plugin.
*/
public Future<UpdateCenterJob> deployBackup() {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
return uc.addJob(uc.new PluginDowngradeJob(this, UpdateSite.this, Hudson.getAuthentication()));
}
/**
* Making the installation web bound.
*/
......@@ -608,6 +618,14 @@ public class UpdateSite {
deploy();
rsp.sendRedirect2("../..");
}
/**
* Performs the downgrade of the plugin.
*/
public void doDowngrade(StaplerResponse rsp) throws IOException {
deployBackup();
rsp.sendRedirect("../updateCenter/");
}
}
private static final long DAY = DAYS.toMillis(1);
......
......@@ -42,6 +42,7 @@ THE SOFTWARE.
<th width="32" tooltip="${%Uncheck to disable the plugin}">${%Enabled}</th>
<th initialSortDir="down">${%Name}</th>
<th width="32">${%Version}</th>
<th width="32">${%Previously installed version}</th>
<th width="32">${%Pinned}</th>
</tr>
<j:forEach var="p" items="${app.pluginManager.plugins}">
......@@ -68,6 +69,13 @@ THE SOFTWARE.
<td class="center pane" style="white-space:nowrap">
${p.version}
</td>
<td>
<j:if test="${p.downgradable}">
<form method="post" action="${rootURL}/updateCenter/plugin/${p.shortName}/downgrade">
<s:submit value="${%downgradeTo(p.backupVersion)}"/>
</form>
</j:if>
</td>
<td class="center pane" id='unpin-${p.shortName}'>
<j:if test="${p.isPinned()}">
<input type="button" onclick="unpin(this,'${p.shortName}')" value="${%Unpin}" class="yui-button" />
......
......@@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
wiki.url=http://wiki.hudson-ci.org/display/HUDSON/Pinned+Plugins
downgradeTo=Downgrade to {0}
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi
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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<div class="downgrade">
<j:if test="${app.updateCenter.downgradable}">
<form method="post" action="${rootURL}/updateCenter/downgrade">
<br/>${%Restore the previous version of Hudson}
<f:submit value="${%buttonText(app.updateCenter.backupVersion)}"/>
</form>
</j:if>
</div>
</j:jelly>
......@@ -63,6 +63,8 @@ THE SOFTWARE.
</j:if>
</j:forEach>
<st:include page="downgrade.jelly" />
<table style="padding-left: 2em;" id="management-links">
<local:feature icon="setting.gif" href="configure" title="${%Configure System}">
${%Configure global settings and paths.}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册