Install all packaged plugins automatically on auto site init

When Gerrit is deployed in a servlet container and the system property
'gerrit.init' is defined then, besides the automatic site
initialization, also all packaged plugins (under WEB-INF/plugins) will
be installed.

Change-Id: Ia1e50d88ba63e96fc70717f854c87c7409f0c618
This commit is contained in:
Saša Živkov 2014-02-11 10:50:59 +01:00
parent e90d6e81d3
commit e2a1e7ef34
12 changed files with 274 additions and 58 deletions

View File

@ -4,8 +4,9 @@
Gerrit supports automatic site initialization on server startup
when Gerrit runs in a servlet container. Both creation of a new site
and upgrade of an existing site are supported. Installation of
plugins during the site creation/initialization is not yet supported.
and upgrade of an existing site are supported. All packaged plugins
will be installed when Gerrit is deployed in a servlet container and the
location of the Gerrit distribution can be determined at runtime.
This feature may be useful for such setups where Gerrit administrators
don't have direct access to the database and the file system of the

View File

@ -72,6 +72,7 @@ java_library2(
'//lib:guava',
'//lib:gwtjsonrpc',
'//lib:gwtorm',
'//lib/log:api',
],
compile_deps = ['//gerrit-launcher:launcher'],
visibility = [

View File

@ -22,6 +22,7 @@ import com.google.common.collect.Lists;
import com.google.gerrit.pgm.init.InitFlags;
import com.google.gerrit.pgm.init.InitModule;
import com.google.gerrit.pgm.init.InstallPlugins;
import com.google.gerrit.pgm.init.PluginsDistribution;
import com.google.gerrit.pgm.init.SitePathInitializer;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.pgm.util.Die;
@ -47,7 +48,11 @@ import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
@ -55,24 +60,31 @@ import javax.sql.DataSource;
/** Initialize a new Gerrit installation. */
public class BaseInit extends SiteProgram {
private static final Logger log =
LoggerFactory.getLogger(BaseInit.class);
private final boolean standalone;
private final boolean initDb;
protected final PluginsDistribution pluginsDistribution;
public BaseInit() {
protected BaseInit(PluginsDistribution pluginsDistribution) {
this.standalone = true;
this.initDb = true;
this.pluginsDistribution = pluginsDistribution;
}
public BaseInit(File sitePath, boolean standalone, boolean initDb) {
this(sitePath, null, standalone, initDb);
public BaseInit(File sitePath, boolean standalone, boolean initDb,
PluginsDistribution pluginsDistribution) {
this(sitePath, null, standalone, initDb, pluginsDistribution);
}
public BaseInit(File sitePath, final Provider<DataSource> dsProvider,
boolean standalone, boolean initDb) {
boolean standalone, boolean initDb,
PluginsDistribution pluginsDistribution) {
super(sitePath, dsProvider);
this.standalone = standalone;
this.initDb = initDb;
this.pluginsDistribution = pluginsDistribution;
}
@Override
@ -123,7 +135,13 @@ public class BaseInit extends SiteProgram {
}
protected List<String> getInstallPlugins() {
return null;
try {
return pluginsDistribution.listPluginNames();
} catch (FileNotFoundException e) {
log.warn("Couldn't find distribution archive location."
+ " No plugin will be installed");
return null;
}
}
protected boolean getAutoStart() {
@ -161,6 +179,7 @@ public class BaseInit extends SiteProgram {
Objects.firstNonNull(getInstallPlugins(), Lists.<String> newArrayList());
bind(new TypeLiteral<List<String>>() {}).annotatedWith(
InstallPlugins.class).toInstance(plugins);
bind(PluginsDistribution.class).toInstance(pluginsDistribution);
}
});

View File

@ -60,10 +60,11 @@ public class Init extends BaseInit {
Browser browser;
public Init() {
super(new WarDistribution());
}
public Init(File sitePath) {
super(sitePath, true, true);
super(sitePath, true, true, new WarDistribution());
batchMode = true;
noAutoStart = true;
}
@ -74,7 +75,7 @@ public class Init extends BaseInit {
if (!skipPlugins) {
final List<PluginData> plugins =
InitPlugins.listPluginsAndRemoveTempFiles(init.site);
InitPlugins.listPluginsAndRemoveTempFiles(init.site, pluginsDistribution);
ConsoleUI ui = ConsoleUI.getInstance(false);
verifyInstallPluginList(ui, plugins);
if (listPlugins) {

View File

@ -0,0 +1,65 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.pgm;
import static com.google.gerrit.pgm.init.InitPlugins.JAR;
import static com.google.gerrit.pgm.init.InitPlugins.PLUGIN_DIR;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.pgm.init.PluginsDistribution;
import com.google.inject.Singleton;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@Singleton
public class WarDistribution implements PluginsDistribution {
@Override
public void foreach(Processor processor) throws FileNotFoundException, IOException {
File myWar = GerritLauncher.getDistributionArchive();
if (myWar.isFile()) {
try (ZipFile zf = new ZipFile(myWar)) {
Enumeration<? extends ZipEntry> e = zf.entries();
while (e.hasMoreElements()) {
ZipEntry ze = e.nextElement();
if (ze.isDirectory()) {
continue;
}
if (ze.getName().startsWith(PLUGIN_DIR) && ze.getName().endsWith(JAR)) {
String pluginJarName = new File(ze.getName()).getName();
String pluginName = pluginJarName.substring(0,
pluginJarName.length() - JAR.length());
final InputStream in = zf.getInputStream(ze);
processor.process(pluginName, in);
}
}
}
}
}
@Override
public List<String> listPluginNames() throws FileNotFoundException {
// not yet used
throw new UnsupportedOperationException();
}
}

View File

@ -58,9 +58,7 @@ public class InitModule extends FactoryModule {
step().to(InitSshd.class);
step().to(InitHttpd.class);
step().to(InitCache.class);
if (standalone) {
step().to(InitPlugins.class);
}
step().to(InitPlugins.class);
}
protected LinkedBindingBuilder<InitStep> step() {

View File

@ -15,7 +15,6 @@
package com.google.gerrit.pgm.init;
import com.google.common.collect.Lists;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.plugins.PluginLoader;
@ -25,18 +24,15 @@ import com.google.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@Singleton
public class InitPlugins implements InitStep {
private static final String PLUGIN_DIR = "WEB-INF/plugins/";
private static final String JAR = ".jar";
public static final String PLUGIN_DIR = "WEB-INF/plugins/";
public static final String JAR = ".jar";
public static class PluginData {
public final String name;
@ -50,46 +46,31 @@ public class InitPlugins implements InitStep {
}
}
public static List<PluginData> listPlugins(SitePaths site) throws IOException {
return listPlugins(site, false);
public static List<PluginData> listPlugins(SitePaths site,
PluginsDistribution pluginsDistribution) throws IOException {
return listPlugins(site, false, pluginsDistribution);
}
public static List<PluginData> listPluginsAndRemoveTempFiles(SitePaths site) throws IOException {
return listPlugins(site, true);
public static List<PluginData> listPluginsAndRemoveTempFiles(SitePaths site,
PluginsDistribution pluginsDistribution) throws IOException {
return listPlugins(site, true, pluginsDistribution);
}
private static List<PluginData> listPlugins(SitePaths site, boolean deleteTempPluginFile) throws IOException {
final File myWar = GerritLauncher.getDistributionArchive();
private static List<PluginData> listPlugins(final SitePaths site,
final boolean deleteTempPluginFile, PluginsDistribution pluginsDistribution)
throws IOException {
final List<PluginData> result = Lists.newArrayList();
try {
final ZipFile zf = new ZipFile(myWar);
try {
final Enumeration<? extends ZipEntry> e = zf.entries();
while (e.hasMoreElements()) {
final ZipEntry ze = e.nextElement();
if (ze.isDirectory()) {
continue;
}
if (ze.getName().startsWith(PLUGIN_DIR) && ze.getName().endsWith(JAR)) {
final String pluginJarName = new File(ze.getName()).getName();
final String pluginName = pluginJarName.substring(0, pluginJarName.length() - JAR.length());
final InputStream in = zf.getInputStream(ze);
final File tmpPlugin = PluginLoader.storeInTemp(pluginName, in, site);
final String pluginVersion = getVersion(tmpPlugin);
if (deleteTempPluginFile) {
tmpPlugin.delete();
}
result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
}
pluginsDistribution.foreach(new PluginsDistribution.Processor() {
@Override
public void process(String pluginName, InputStream in) throws IOException {
File tmpPlugin = PluginLoader.storeInTemp(pluginName, in, site);
String pluginVersion = getVersion(tmpPlugin);
if (deleteTempPluginFile) {
tmpPlugin.delete();
}
} finally {
zf.close();
result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
}
} catch (IOException e) {
throw new IOException("Failure during plugin installation", e);
}
});
return result;
}
@ -97,14 +78,17 @@ public class InitPlugins implements InitStep {
private final SitePaths site;
private final InitFlags initFlags;
private final InitPluginStepsLoader pluginLoader;
private final PluginsDistribution pluginsDistribution;
@Inject
InitPlugins(final ConsoleUI ui, final SitePaths site,
InitFlags initFlags, InitPluginStepsLoader pluginLoader) {
InitFlags initFlags, InitPluginStepsLoader pluginLoader,
PluginsDistribution pluginsDistribution) {
this.ui = ui;
this.site = site;
this.initFlags = initFlags;
this.pluginLoader = pluginLoader;
this.pluginsDistribution = pluginsDistribution;
}
@Override
@ -121,7 +105,7 @@ public class InitPlugins implements InitStep {
}
private void installPlugins() throws IOException {
List<PluginData> plugins = listPlugins(site);
List<PluginData> plugins = listPlugins(site, pluginsDistribution);
for (PluginData plugin : plugins) {
String pluginName = plugin.name;
try {

View File

@ -0,0 +1,57 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.pgm.init;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* Represents the plugins packaged in the Gerrit distribution
*/
public interface PluginsDistribution {
public interface Processor {
/**
* @param pluginName the name of the plugin (without the .jar extension)
* @param in the content of the plugin .jar file. Implementors don't have to
* close this stream.
* @throws IOException implementations will typically propagate any
* IOException caused by dealing with the InputStream back to the
* caller
*/
public void process(String pluginName, InputStream in) throws IOException;
}
/**
* Iterate over plugins package in the Gerrit distribution
*
* @param processor invoke for each plugin via its process method
* @throws FileNotFoundException if the location of the plugins couldn't be
* determined
* @throws IOException in case of any other IO error caused by reading the
* plugin input stream
*/
public void foreach(Processor processor) throws FileNotFoundException, IOException;
/**
* List plugins included in the Gerrit distribution
* @return list of plugins names included in the Gerrit distribution
* @throws FileNotFoundException if the location of the plugins couldn't be
* determined
*/
public List<String> listPluginNames() throws FileNotFoundException;
}

View File

@ -9,6 +9,7 @@ java_library2(
'//gerrit-httpd:httpd',
'//gerrit-lucene:lucene',
'//gerrit-openid:openid',
'//gerrit-pgm:init-api',
'//gerrit-pgm:init-base',
'//gerrit-reviewdb:server',
'//gerrit-server:server',

View File

@ -15,6 +15,7 @@
package com.google.gerrit.httpd;
import com.google.gerrit.pgm.BaseInit;
import com.google.gerrit.pgm.init.PluginsDistribution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -31,10 +32,13 @@ public final class SiteInitializer {
private final String sitePath;
private final String initPath;
private final PluginsDistribution pluginsDistribution;
SiteInitializer(String sitePath, String initPath) {
SiteInitializer(String sitePath, String initPath,
PluginsDistribution pluginsDistribution) {
this.sitePath = sitePath;
this.initPath = initPath;
this.pluginsDistribution = pluginsDistribution;
}
public void init() {
@ -43,7 +47,7 @@ public final class SiteInitializer {
File site = new File(sitePath);
LOG.info(String.format("Initializing site at %s",
site.getAbsolutePath()));
new BaseInit(site, false, true).run();
new BaseInit(site, false, true, pluginsDistribution).run();
return;
}
@ -56,7 +60,8 @@ public final class SiteInitializer {
if (site != null) {
LOG.info(String.format("Initializing site at %s",
site.getAbsolutePath()));
new BaseInit(site, new ReviewDbDataSourceProvider(), false, false).run();
new BaseInit(site, new ReviewDbDataSourceProvider(), false, false,
pluginsDistribution).run();
}
} finally {
conn.close();

View File

@ -0,0 +1,79 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd;
import static com.google.gerrit.pgm.init.InitPlugins.JAR;
import static com.google.gerrit.pgm.init.InitPlugins.PLUGIN_DIR;
import com.google.common.collect.Lists;
import com.google.gerrit.pgm.init.PluginsDistribution;
import com.google.inject.Singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.ServletContext;
@Singleton
class UnzippedDistribution implements PluginsDistribution {
private ServletContext servletContext;
private File pluginsDir;
public UnzippedDistribution(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void foreach(Processor processor) throws FileNotFoundException, IOException {
File[] list = getPluginsDir().listFiles();
if (list != null) {
for (File p : list) {
String pluginJarName = p.getName();
String pluginName = pluginJarName.substring(0,
pluginJarName.length() - JAR.length());
try (InputStream in = new FileInputStream(p)) {
processor.process(pluginName, in);
}
}
}
}
@Override
public List<String> listPluginNames() throws FileNotFoundException {
List<String> names = Lists.newArrayList();
String[] list = getPluginsDir().list();
if (list != null) {
for (String pluginJarName : list) {
String pluginName = pluginJarName.substring(0,
pluginJarName.length() - JAR.length());
names.add(pluginName);
}
}
return names;
}
private File getPluginsDir() {
if (pluginsDir == null) {
File root = new File(servletContext.getRealPath(""));
pluginsDir = new File(root, PLUGIN_DIR);
}
return pluginsDir;
}
}

View File

@ -83,6 +83,7 @@ import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@ -105,6 +106,8 @@ public class WebAppInitializer extends GuiceServletContextListener
private LifecycleManager manager;
private GuiceFilter filter;
private ServletContext servletContext;
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
@ -119,7 +122,8 @@ public class WebAppInitializer extends GuiceServletContextListener
}
if (System.getProperty("gerrit.init") != null) {
new SiteInitializer(path, System.getProperty("gerrit.init_path")).init();
new SiteInitializer(path, System.getProperty("gerrit.init_path"),
new UnzippedDistribution(servletContext)).init();
}
try {
@ -339,7 +343,8 @@ public class WebAppInitializer extends GuiceServletContextListener
@Override
public void init(FilterConfig cfg) throws ServletException {
contextInitialized(new ServletContextEvent(cfg.getServletContext()));
servletContext = cfg.getServletContext();
contextInitialized(new ServletContextEvent(servletContext));
init();
manager.start();
}