Allow plugins to contribute InitStep to Gerrit init.
Plugin has to include a class that implements com.google.gerrit.pgm.init.InitStep and specifying the class name into the Plugin MANIFEST.MF. Plugin InitStep is getting instantiated using the Gerrit PGM injector and then is able to access the same init objects. Example: -------- public class InitJira implements InitStep { private ConsoleUI ui; @Inject public InitJira(final ConsoleUI ui, final Section.Factory sections) { this.ui = ui; this.sections = sections; } @Override public void run() throws Exception { ui.header("Jira integration"); // Jira-specific init steps. Section jira = sections.get("link", "jira"); // Jira-specific link expansions } } MANIFEST.MF: ------------ Gerrit-InitStep: com.googlesource.plugins.jira.InitJira Change-Id: I84fcb9acd6845c706961657219a14570e2060b0f Signed-off-by: Luca Milanesio <luca.milanesio@gmail.com>
This commit is contained in:
parent
f121c1bff1
commit
737285df3f
@ -176,6 +176,69 @@ reload::
|
||||
To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
|
||||
command can be used.
|
||||
|
||||
[[init_step]]
|
||||
Init step
|
||||
~~~~~~~~~
|
||||
|
||||
Plugins can contribute their own "init step" during the Gerrit init
|
||||
wizard. This is useful for guiding the Gerrit administrator through
|
||||
the settings needed by the plugin to work propertly.
|
||||
|
||||
For instance plugins to integrate Jira issues to Gerrit changes may
|
||||
contribute their own "init step" to allow configuring the Jira URL,
|
||||
credentials and possibly verify connectivity to validate them.
|
||||
|
||||
====
|
||||
Gerrit-InitStep: tld.example.project.MyInitStep
|
||||
====
|
||||
|
||||
MyInitStep needs to follow the standard Gerrit InitStep syntax
|
||||
and behaviour: writing to the console using the injected ConsoleUI
|
||||
and accessing / changing configuration settings using Section.Factory.
|
||||
|
||||
In addition to the standard Gerrit init injections, plugins receive
|
||||
the @PluginName String injection containing their own plugin name.
|
||||
|
||||
Bear in mind that the Plugin's InitStep class will be loaded but
|
||||
the standard Gerrit runtime environment is not available and the plugin's
|
||||
own Guice modules were not initialized.
|
||||
This means the InitStep for a plugin is not executed in the same way that
|
||||
the plugin executes within the server, and may mean a plugin author cannot
|
||||
trivially reuse runtime code during init.
|
||||
|
||||
For instance a plugin that wants to verify connectivity may need to statically
|
||||
call the constructor of their connection class, passing in values obtained
|
||||
from the Section.Factory rather than from an injected Config object.
|
||||
|
||||
Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
|
||||
the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
|
||||
and before the DB Schema initialization or upgrade.
|
||||
Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
|
||||
objects injected at startup.
|
||||
|
||||
====
|
||||
public class MyInitStep implements InitStep {
|
||||
private final ConsoleUI ui;
|
||||
private final Section.Factory sections;
|
||||
private final String pluginName;
|
||||
|
||||
@Inject
|
||||
public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
|
||||
this.ui = ui;
|
||||
this.sections = sections;
|
||||
this.pluginName = pluginName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
ui.header("\nMy plugin");
|
||||
|
||||
Section mySection = getSection("myplugin", null);
|
||||
mySection.string("Link name", "linkname", "MyLink");
|
||||
}
|
||||
}
|
||||
====
|
||||
|
||||
[[classpath]]
|
||||
Classpath
|
||||
---------
|
||||
|
@ -32,8 +32,8 @@ class InitAuth implements InitStep {
|
||||
@Inject
|
||||
InitAuth(final ConsoleUI ui, final Section.Factory sections) {
|
||||
this.ui = ui;
|
||||
this.auth = sections.get("auth");
|
||||
this.ldap = sections.get("ldap");
|
||||
this.auth = sections.get("auth", null);
|
||||
this.ldap = sections.get("ldap", null);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
@ -31,7 +31,7 @@ class InitCache implements InitStep {
|
||||
@Inject
|
||||
InitCache(final SitePaths site, final Section.Factory sections) {
|
||||
this.site = site;
|
||||
this.cache = sections.get("cache");
|
||||
this.cache = sections.get("cache", null);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
@ -44,7 +44,7 @@ class InitContainer implements InitStep {
|
||||
final Section.Factory sections) {
|
||||
this.ui = ui;
|
||||
this.site = site;
|
||||
this.container = sections.get("container");
|
||||
this.container = sections.get("container", null);
|
||||
}
|
||||
|
||||
public void run() throws FileNotFoundException, IOException {
|
||||
|
@ -47,7 +47,7 @@ class InitDatabase implements InitStep {
|
||||
this.ui = ui;
|
||||
this.site = site;
|
||||
this.libraries = libraries;
|
||||
this.database = sections.get("database");
|
||||
this.database = sections.get("database", null);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
@ -31,7 +31,7 @@ class InitGitManager implements InitStep {
|
||||
@Inject
|
||||
InitGitManager(final ConsoleUI ui, final Section.Factory sections) {
|
||||
this.ui = ui;
|
||||
this.gerrit = sections.get("gerrit");
|
||||
this.gerrit = sections.get("gerrit", null);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
@ -48,8 +48,8 @@ class InitHttpd implements InitStep {
|
||||
this.ui = ui;
|
||||
this.site = site;
|
||||
this.flags = flags;
|
||||
this.httpd = sections.get("httpd");
|
||||
this.gerrit = sections.get("gerrit");
|
||||
this.httpd = sections.get("httpd", null);
|
||||
this.gerrit = sections.get("gerrit", null);
|
||||
}
|
||||
|
||||
public void run() throws IOException, InterruptedException {
|
||||
|
@ -0,0 +1,132 @@
|
||||
// Copyright (C) 2012 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 com.google.gerrit.extensions.annotations.PluginName;
|
||||
import com.google.gerrit.pgm.util.ConsoleUI;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
@Singleton
|
||||
public class InitPluginStepsLoader {
|
||||
private final File pluginsDir;
|
||||
private final Injector initInjector;
|
||||
final ConsoleUI ui;
|
||||
|
||||
@Inject
|
||||
public InitPluginStepsLoader(final ConsoleUI ui, final SitePaths sitePaths,
|
||||
final Injector initInjector) {
|
||||
this.pluginsDir = sitePaths.plugins_dir;
|
||||
this.initInjector = initInjector;
|
||||
this.ui = ui;
|
||||
}
|
||||
|
||||
public Collection<InitStep> getInitSteps() {
|
||||
List<File> jars = scanJarsInPluginsDirectory();
|
||||
ArrayList<InitStep> pluginsInitSteps = new ArrayList<InitStep>();
|
||||
|
||||
for (File jar : jars) {
|
||||
InitStep init = loadInitStep(jar);
|
||||
if (init != null) {
|
||||
pluginsInitSteps.add(init);
|
||||
}
|
||||
}
|
||||
return pluginsInitSteps;
|
||||
}
|
||||
|
||||
private InitStep loadInitStep(File jar) {
|
||||
try {
|
||||
ClassLoader pluginLoader =
|
||||
new URLClassLoader(new URL[] {jar.toURI().toURL()},
|
||||
InitPluginStepsLoader.class.getClassLoader());
|
||||
JarFile jarFile = new JarFile(jar);
|
||||
Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
|
||||
String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
|
||||
if (initClassName == null) {
|
||||
return null;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends InitStep> initStepClass =
|
||||
(Class<? extends InitStep>) pluginLoader.loadClass(initClassName);
|
||||
return getPluginInjector(jar).getInstance(initStepClass);
|
||||
} catch (ClassCastException e) {
|
||||
ui.message(
|
||||
"WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
|
||||
jar.getName(), InitStep.class.getName(), e.getMessage());
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
ui.message(
|
||||
"WARN: Cannot load and get plugin init step for %s (Exception: %s)",
|
||||
jar, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Injector getPluginInjector(File jarFile) {
|
||||
String jarFileName = jarFile.getName();
|
||||
final String pluginName =
|
||||
jarFileName.substring(0, jarFileName.lastIndexOf('.'));
|
||||
return initInjector.createChildInjector(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(String.class).annotatedWith(PluginName.class).toInstance(
|
||||
pluginName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<File> scanJarsInPluginsDirectory() {
|
||||
if (pluginsDir == null || !pluginsDir.exists()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
File[] matches = pluginsDir.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
String n = pathname.getName();
|
||||
return (n.endsWith(".jar") && pathname.isFile());
|
||||
}
|
||||
});
|
||||
if (matches == null) {
|
||||
ui.message("WARN: Cannot list %s", pluginsDir.getAbsolutePath());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Arrays.sort(matches, new Comparator<File>() {
|
||||
@Override
|
||||
public int compare(File o1, File o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
return Arrays.asList(matches);
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
@ -39,17 +40,24 @@ public class InitPlugins implements InitStep {
|
||||
|
||||
private final ConsoleUI ui;
|
||||
private final SitePaths site;
|
||||
private InitPluginStepsLoader pluginLoader;
|
||||
|
||||
@Inject
|
||||
InitPlugins(final ConsoleUI ui, final SitePaths site) {
|
||||
InitPlugins(final ConsoleUI ui, final SitePaths site, InitPluginStepsLoader pluginLoader) {
|
||||
this.ui = ui;
|
||||
this.site = site;
|
||||
this.pluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
ui.header("Plugins");
|
||||
|
||||
installPlugins();
|
||||
initPlugins();
|
||||
}
|
||||
|
||||
private void installPlugins() throws IOException {
|
||||
final File myWar;
|
||||
try {
|
||||
myWar = GerritLauncher.getDistributionArchive();
|
||||
@ -127,6 +135,12 @@ public class InitPlugins implements InitStep {
|
||||
}
|
||||
}
|
||||
|
||||
private void initPlugins() throws Exception {
|
||||
for (InitStep initStep : pluginLoader.getInitSteps()) {
|
||||
initStep.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getVersion(final File plugin) throws IOException {
|
||||
final JarFile jarFile = new JarFile(plugin);
|
||||
try {
|
||||
|
@ -34,7 +34,7 @@ class InitSendEmail implements InitStep {
|
||||
InitSendEmail(final ConsoleUI ui, final SitePaths site,
|
||||
final Section.Factory sections) {
|
||||
this.ui = ui;
|
||||
this.sendemail = sections.get("sendemail");
|
||||
this.sendemail = sections.get("sendemail", null);
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class InitSshd implements InitStep {
|
||||
this.ui = ui;
|
||||
this.site = site;
|
||||
this.libraries = libraries;
|
||||
this.sshd = sections.get("sshd");
|
||||
this.sshd = sections.get("sshd", null);
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
|
@ -25,52 +25,58 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Helper to edit a section of the configuration files. */
|
||||
class Section {
|
||||
interface Factory {
|
||||
Section get(String name);
|
||||
public class Section {
|
||||
public interface Factory {
|
||||
Section get(@Assisted("section") String section,
|
||||
@Assisted("subsection") String subsection);
|
||||
}
|
||||
|
||||
private final InitFlags flags;
|
||||
private final SitePaths site;
|
||||
private final ConsoleUI ui;
|
||||
private final String section;
|
||||
private final String subsection;
|
||||
|
||||
@Inject
|
||||
Section(final InitFlags flags, final SitePaths site, final ConsoleUI ui,
|
||||
@Assisted final String section) {
|
||||
public Section(final InitFlags flags, final SitePaths site,
|
||||
final ConsoleUI ui, @Assisted("section") final String section,
|
||||
@Assisted("subsection") @Nullable final String subsection) {
|
||||
this.flags = flags;
|
||||
this.site = site;
|
||||
this.ui = ui;
|
||||
this.section = section;
|
||||
this.subsection = subsection;
|
||||
}
|
||||
|
||||
String get(String name) {
|
||||
return flags.cfg.getString(section, null, name);
|
||||
}
|
||||
|
||||
void set(final String name, final String value) {
|
||||
public void set(final String name, final String value) {
|
||||
final ArrayList<String> all = new ArrayList<String>();
|
||||
all.addAll(Arrays.asList(flags.cfg.getStringList(section, null, name)));
|
||||
all.addAll(Arrays.asList(flags.cfg.getStringList(section, subsection, name)));
|
||||
|
||||
if (value != null) {
|
||||
if (all.size() == 0 || all.size() == 1) {
|
||||
flags.cfg.setString(section, null, name, value);
|
||||
flags.cfg.setString(section, subsection, name, value);
|
||||
} else {
|
||||
all.set(0, value);
|
||||
flags.cfg.setStringList(section, null, name, all);
|
||||
flags.cfg.setStringList(section, subsection, name, all);
|
||||
}
|
||||
|
||||
} else if (all.size() == 0) {
|
||||
} else if (all.size() == 1) {
|
||||
flags.cfg.unset(section, null, name);
|
||||
flags.cfg.unset(section, subsection, name);
|
||||
} else {
|
||||
all.remove(0);
|
||||
flags.cfg.setStringList(section, null, name, all);
|
||||
flags.cfg.setStringList(section, subsection, name, all);
|
||||
}
|
||||
}
|
||||
|
||||
<T extends Enum<?>> void set(final String name, final T value) {
|
||||
public <T extends Enum<?>> void set(final String name, final T value) {
|
||||
if (value != null) {
|
||||
set(name, value.name());
|
||||
} else {
|
||||
@ -78,15 +84,15 @@ class Section {
|
||||
}
|
||||
}
|
||||
|
||||
void unset(String name) {
|
||||
public void unset(String name) {
|
||||
set(name, (String) null);
|
||||
}
|
||||
|
||||
String string(final String title, final String name, final String dv) {
|
||||
public String string(final String title, final String name, final String dv) {
|
||||
return string(title, name, dv, false);
|
||||
}
|
||||
|
||||
String string(final String title, final String name, final String dv,
|
||||
public String string(final String title, final String name, final String dv,
|
||||
final boolean nullIfDefault) {
|
||||
final String ov = get(name);
|
||||
String nv = ui.readString(ov != null ? ov : dv, "%s", title);
|
||||
@ -99,19 +105,19 @@ class Section {
|
||||
return nv;
|
||||
}
|
||||
|
||||
File path(final String title, final String name, final String defValue) {
|
||||
public File path(final String title, final String name, final String defValue) {
|
||||
return site.resolve(string(title, name, defValue));
|
||||
}
|
||||
|
||||
<T extends Enum<?>> T select(final String title, final String name,
|
||||
public <T extends Enum<?>> T select(final String title, final String name,
|
||||
final T defValue) {
|
||||
return select(title, name, defValue, false);
|
||||
}
|
||||
|
||||
<T extends Enum<?>> T select(final String title, final String name,
|
||||
public <T extends Enum<?>> T select(final String title, final String name,
|
||||
final T defValue, final boolean nullIfDefault) {
|
||||
final boolean set = get(name) != null;
|
||||
T oldValue = ConfigUtil.getEnum(flags.cfg, section, null, name, defValue);
|
||||
T oldValue = ConfigUtil.getEnum(flags.cfg, section, subsection, name, defValue);
|
||||
T newValue = ui.readEnum(oldValue, "%s", title);
|
||||
if (nullIfDefault && newValue == defValue) {
|
||||
newValue = null;
|
||||
@ -126,7 +132,7 @@ class Section {
|
||||
return newValue;
|
||||
}
|
||||
|
||||
String select(final String title, final String name, final String dv,
|
||||
public String select(final String title, final String name, final String dv,
|
||||
Set<String> allowedValues) {
|
||||
final String ov = get(name);
|
||||
String nv = ui.readString(ov != null ? ov : dv, allowedValues, "%s", title);
|
||||
@ -136,16 +142,16 @@ class Section {
|
||||
return nv;
|
||||
}
|
||||
|
||||
String password(final String username, final String password) {
|
||||
public String password(final String username, final String password) {
|
||||
final String ov = getSecure(password);
|
||||
|
||||
String user = flags.sec.getString(section, null, username);
|
||||
String user = flags.sec.getString(section, subsection, username);
|
||||
if (user == null) {
|
||||
user = get(username);
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
flags.sec.unset(section, null, password);
|
||||
flags.sec.unset(section, subsection, password);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -165,15 +171,15 @@ class Section {
|
||||
return nv;
|
||||
}
|
||||
|
||||
String getSecure(String name) {
|
||||
return flags.sec.getString(section, null, name);
|
||||
public String getSecure(String name) {
|
||||
return flags.sec.getString(section, subsection, name);
|
||||
}
|
||||
|
||||
void setSecure(String name, String value) {
|
||||
public void setSecure(String name, String value) {
|
||||
if (value != null) {
|
||||
flags.sec.setString(section, null, name, value);
|
||||
flags.sec.setString(section, subsection, name, value);
|
||||
} else {
|
||||
flags.sec.unset(section, null, name);
|
||||
flags.sec.unset(section, subsection, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ class UpgradeFrom2_0_x implements InitStep {
|
||||
|
||||
final Properties oldprop = readGerritServerProperties();
|
||||
if (oldprop != null) {
|
||||
final Section database = sections.get("database");
|
||||
final Section database = sections.get("database", null);
|
||||
|
||||
String url = oldprop.getProperty("url");
|
||||
if (url != null && !convertUrl(database, url)) {
|
||||
|
@ -72,8 +72,8 @@ public class UpgradeFrom2_0_xTest extends InitTestCase {
|
||||
final ConsoleUI ui = createStrictMock(ConsoleUI.class);
|
||||
Section.Factory sections = new Section.Factory() {
|
||||
@Override
|
||||
public Section get(String name) {
|
||||
return new Section(flags, site, ui, name);
|
||||
public Section get(String name, String subsection) {
|
||||
return new Section(flags, site, ui, name, subsection);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -45,6 +45,18 @@ limitations under the License.
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.gerrit</groupId>
|
||||
<artifactId>gerrit-pgm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
|
Loading…
Reference in New Issue
Block a user