Skip to content
Permalink
Browse files
[JENKINS-34733] - Allow overriding Jenkins UpdateCenter by a custom i…
…mplementation (#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
  • Loading branch information
oleg-nenashev committed May 14, 2016
1 parent 6ceccc0 commit 768da3e241417da47f306ac2e32fbc14c31be8c8
@@ -79,6 +79,7 @@
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 @@
* 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 @@
* @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 UpdateCenter() {
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 static Jenkins getInstance() {
*/
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.
@@ -0,0 +1,90 @@
/*
* 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);
}

}
}

0 comments on commit 768da3e

Please sign in to comment.