diff --git a/Documentation/config-auto-site-initialization.txt b/Documentation/config-auto-site-initialization.txt index 2bb0d4752b..3856e71c4d 100644 --- a/Documentation/config-auto-site-initialization.txt +++ b/Documentation/config-auto-site-initialization.txt @@ -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 diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK index c24342a6e3..b7162edaf4 100644 --- a/gerrit-pgm/BUCK +++ b/gerrit-pgm/BUCK @@ -72,6 +72,7 @@ java_library2( '//lib:guava', '//lib:gwtjsonrpc', '//lib:gwtorm', + '//lib/log:api', ], compile_deps = ['//gerrit-launcher:launcher'], visibility = [ diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java index 07e985ae5d..8221f5f047 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java @@ -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 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 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. newArrayList()); bind(new TypeLiteral>() {}).annotatedWith( InstallPlugins.class).toInstance(plugins); + bind(PluginsDistribution.class).toInstance(pluginsDistribution); } }); diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java index d97c6d0822..03973c02bd 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java @@ -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 plugins = - InitPlugins.listPluginsAndRemoveTempFiles(init.site); + InitPlugins.listPluginsAndRemoveTempFiles(init.site, pluginsDistribution); ConsoleUI ui = ConsoleUI.getInstance(false); verifyInstallPluginList(ui, plugins); if (listPlugins) { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java new file mode 100644 index 0000000000..2c34711559 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java @@ -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 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 listPluginNames() throws FileNotFoundException { + // not yet used + throw new UnsupportedOperationException(); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java index bbab099945..4ce9a24020 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java @@ -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 step() { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java index cc7c323b65..e5f6f561ed 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java @@ -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 listPlugins(SitePaths site) throws IOException { - return listPlugins(site, false); + public static List listPlugins(SitePaths site, + PluginsDistribution pluginsDistribution) throws IOException { + return listPlugins(site, false, pluginsDistribution); } - public static List listPluginsAndRemoveTempFiles(SitePaths site) throws IOException { - return listPlugins(site, true); + public static List listPluginsAndRemoveTempFiles(SitePaths site, + PluginsDistribution pluginsDistribution) throws IOException { + return listPlugins(site, true, pluginsDistribution); } - private static List listPlugins(SitePaths site, boolean deleteTempPluginFile) throws IOException { - final File myWar = GerritLauncher.getDistributionArchive(); + private static List listPlugins(final SitePaths site, + final boolean deleteTempPluginFile, PluginsDistribution pluginsDistribution) + throws IOException { final List result = Lists.newArrayList(); - try { - final ZipFile zf = new ZipFile(myWar); - try { - final Enumeration 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 plugins = listPlugins(site); + List plugins = listPlugins(site, pluginsDistribution); for (PluginData plugin : plugins) { String pluginName = plugin.name; try { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java new file mode 100644 index 0000000000..6b7386d243 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java @@ -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 listPluginNames() throws FileNotFoundException; +} diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK index 55363b113b..cde128ed81 100644 --- a/gerrit-war/BUCK +++ b/gerrit-war/BUCK @@ -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', diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java index a572fc8914..237881ba1a 100644 --- a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java @@ -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(); diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java new file mode 100644 index 0000000000..665d420f51 --- /dev/null +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java @@ -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 listPluginNames() throws FileNotFoundException { + List 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; + } +} diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java index 32fac33ac2..132c7292e7 100644 --- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java @@ -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(); }