Index: main/core/src/main/java/hudson/model/UpdateCenter.java
===================================================================
--- main/core/src/main/java/hudson/model/UpdateCenter.java (revision 13657)
+++ main/core/src/main/java/hudson/model/UpdateCenter.java (working copy)
@@ -1,5 +1,6 @@
package hudson.model;
+import hudson.ExtensionPoint;
import hudson.Functions;
import hudson.PluginManager;
import hudson.PluginWrapper;
@@ -11,6 +12,8 @@
import hudson.util.VersionNumber;
import static hudson.util.TimeUnit2.DAYS;
import net.sf.json.JSONObject;
+
+import org.acegisecurity.Authentication;
import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.io.IOUtils;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -41,6 +44,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Controls update center capability.
@@ -48,7 +53,12 @@
*
* The main job of this class is to keep track of the latest update center metadata file, and perform installations.
* Much of the UI about choosing plugins to install is done in {@link PluginManager}.
- *
+ *
+ * The update center can be configured to contact alternate servers for updates
+ * and plugins, and to use alternate strategies for downloading, installing
+ * and updating components. See the Javadocs for {@link UpdateCenterConfigurator}
+ * for more information.
+ *
* @author Kohsuke Kawaguchi
* @since 1.220
*/
@@ -86,6 +96,104 @@
private final Vector jobs = new Vector();
/**
+ * Update center configuration data
+ */
+ private UpdateCenterConfigurator config;
+
+ /**
+ * Default configuration
+ */
+ private static final UpdateCenterConfigurator DEFAULT_CONFIG = new UpdateCenterConfigurator(
+ new ConnectionCheckStrategy() {
+ public void checkConnection(ConnectionCheckJob job, String connectionCheckUrl) throws IOException {
+ testConnection(new URL(connectionCheckUrl + "?uctest"));
+ }
+
+ public void checkUpdateCenter(ConnectionCheckJob job, String updateCenterUrl) throws IOException {
+ testConnection(new URL(updateCenterUrl));
+ }
+
+ private void testConnection(URL url) throws IOException {
+ InputStream in = ProxyConfiguration.open(url).getInputStream();
+ IOUtils.copy(in,new ByteArrayOutputStream());
+ in.close();
+ }
+ },
+ new ValidationStrategy() {
+ public void preValidate(DownloadJob job, URL src) throws IOException {
+ if(!src.toExternalForm().startsWith(job.configuration.getPluginRepositoryBaseUrl())) {
+ throw new IOException("Installation of plugin from "+src+" is not allowed");
+ }
+ }
+
+ public void postValidate(DownloadJob job, File src) throws IOException {
+ }
+ },
+ new DownloadStrategy() {
+ public File download(DownloadJob job, URL src) throws IOException {
+ // In the future if we are to open up update center to 3rd party, we need more elaborate scheme
+ // like signing to ensure the safety of the bits.
+ URLConnection con = ProxyConfiguration.open(src);
+ int total = con.getContentLength();
+ CountingInputStream in = new CountingInputStream(con.getInputStream());
+ byte[] buf = new byte[8192];
+ int len;
+
+ File dst = job.getDestination();
+ File tmp = new File(dst.getPath()+".tmp");
+ OutputStream out = new FileOutputStream(tmp);
+
+ LOGGER.info("Downloading "+job.getName());
+ while((len=in.read(buf))>=0) {
+ out.write(buf,0,len);
+ job.status = job.new Installing(total==-1 ? -1 : in.getCount()*100/total);
+ }
+
+ in.close();
+ out.close();
+
+ return tmp;
+ }
+
+ },
+ new InstallStrategy() {
+ public void install(DownloadJob job, File src, File dst) throws IOException {
+ job.replace(dst, src);
+ }
+
+ },
+ new UpgradeStrategy() {
+ public void upgrade(DownloadJob job, File src, File dst) throws IOException {
+ job.replace(dst, src);
+ }
+ },
+ "http://www.google.com",
+ "https://hudson.dev.java.net/",
+ "https://hudson.dev.java.net/"
+ );
+
+ /**
+ * Create update center to get plugins/updates from hudson.dev.java.net
+ */
+ public UpdateCenter() {
+ configure(DEFAULT_CONFIG);
+ }
+
+ /**
+ * Configures update center to get plugins/updates from alternate servers,
+ * and optionally using alternate strategies for downloading, installing
+ * and upgrading.
+ *
+ * @param config Configuration data
+ * @see UpdateCenterConfigurator
+ */
+ public void configure(UpdateCenterConfigurator config) {
+ if (config!=null) {
+ this.config = config;
+ }
+ }
+
+ /**
* Returns true if it's time for us to check for new version.
*/
public boolean isDue() {
@@ -142,7 +250,7 @@
public void doUpgrade(StaplerResponse rsp) throws IOException, ServletException {
requirePOST();
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
- HudsonUpgradeJob job = new HudsonUpgradeJob();
+ HudsonUpgradeJob job = new HudsonUpgradeJob(Hudson.getAuthentication());
if(!Lifecycle.get().canRewriteHudsonWar()) {
sendError("Hudson upgrade not supported in this running mode");
return;
@@ -258,6 +366,14 @@
}
/**
+ * Exposed to get rid of hardcoding of the URL that serves up update-center.json
+ * in Javascript.
+ */
+ public String getUrl() {
+ return config.getUpdateCenterUrl();
+ }
+
+ /**
* In-memory representation of the update center data.
*/
public final class Data {
@@ -387,7 +503,7 @@
*/
public void install() {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
- addJob(new InstallationJob(this));
+ addJob(new InstallationJob(this, Hudson.getAuthentication()));
}
/**
@@ -400,6 +516,226 @@
}
/**
+ * Configuration data for control the update center's behaviors. The update
+ * center's defaults will check internet connectivity by trying to connect
+ * to www.google.com; will download plugins, the plugin catalog and updates
+ * from hudson.dev.java.net; and will install plugins with file system
+ * operations. This class, in conjunction with the {@link ConnectionCheckStrategy},
+ * {@link DownloadStrategy}, {@link InstallStrategy}, and {@link UpgradeStrategy}
+ * interfaces provides hooks to customize any of these behaviors.
+ *
+ * @author Dean Yu
+ * @since 1.265
+ */
+ public static final class UpdateCenterConfigurator {
+ /**
+ * Create an immutable update center configuration. Null may be passed
+ * in for any parameter to use Hudson's default for that behavior or value.
+ *
+ * @param c A {@link ConnectionCheckStrategy} implementation. If not null,
+ * Hudson will use this strategy for checking network connectivity
+ * before trying to download a plugin or upgrade. If null, Hudson's
+ * default behavior will try to connect to {@link #connectionCheckUrl}
+ * followed by a connection to {@link #updateCenterUrl}.
+ * @param v A {@link ValidationStrategy} implementation. If not null, Hudson
+ * will use this strategy to validate a resource before downloading it,
+ * and to validate the downloaded resource before installing it. If
+ * null, Hudson's default behavior is to enforce that the URL of
+ * the resource starts with {@link #pluginRepositoryBaseUrl} before
+ * the resource is downloaded.
+ * @param d A {@link DownloadStrategy} implementation. If not null, Hudson
+ * will use this strategy to download a plugin or core upgrade. The
+ * download strategy Hudson uses by default will download the
+ * resource from the URL specified in the catalog. Regardless of
+ * the strategy, Hudson will always enforce that the URL starts
+ * with the value of {@link #pluginRepositoryBaseUrl}.
+ * @param i A {@link InstallStrategy} implementation. If not null, Hudson
+ * will use this strategy to move the downloaded file into its
+ * final destination. If null, Hudson will simply rename the
+ * downloaded file.
+ * @param u A {@link UpgradeStrategy} implementation. If not null, Hudson
+ * will use this strategy to upgrade itself.
+ * @param connectionCheckUrl A string containing an "always up" URL. If
+ * null is passed, Hudson will use "http://www.google.com".
+ * @param updateCenterUrl A string containing the URL to retrieve the update-center.json
+ * file from. If null, Hudson will use "https://hudson.dev.java.net/".
+ * @param pluginRepositoryBaseUrl A string containing the base URL for
+ * plugin downloads. Hudson enforces that all plugins must be downloaded
+ * from this domain. If null, Hudson will use "https://hudson.dev.java.net/".
+ */
+ public UpdateCenterConfigurator(ConnectionCheckStrategy c, ValidationStrategy v,
+ DownloadStrategy d, InstallStrategy i,UpgradeStrategy u,
+ String connectionCheckUrl, String updateCenterUrl, String pluginRepositoryBaseUrl) {
+ connectionChecker = c==null ? UpdateCenter.DEFAULT_CONFIG.getConnectionCheckStrategy() : c;
+ validator = v==null ? UpdateCenter.DEFAULT_CONFIG.getValidationStrategy() : v;
+ downloader = d==null ? UpdateCenter.DEFAULT_CONFIG.getDownloadStrategy() : d;
+ installer = i==null ? UpdateCenter.DEFAULT_CONFIG.getInstallStrategy() : i;
+ upgrader = u==null ? UpdateCenter.DEFAULT_CONFIG.getUpgradeStrategy() : u;
+ this.connectionCheckUrl = Util.fixEmptyAndTrim(connectionCheckUrl)==null ? UpdateCenter.DEFAULT_CONFIG.getConnectionCheckUrl() : connectionCheckUrl;
+ this.updateCenterUrl = Util.fixEmptyAndTrim(updateCenterUrl)==null ? UpdateCenter.DEFAULT_CONFIG.getUpdateCenterUrl() : updateCenterUrl;
+ this.pluginRepositoryBaseUrl = Util.fixEmptyAndTrim(pluginRepositoryBaseUrl)==null ? UpdateCenter.DEFAULT_CONFIG.getPluginRepositoryBaseUrl() : pluginRepositoryBaseUrl;
+ }
+
+ public ConnectionCheckStrategy getConnectionCheckStrategy() {
+ return connectionChecker;
+ }
+
+ public ValidationStrategy getValidationStrategy() {
+ return validator;
+ }
+
+ public DownloadStrategy getDownloadStrategy() {
+ return downloader;
+ }
+
+ public InstallStrategy getInstallStrategy() {
+ return installer;
+ }
+
+ public UpgradeStrategy getUpgradeStrategy() {
+ return upgrader;
+ }
+
+ public String getConnectionCheckUrl() {
+ return connectionCheckUrl;
+ }
+
+ public String getUpdateCenterUrl() {
+ return updateCenterUrl;
+ }
+
+ public String getPluginRepositoryBaseUrl() {
+ return pluginRepositoryBaseUrl;
+ }
+
+ private ConnectionCheckStrategy connectionChecker;
+ private ValidationStrategy validator;
+ private DownloadStrategy downloader;
+ private InstallStrategy installer;
+ private UpgradeStrategy upgrader;
+ private String connectionCheckUrl;
+ private String updateCenterUrl;
+ private String pluginRepositoryBaseUrl;
+ }
+
+ /**
+ * Determine network connectivity before attempting to download. You will typically only
+ * ever need to implement this interface if you plan to fetch plugins
+ * over a transport protocol other than HTTP.
+ */
+ public interface ConnectionCheckStrategy extends ExtensionPoint {
+ /**
+ * Check network connectivity by trying to establish a connection to
+ * the host in connectionCheckUrl.
+ *
+ * @param job The connection checker that is invoking this strategy.
+ * @param connectionCheckUrl A string containing the URL of a domain
+ * that is assumed to be always available.
+ * @throws IOException if a connection can't be established
+ */
+ public void checkConnection(ConnectionCheckJob job, String connectionCheckUrl) throws IOException;
+
+ /**
+ * Check connection to update center server.
+ *
+ * @param job The connection checker that is invoking this strategy.
+ * @param updateCenterUrl A sting containing the URL of the update center host.
+ * @throws IOException if a connection to the update center server can't be established.
+ */
+ public void checkUpdateCenter(ConnectionCheckJob job, String updateCenterUrl) throws IOException;
+ }
+
+ /**
+ * Hooks for validating a resource before and after it is downloaded. This
+ * interface defines two validation points. Prevalidation allows validation
+ * of the URI (URL) a plugin or upgrade resource will be downloaded from.
+ * Post-validation allows validation of the resource after it has been
+ * downloaded. The default implementation enforces that the resource is
+ * hosted by {@link UpdateCenterConfigurator#pluginRepositoryBaseUrl} before
+ * it is downloaded. It does no additional validation of the downloaded
+ * bits.
+ */
+ public interface ValidationStrategy extends ExtensionPoint {
+ /**
+ * Validate the URL of the resource before downloading it.
+ *
+ * @param job The download job that is invoking this strategy. This job is
+ * responsible for managing the status of the download and installation.
+ * @param src The location of the resource on the network
+ * @throws IOException if the validation fails
+ */
+ public void preValidate(DownloadJob job, URL src) throws IOException;
+
+ /**
+ * Validate the resource after it has been downloaded, before it is
+ * installed.
+ *
+ * @param job The download job that is invoking this strategy. This job is
+ * responsible for managing the status of the download and installation.
+ * @param src The location of the downloaded resource.
+ * @throws IOException if the validation fails.
+ */
+ public void postValidate(DownloadJob job, File src) throws IOException;
+ }
+
+ /**
+ * Retrieve a plugin or core upgrade to a temporary location. Implement this interface if you
+ * need to do more than read a byte stream from an Internet host. The download
+ * job will not call this strategy if the resource does not originate from
+ * the host specified by {@link UpdateCenterConfigurator#pluginRepositoryBaseUrl}.
+ */
+ public interface DownloadStrategy extends ExtensionPoint {
+ /**
+ * Download a plugin or core upgrade in preparation for installing it
+ * into its final location. Implementations will normally download the
+ * resource into a temporary location and hand off a reference to this
+ * location to the install or upgrade strategy to move into the final location.
+ *
+ * @param job The download job that is invoking this strategy. This job is
+ * responsible for managing the status of the download and installation.
+ * @param src The URL to the resource to be downloaded.
+ * @return A File object that describes the downloaded resource.
+ * @throws IOException if there were problems downloading the resource.
+ * @see DownloadJob
+ */
+ public File download(DownloadJob job, URL src) throws IOException;
+ }
+
+ /**
+ * Installs the downloaded plugin into its final location. Implement this interface
+ * if you need to do more than move the temporary file into place.
+ */
+ public interface InstallStrategy extends ExtensionPoint {
+ /**
+ * Called after a plugin has been downloaded to move it into its final
+ * location.
+ *
+ * @param job The install job that is invoking this strategy.
+ * @param src The temporary location of the plugin.
+ * @param dst The final destination to install the plugin to.
+ * @throws IOException if there are problems installing the resource.
+ */
+ public void install(DownloadJob job, File src, File dst) throws IOException;
+ }
+
+ /**
+ * Installs the downloaded upgrade into its final location. Implement this
+ * interface if you need to do more than move the temporary file into place.
+ */
+ public interface UpgradeStrategy extends ExtensionPoint {
+ /**
+ * Called after an upgrade has been downloaded to move it into its final
+ * location.
+ *
+ * @param job The upgrade job that is invoking this strategy.
+ * @param src The temporary location of the upgrade.
+ * @param dst The final destination to install the upgrade to.
+ * @throws IOException if there are problems installing the resource.
+ */
+ public void upgrade(DownloadJob job, File src, File dst) throws IOException;
+ }
+
+ /**
* Things that {@link UpdateCenter#installerService} executes.
*
* This object will have the row.jelly which renders the job on UI.
@@ -419,19 +755,21 @@
public void run() {
try {
+ String connectionCheckUrl = config.getConnectionCheckUrl();
+
statuses.add(Messages.UpdateCenter_Status_CheckingInternet());
try {
- testConnection(new URL("http://www.google.com/"));
+ config.getConnectionCheckStrategy().checkConnection(this, connectionCheckUrl);
} catch (IOException e) {
if(e.getMessage().contains("Connection timed out")) {
// Google can't be down, so this is probably a proxy issue
- statuses.add(Messages.UpdateCenter_Status_ConnectionFailed("www.google.com"));
+ statuses.add(Messages.UpdateCenter_Status_ConnectionFailed(connectionCheckUrl));
return;
}
}
statuses.add(Messages.UpdateCenter_Status_CheckingJavaNet());
- testConnection(new URL("https://hudson.dev.java.net/?uctest"));
+ config.getConnectionCheckStrategy().checkUpdateCenter(this, config.getUpdateCenterUrl());
statuses.add(Messages.UpdateCenter_Status_Success());
} catch (UnknownHostException e) {
@@ -451,12 +789,6 @@
return statuses.toArray(new String[statuses.size()]);
}
}
-
- private void testConnection(URL url) throws IOException {
- InputStream in = ProxyConfiguration.open(url).getInputStream();
- IOUtils.copy(in,new ByteArrayOutputStream());
- in.close();
- }
}
/**
@@ -489,41 +821,40 @@
*/
protected abstract void onSuccess();
+ /**
+ * So custom strategies have access to the configuration data
+ */
+ public final UpdateCenterConfigurator configuration = config;
+
+ private Authentication authentication;
+
+ /**
+ * Get the user that initiated this job
+ */
+ public Authentication getUser()
+ {
+ return this.authentication;
+ }
+
+ protected DownloadJob(Authentication authentication)
+ {
+ this.authentication = authentication;
+ }
+
public void run() {
try {
- LOGGER.info("Starting the installation of "+getName());
+ LOGGER.info("Starting the installation of "+getName()+" on behalf of "+getUser().getName());
URL src = getURL();
- // for security reasons, only install from hudson.dev.java.net for now, which is also conveniently
- // https to guarantee transport level security.
- if(!src.toExternalForm().startsWith("https://hudson.dev.java.net/")) {
- throw new IOException("Installation from non-official repository at "+src+" is not support yet");
- }
+ config.getValidationStrategy().preValidate(this, src);
- // In the future if we are to open up update center to 3rd party, we need more elaborate scheme
- // like signing to ensure the safety of the bits.
- URLConnection con = ProxyConfiguration.open(src);
- int total = con.getContentLength();
- CountingInputStream in = new CountingInputStream(con.getInputStream());
- byte[] buf = new byte[8192];
- int len;
-
File dst = getDestination();
- File tmp = new File(dst.getPath()+".tmp");
- OutputStream out = new FileOutputStream(tmp);
-
- LOGGER.info("Downloading "+getName());
- while((len=in.read(buf))>=0) {
- out.write(buf,0,len);
- status = new Installing(total==-1 ? -1 : in.getCount()*100/total);
- }
-
- in.close();
- out.close();
-
- replace(dst, tmp);
-
+ File tmp = config.getDownloadStrategy().download(this, src);
+
+ config.getValidationStrategy().postValidate(this, tmp);
+ config.getInstallStrategy().install(this, tmp, dst);
+
LOGGER.info("Installation successful: "+getName());
status = new Success();
onSuccess();
@@ -606,7 +937,8 @@
private final PluginManager pm = Hudson.getInstance().getPluginManager();
- public InstallationJob(Plugin plugin) {
+ public InstallationJob(Plugin plugin, Authentication auth) {
+ super(auth);
this.plugin = plugin;
}
@@ -632,7 +964,8 @@
* Represents the state of the upgrade activity of Hudson core.
*/
public final class HudsonUpgradeJob extends DownloadJob {
- public HudsonUpgradeJob() {
+ public HudsonUpgradeJob(Authentication auth) {
+ super(auth);
}
protected URL getURL() throws MalformedURLException {
Index: main/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly
===================================================================
--- main/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly (revision 13657)
+++ main/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly (working copy)
@@ -10,7 +10,8 @@
-
\ No newline at end of file
+
Index: main/war/resources/scripts/hudson-behavior.js
===================================================================
--- main/war/resources/scripts/hudson-behavior.js (revision 13657)
+++ main/war/resources/scripts/hudson-behavior.js (working copy)
@@ -1319,9 +1319,10 @@
postBackURL : null,
info: {},
completionHandler: null,
+ url: "https://hudson.dev.java.net/",
checkUpdates : function() {
- loadScript("https://hudson.dev.java.net/update-center.json?"+Hash.toQueryString(updateCenter.info));
+ loadScript(updateCenter.url+"update-center.json?"+Hash.toQueryString(updateCenter.info));
},
post : function(data) {
@@ -1369,4 +1370,4 @@
}
});
}, 5000);
-}
\ No newline at end of file
+}