提交 768da3e2 编写于 作者: O Oleg Nenashev

[JENKINS-34733] - Allow overriding Jenkins UpdateCenter by a custom implementation (#2332)

* [JENKINS-34733] - Allow overriding Jenkins UpdateCenter by a custom implementation

* [JENKINS-34733] - Reduce the logging level for the custom update center selection

* [JENKINS-34733 and JENKINS-34674] - Use SystemProperties in the UpdateCenter
上级 6ceccc0c
......@@ -79,6 +79,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
......@@ -129,7 +130,16 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
* and plugins, and to use alternate strategies for downloading, installing
* and updating components. See the Javadocs for {@link UpdateCenterConfiguration}
* for more information.
*
* <p>
* <b>Extending Update Centers</b>. The update center in {@code Jenkins} can be replaced by defining a
* System Property (<code>hudson.model.UpdateCenter.className</code>). See {@link #createUpdateCenter(hudson.model.UpdateCenter.UpdateCenterConfiguration)}.
* This className should be available on early startup, so it cannot come only from a library
* (e.g. Jenkins module or Extra library dependency in the WAR file project).
* Plugins cannot be used for such purpose.
* In order to be correctly instantiated, the class definition must have two constructors:
* {@link #UpdateCenter()} and {@link #UpdateCenter(hudson.model.UpdateCenter.UpdateCenterConfiguration)}.
* If the class does not comply with the requirements, a fallback to the default UpdateCenter will be performed.
*
* @author Kohsuke Kawaguchi
* @since 1.220
*/
......@@ -148,7 +158,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
* @since 1.483 - public property
* @since TODO - configurable via system property
*/
public static final String ID_DEFAULT = System.getProperty(UpdateCenter.class.getName()+".defaultUpdateSiteId", "default");
public static final String ID_DEFAULT = SystemProperties.getString(UpdateCenter.class.getName()+".defaultUpdateSiteId", "default");
@Restricted(NoExternalUse.class)
public static final String ID_UPLOAD = "_upload";
......@@ -232,6 +242,51 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
UpdateCenter(@Nonnull UpdateCenterConfiguration configuration) {
configure(configuration);
}
/**
* Creates an update center.
* @param config Requested configuration. May be {@code null} if defaults should be used
* @return Created Update center. {@link UpdateCenter} by default, but may be overridden
* @since TODO
*/
@Nonnull
public static UpdateCenter createUpdateCenter(@CheckForNull UpdateCenterConfiguration config) {
String requiredClassName = SystemProperties.getString(UpdateCenter.class.getName()+".className", null);
if (requiredClassName == null) {
// Use the defaul Update Center
LOGGER.log(Level.FINE, "Using the default Update Center implementation");
return createDefaultUpdateCenter(config);
}
LOGGER.log(Level.FINE, "Using the custom update center: {0}", requiredClassName);
try {
final Class<?> clazz = Class.forName(requiredClassName).asSubclass(UpdateCenter.class);
if (!UpdateCenter.class.isAssignableFrom(clazz)) {
LOGGER.log(Level.SEVERE, "The specified custom Update Center {0} is not an instance of {1}. Falling back to default.",
new Object[] {requiredClassName, UpdateCenter.class.getName()});
return createDefaultUpdateCenter(config);
}
final Class<? extends UpdateCenter> ucClazz = clazz.asSubclass(UpdateCenter.class);
final Constructor<? extends UpdateCenter> defaultConstructor = ucClazz.getConstructor();
final Constructor<? extends UpdateCenter> configConstructor = ucClazz.getConstructor(UpdateCenterConfiguration.class);
LOGGER.log(Level.FINE, "Using the constructor {0} Update Center configuration for {1}",
new Object[] {config != null ? "with" : "without", requiredClassName});
return config != null ? configConstructor.newInstance(config) : defaultConstructor.newInstance();
} catch(ClassCastException e) {
// Should never happen
LOGGER.log(WARNING, "UpdateCenter class {0} does not extend hudson.model.UpdateCenter. Using default.", requiredClassName);
} catch(NoSuchMethodException e) {
LOGGER.log(WARNING, String.format("UpdateCenter class {0} does not define one of the required constructors. Using default", requiredClassName), e);
} catch(Exception e) {
LOGGER.log(WARNING, String.format("Unable to instantiate custom plugin manager [%s]. Using default.", requiredClassName), e);
}
return createDefaultUpdateCenter(config);
}
@Nonnull
private static UpdateCenter createDefaultUpdateCenter(@CheckForNull UpdateCenterConfiguration config) {
return config != null ? new UpdateCenter(config) : new UpdateCenter();
}
public Api getApi() {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
......
......@@ -776,7 +776,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
private transient final String secretKey;
private transient final UpdateCenter updateCenter = new UpdateCenter();
private transient final UpdateCenter updateCenter = UpdateCenter.createUpdateCenter(null);
/**
* True if the user opted out from the statistics tracking. We'll never send anything if this is true.
......
/*
* The MIT License
*
* Copyright (c) 2016 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import javax.servlet.ServletContext;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
/**
* Tests of the custom {@link UpdateCenter} implementation.
*/
public class UpdateCenterCustomTest {
@Rule
public final JenkinsRule j = new CustomUpdateCenterRule(CustomUpdateCenter.class);
@Test
public void shouldStartupWithCustomUpdateCenter() throws Exception {
UpdateCenter uc = j.jenkins.getUpdateCenter();
assertThat("Update Center must be a custom instance", uc, instanceOf(CustomUpdateCenter.class));
}
// TODO: move to Jenkins Test Harness
private static final class CustomUpdateCenterRule extends JenkinsRule {
private final String updateCenterClassName;
private String _oldValue = null;
private static final String PROPERTY_NAME = UpdateCenter.class.getName()+".className";
public CustomUpdateCenterRule(Class<?> ucClass) {
this.updateCenterClassName = ucClass.getName();
}
@Override
protected ServletContext createWebServer() throws Exception {
_oldValue = System.getProperty(PROPERTY_NAME);
System.setProperty(PROPERTY_NAME, updateCenterClassName);
return super.createWebServer();
}
@Override
public void after() throws Exception {
if (_oldValue != null) {
System.setProperty(PROPERTY_NAME, _oldValue);
}
}
public String getUpdateCenterClassName() {
return updateCenterClassName;
}
};
public static final class CustomUpdateCenter extends UpdateCenter {
public CustomUpdateCenter() {
super();
}
public CustomUpdateCenter(UpdateCenterConfiguration config) {
super(config);
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册