Expose only extension-api to extensions
Unless a plugin declares "Gerrit-ApiType: plugin" in its manifest, assume it is an extension and only make the gerrit-extension-api available to it through the ClassLoader. For non-plugins, do not make any Guice bindings available from the server. This further restricts what an extension can see and do with the system internals. Change-Id: Ia38336c42786afb1419d64c06b0d908ae92a64d1
This commit is contained in:
parent
6fa1e9121d
commit
da4919abf6
@ -1,61 +1,296 @@
|
||||
Gerrit Code Review - Plugin Development
|
||||
=======================================
|
||||
|
||||
A plugin in gerrit is tightly coupled code that runs in the same
|
||||
JVM as gerrit. It has full access to all gerrit internals. Plugins
|
||||
are coupled to a specific major.minor gerrit version.
|
||||
A plugin in Gerrit is tightly coupled code that runs in the same
|
||||
JVM as Gerrit. It has full access to all server internals. Plugins
|
||||
are tightly coupled to a specific major.minor server version and
|
||||
may require source code changes to compile against a different
|
||||
server version.
|
||||
|
||||
An extension in Gerrit runs inside of the same JVM as Gerrit
|
||||
in the same way as a plugin, but has limited visibility to the
|
||||
server's internals. The limited visiblity reduces the extension's
|
||||
dependencies, enabling it to be compatiable across a wider range
|
||||
of server versions.
|
||||
|
||||
Most of this documentation refers to either type as a plugin.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
To start development, download the sample maven project, which downloads the
|
||||
following dependencies:
|
||||
To start development, download the sample maven project, which
|
||||
downloads the dependencies file that matches the war file to develop
|
||||
against. Dependencies are offered in two different formats:
|
||||
|
||||
* gerrit-sdk.jar file that matches the war file to develop against
|
||||
gerrit-extension-api.jar::
|
||||
A stable but thin interface. Suitable for extensions that need
|
||||
to be notified of events, but do not require tight coupling to
|
||||
the internals of Gerrit. Extensions built against this API can
|
||||
expect to be binary compatible across a wide range of server
|
||||
versions.
|
||||
|
||||
gerrit-plugin-api.jar::
|
||||
The complete internals of the Gerrit server, permitting a
|
||||
plugin to tightly couple itself and provide additional
|
||||
functionality that is not possible as an extension. Plugins
|
||||
built against this API are expected to break at the source
|
||||
code level between every major.minor Gerrit release. A plugin
|
||||
that compiles against 2.5 will probably need source code level
|
||||
changes to work with 2.6, 2.7, and so on.
|
||||
|
||||
Manifest
|
||||
--------
|
||||
|
||||
Plugins need to include the following data in the jar manifest file:
|
||||
Plugins may provide optional description information with standard
|
||||
manifest fields:
|
||||
|
||||
Gerrit-Module = pkg.class
|
||||
====
|
||||
Implementation-Title: Example plugin showing examples
|
||||
Implementation-Version: 1.0
|
||||
Implementation-Vendor: Example, Inc.
|
||||
Implementation-URL: http://example.com/opensource/plugin-foo/
|
||||
====
|
||||
|
||||
Optionally include:
|
||||
ApiType
|
||||
~~~~~~~
|
||||
|
||||
Gerrit-ReloadMode = 'reload' (default) or 'restart'
|
||||
Plugins using the tightly coupled `gerrit-plugin-api.jar` must
|
||||
declare this API dependency in the manifest to gain access to server
|
||||
internals. If no Gerrit-ApiType is specified the stable `extension`
|
||||
API will be assumed. This may cause ClassNotFoundExceptions when
|
||||
loading a plugin that needs the plugin API.
|
||||
|
||||
If the plugin holds an exclusive resource that must be released before loading
|
||||
the plugin again, ReloadMode must be set to 'restart'. Otherwise 'reload' is
|
||||
sufficient.
|
||||
====
|
||||
Gerrit-ApiType: plugin
|
||||
====
|
||||
|
||||
Explicit Registration
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Plugins that use explicit Guice registration must name the Guice
|
||||
modules in the manifest. Up to three modules can be named in the
|
||||
manifest. Gerrit-Module supplies bindings to the core server;
|
||||
Gerrit-SshModule supplies SSH commands to the SSH server (if
|
||||
enabled); Gerrit-HttpModule supplies servlets and filters to the HTTP
|
||||
server (if enabled). If no modules are named automatic registration
|
||||
will be performed by scanning all classes in the plugin JAR for
|
||||
`@Listen` and `@Export("")` annotations.
|
||||
|
||||
====
|
||||
Gerrit-Module: tld.example.project.CoreModuleClassName
|
||||
Gerrit-SshModule: tld.example.project.SshModuleClassName
|
||||
Gerrit-HttpModule: tld.example.project.HttpModuleClassName
|
||||
====
|
||||
|
||||
Reload Method
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
If a plugin holds an exclusive resource that must be released before
|
||||
loading the plugin again (for example listening on a network port or
|
||||
acquiring a file lock) the manifest must declare Gerrit-ReloadMode
|
||||
to be `restart`. Otherwise the preferred method of `reload` will
|
||||
be used, as it enables the server to hot-patch an updated plugin
|
||||
with no down time.
|
||||
|
||||
====
|
||||
Gerrit-ReloadMode: restart
|
||||
====
|
||||
|
||||
In either mode ('restart' or 'reload') any plugin or extension can
|
||||
be updated without restarting the Gerrit server. The difference is
|
||||
how Gerrit handles the upgrade:
|
||||
|
||||
restart::
|
||||
The old plugin is completely stopped. All registrations of SSH
|
||||
commands and HTTP servlets are removed. All registrations of any
|
||||
extension points are removed. All registered LifecycleListeners
|
||||
have their `stop()` method invoked in reverse order. The new
|
||||
plugin is started, and registrations are made from the new
|
||||
plugin. There is a brief window where neither the old nor the
|
||||
new plugin is connected to the server. This means SSH commands
|
||||
and HTTP servlets will return not found errors, and the plugin
|
||||
will not be notified of events that occurred during the restart.
|
||||
|
||||
reload::
|
||||
The new plugin is started. Its LifecycleListeners are permitted
|
||||
to perform their `start()` methods. All SSH and HTTP registrations
|
||||
are atomically swapped out from the old plugin to the new plugin,
|
||||
ensuring the server never returns a not found error. All extension
|
||||
point listeners are atomically swapped out from the old plugin to
|
||||
the new plugin, ensuring no events are missed (however some events
|
||||
may still route to the old plugin if the swap wasn't complete yet).
|
||||
The old plugin is stopped.
|
||||
|
||||
Classpath
|
||||
---------
|
||||
|
||||
Each plugin is loaded into its own ClassLoader, isolating plugins
|
||||
from each other. A plugin or extension inherits the Java runtime
|
||||
and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
|
||||
from the hosting server.
|
||||
|
||||
Plugins are loaded from a single JAR file. If a plugin needs
|
||||
additional libraries, it must include those dependencies within
|
||||
its own JAR. Plugins built using Maven may be able to use the
|
||||
link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
|
||||
to package additional dependencies. Relocating (or renaming) classes
|
||||
should not be necessary due to the ClassLoader isolation.
|
||||
|
||||
SSH Commands
|
||||
------------
|
||||
|
||||
Plugins may provide commands that can be accessed through the SSH interface.
|
||||
These commands register themselves as a part of link:cmd-index.html[SSH Commands].
|
||||
Plugins may provide commands that can be accessed through the SSH
|
||||
interface (extensions do not have this option).
|
||||
|
||||
Each of the plugin commands needs to extend SshCommand.
|
||||
Command implementations must extend the base class SshCommand:
|
||||
|
||||
Any plugin which implements at least one ssh command needs to also provide a
|
||||
class which extends the PluginCommandModule in order to register the ssh
|
||||
command(s) in its configure method which must be overriden.
|
||||
====
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
|
||||
Registering is done by calling:
|
||||
class PrintHello extends SshCommand {
|
||||
protected abstract void run() {
|
||||
stdout.print("Hello\n");
|
||||
}
|
||||
}
|
||||
====
|
||||
|
||||
command(String commandName).to(ClassName<? extends SshCommand> klass)
|
||||
If no Guice modules are declared in the manifest, SSH commands may
|
||||
use auto-registration by providing an @Export annotatation:
|
||||
|
||||
====
|
||||
import com.google.gerrit.extensions.annotations.Export;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
|
||||
@Export("print")
|
||||
class PrintHello extends SshCommand {
|
||||
protected abstract void run() {
|
||||
stdout.print("Hello\n");
|
||||
}
|
||||
}
|
||||
====
|
||||
|
||||
If explicit registration is being used, a Guice module must be
|
||||
supplied to register the SSH command and declared in the manifest
|
||||
with the `Gerrit-SshModule` attribute:
|
||||
|
||||
====
|
||||
import com.google.gerrit.sshd.PluginCommandModule;
|
||||
|
||||
class MyCommands extends PluginCommandModule {
|
||||
protected void configureCommands() {
|
||||
command("print").to(PrintHello.class);
|
||||
}
|
||||
}
|
||||
====
|
||||
|
||||
For a plugin installed as name `helloworld`, the command implemented
|
||||
by PrintHello class will be available to users as:
|
||||
|
||||
----
|
||||
$ ssh -P 29418 review.example.com helloworld print
|
||||
----
|
||||
|
||||
HTTP Servlets
|
||||
-------------
|
||||
|
||||
Plugins or extensions may register additional HTTP servlets, and
|
||||
wrap them with HTTP filters.
|
||||
|
||||
Servlets may use auto-registration to declare the URL they handle:
|
||||
|
||||
====
|
||||
import com.google.gerrit.extensions.annotations.Export;
|
||||
import com.google.inject.Singleton;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Export("/print")
|
||||
@Singleton
|
||||
class HelloServlet extends HttpServlet {
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||
res.setContentType("text/plain");
|
||||
res.setCharacterEncoding("UTF-8");
|
||||
res.getWriter().write("Hello");
|
||||
}
|
||||
}
|
||||
====
|
||||
|
||||
If explicit registration is being used, a Guice ServletModule must
|
||||
be supplied to register the HTTP servlets, and the module must be
|
||||
declared in the manifest with the `Gerrit-HttpModule` attribute:
|
||||
|
||||
====
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
class MyWebUrls extends ServletModule {
|
||||
protected void configureServlets() {
|
||||
serve("/print").with(HelloServlet.class);
|
||||
}
|
||||
}
|
||||
====
|
||||
|
||||
For a plugin installed as name `helloworld`, the servlet implemented
|
||||
by HelloServlet class will be available to users as:
|
||||
|
||||
----
|
||||
$ curl http://review.example.com/plugins/helloworld/print
|
||||
----
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Place files into Documentation/ or static/ and package them into the plugin jar
|
||||
to access them in a browser via <canonicalWebURL>/plugins/<pluginName>/...
|
||||
If a plugin does not register a filter or servlet to handle URLs
|
||||
`/Documentation/*` or `/static/*`, the core Gerrit server will
|
||||
automatically export these resources over HTTP from the plugin JAR.
|
||||
|
||||
Static resources under `static/` directory in the JAR will be
|
||||
available as `/plugins/helloworld/static/resource`.
|
||||
|
||||
Documentation files under `Documentation/` directory in the JAR
|
||||
will be available as `/plugins/helloworld/Documentation/resource`.
|
||||
|
||||
Documentation may be written in
|
||||
link:http://daringfireball.net/projects/markdown/[Markdown] style
|
||||
if the file name ends with `.md`. Gerrit will automatically convert
|
||||
Markdown to HTML if accessed with extension `.html`.
|
||||
|
||||
Deployment
|
||||
----------
|
||||
|
||||
Deploy plugins into <review_site>/plugins/. The file name in that directory will
|
||||
be the plugin name on the server.
|
||||
Compiled plugins and extensions can be deployed to a
|
||||
running Gerrit server using the SSH interface by any user with
|
||||
link:access-control.html#capability_administrateServer[Administrate Server]
|
||||
capability. Binaries can be specified in three different formats:
|
||||
|
||||
* Absolute file path on the server's host. The server will copy
|
||||
the plugin from this location to its own site path.
|
||||
+
|
||||
----
|
||||
$ ssh -P 29418 localhost gerrit plugin install -n name $(pwd)/my-plugin.jar
|
||||
----
|
||||
|
||||
* Valid URL, including any HTTP or FTP site reachable by the
|
||||
server. The server will download the plugin and save a copy in
|
||||
its own site path.
|
||||
+
|
||||
----
|
||||
$ ssh -P 29418 localhost gerrit plugin install -n name http://build-server/output/our-plugin.jar
|
||||
----
|
||||
|
||||
* As piped input to the plugin install command. The server will
|
||||
copy input until EOF, and save a copy under its own site path.
|
||||
+
|
||||
----
|
||||
$ ssh -P 29418 localhost gerrit plugin install -n name - <target/name-0.1.jar
|
||||
----
|
||||
|
||||
Plugins can also be copied directly into the server's
|
||||
directory at `$site_path/plugins/$name.jar`. The name of
|
||||
the JAR file, minus the `.jar` extension, will be used as the
|
||||
plugin name. Unless disabled, servers periodically scan this
|
||||
directory for updated plugins. The time can be adjusted by
|
||||
link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
|
||||
|
||||
GERRIT
|
||||
------
|
||||
|
@ -56,6 +56,8 @@ limitations under the License.
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<createSourcesJar>true</createSourcesJar>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedClassifierName>all</shadedClassifierName>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -31,9 +31,10 @@ import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.CodeSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
@ -196,7 +197,7 @@ public final class GerritLauncher {
|
||||
throw e;
|
||||
}
|
||||
|
||||
final ArrayList<URL> jars = new ArrayList<URL>();
|
||||
final SortedMap<String, URL> jars = new TreeMap<String, URL>();
|
||||
try {
|
||||
final ZipFile zf = new ZipFile(path);
|
||||
try {
|
||||
@ -208,6 +209,7 @@ public final class GerritLauncher {
|
||||
}
|
||||
|
||||
if (ze.getName().startsWith("WEB-INF/lib/")) {
|
||||
String name = ze.getName().substring("WEB-INF/lib/".length());
|
||||
final File tmp = createTempFile(safeName(ze), ".jar");
|
||||
final FileOutputStream out = new FileOutputStream(tmp);
|
||||
try {
|
||||
@ -224,7 +226,7 @@ public final class GerritLauncher {
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
jars.add(tmp.toURI().toURL());
|
||||
jars.put(name, tmp.toURI().toURL());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@ -237,13 +239,38 @@ public final class GerritLauncher {
|
||||
if (jars.isEmpty()) {
|
||||
return GerritLauncher.class.getClassLoader();
|
||||
}
|
||||
Collections.sort(jars, new Comparator<URL>() {
|
||||
public int compare(URL o1, URL o2) {
|
||||
return o1.toString().compareTo(o2.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return new URLClassLoader(jars.toArray(new URL[jars.size()]));
|
||||
// The extension API needs to be its own ClassLoader, along
|
||||
// with a few of its dependencies. Try to construct this first.
|
||||
List<URL> extapi = new ArrayList<URL>();
|
||||
move(jars, "gerrit-extension-api-", extapi);
|
||||
move(jars, "guice-", extapi);
|
||||
move(jars, "javax.inject-1.jar", extapi);
|
||||
move(jars, "aopalliance-1.0.jar", extapi);
|
||||
move(jars, "guice-servlet-", extapi);
|
||||
move(jars, "servlet-api-", extapi);
|
||||
|
||||
ClassLoader parent = ClassLoader.getSystemClassLoader();
|
||||
if (!extapi.isEmpty()) {
|
||||
parent = new URLClassLoader(
|
||||
extapi.toArray(new URL[extapi.size()]),
|
||||
parent);
|
||||
}
|
||||
return new URLClassLoader(
|
||||
jars.values().toArray(new URL[jars.size()]),
|
||||
parent);
|
||||
}
|
||||
|
||||
private static void move(SortedMap<String, URL> jars,
|
||||
String prefix,
|
||||
List<URL> extapi) {
|
||||
SortedMap<String, URL> matches = jars.tailMap(prefix);
|
||||
if (!matches.isEmpty()) {
|
||||
String first = matches.firstKey();
|
||||
if (first.startsWith(prefix)) {
|
||||
extapi.add(jars.remove(first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String safeName(final ZipEntry ze) {
|
||||
|
@ -20,6 +20,7 @@ import com.google.gerrit.extensions.annotations.PluginData;
|
||||
import com.google.gerrit.extensions.annotations.PluginName;
|
||||
import com.google.gerrit.extensions.registration.RegistrationHandle;
|
||||
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
|
||||
import com.google.gerrit.extensions.systemstatus.ServerInformation;
|
||||
import com.google.gerrit.lifecycle.LifecycleManager;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
@ -40,6 +41,10 @@ import java.util.jar.Manifest;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class Plugin {
|
||||
public static enum ApiType {
|
||||
EXTENSION, PLUGIN;
|
||||
}
|
||||
|
||||
static {
|
||||
// Guice logs warnings about multiple injectors being created.
|
||||
// Silence this in case HTTP plugins are used.
|
||||
@ -47,12 +52,26 @@ public class Plugin {
|
||||
.setLevel(java.util.logging.Level.OFF);
|
||||
}
|
||||
|
||||
static ApiType getApiType(Manifest manifest) throws InvalidPluginException {
|
||||
Attributes main = manifest.getMainAttributes();
|
||||
String v = main.getValue("Gerrit-ApiType");
|
||||
if (Strings.isNullOrEmpty(v)
|
||||
|| ApiType.EXTENSION.name().equalsIgnoreCase(v)) {
|
||||
return ApiType.EXTENSION;
|
||||
} else if (ApiType.PLUGIN.name().equalsIgnoreCase(v)) {
|
||||
return ApiType.PLUGIN;
|
||||
} else {
|
||||
throw new InvalidPluginException("Invalid Gerrit-ApiType: " + v);
|
||||
}
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final File srcJar;
|
||||
private final FileSnapshot snapshot;
|
||||
private final JarFile jarFile;
|
||||
private final Manifest manifest;
|
||||
private final File dataDir;
|
||||
private final ApiType apiType;
|
||||
private final ClassLoader classLoader;
|
||||
private Class<? extends Module> sysModule;
|
||||
private Class<? extends Module> sshModule;
|
||||
@ -70,6 +89,7 @@ public class Plugin {
|
||||
JarFile jarFile,
|
||||
Manifest manifest,
|
||||
File dataDir,
|
||||
ApiType apiType,
|
||||
ClassLoader classLoader,
|
||||
@Nullable Class<? extends Module> sysModule,
|
||||
@Nullable Class<? extends Module> sshModule,
|
||||
@ -80,6 +100,7 @@ public class Plugin {
|
||||
this.jarFile = jarFile;
|
||||
this.manifest = manifest;
|
||||
this.dataDir = dataDir;
|
||||
this.apiType = apiType;
|
||||
this.classLoader = classLoader;
|
||||
this.sysModule = sysModule;
|
||||
this.sshModule = sshModule;
|
||||
@ -94,11 +115,16 @@ public class Plugin {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getVersion() {
|
||||
Attributes main = manifest.getMainAttributes();
|
||||
return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
|
||||
}
|
||||
|
||||
public ApiType getApiType() {
|
||||
return apiType;
|
||||
}
|
||||
|
||||
boolean canReload() {
|
||||
Attributes main = manifest.getMainAttributes();
|
||||
String v = main.getValue("Gerrit-ReloadMode");
|
||||
@ -139,29 +165,33 @@ public class Plugin {
|
||||
}
|
||||
|
||||
if (env.hasSshModule()) {
|
||||
List<Module> modules = Lists.newLinkedList();
|
||||
if (apiType == ApiType.PLUGIN) {
|
||||
modules.add(env.getSshModule());
|
||||
}
|
||||
if (sshModule != null) {
|
||||
sshInjector = sysInjector.createChildInjector(
|
||||
env.getSshModule(),
|
||||
sysInjector.getInstance(sshModule));
|
||||
modules.add(sysInjector.getInstance(sshModule));
|
||||
sshInjector = sysInjector.createChildInjector(modules);
|
||||
manager.add(sshInjector);
|
||||
} else if (auto != null && auto.sshModule != null) {
|
||||
sshInjector = sysInjector.createChildInjector(
|
||||
env.getSshModule(),
|
||||
auto.sshModule);
|
||||
modules.add(auto.sshModule);
|
||||
sshInjector = sysInjector.createChildInjector(modules);
|
||||
manager.add(sshInjector);
|
||||
}
|
||||
}
|
||||
|
||||
if (env.hasHttpModule()) {
|
||||
List<Module> modules = Lists.newLinkedList();
|
||||
if (apiType == ApiType.PLUGIN) {
|
||||
modules.add(env.getHttpModule());
|
||||
}
|
||||
if (httpModule != null) {
|
||||
httpInjector = sysInjector.createChildInjector(
|
||||
env.getHttpModule(),
|
||||
sysInjector.getInstance(httpModule));
|
||||
modules.add(sysInjector.getInstance(httpModule));
|
||||
httpInjector = sysInjector.createChildInjector(modules);
|
||||
manager.add(httpInjector);
|
||||
} else if (auto != null && auto.httpModule != null) {
|
||||
httpInjector = sysInjector.createChildInjector(
|
||||
env.getHttpModule(),
|
||||
auto.httpModule);
|
||||
modules.add(auto.httpModule);
|
||||
httpInjector = sysInjector.createChildInjector(modules);
|
||||
manager.add(httpInjector);
|
||||
}
|
||||
}
|
||||
@ -169,9 +199,19 @@ public class Plugin {
|
||||
manager.start();
|
||||
}
|
||||
|
||||
private Injector newRootInjector(PluginGuiceEnvironment env) {
|
||||
private Injector newRootInjector(final PluginGuiceEnvironment env) {
|
||||
List<Module> modules = Lists.newArrayListWithCapacity(4);
|
||||
modules.add(env.getSysModule());
|
||||
if (apiType == ApiType.PLUGIN) {
|
||||
modules.add(env.getSysModule());
|
||||
} else {
|
||||
modules.add(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ServerInformation.class).toInstance(env.getServerInformation());
|
||||
}
|
||||
});
|
||||
}
|
||||
modules.add(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
@ -19,6 +19,7 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.extensions.annotations.PluginName;
|
||||
import com.google.gerrit.extensions.events.LifecycleListener;
|
||||
import com.google.gerrit.extensions.systemstatus.ServerInformation;
|
||||
import com.google.gerrit.server.config.ConfigUtil;
|
||||
@ -340,7 +341,7 @@ public class PluginLoader implements LifecycleListener {
|
||||
}
|
||||
|
||||
private Plugin loadPlugin(String name, File srcJar, FileSnapshot snapshot)
|
||||
throws IOException, ClassNotFoundException {
|
||||
throws IOException, ClassNotFoundException, InvalidPluginException {
|
||||
File tmp;
|
||||
FileInputStream in = new FileInputStream(srcJar);
|
||||
try {
|
||||
@ -353,13 +354,20 @@ public class PluginLoader implements LifecycleListener {
|
||||
boolean keep = false;
|
||||
try {
|
||||
Manifest manifest = jarFile.getManifest();
|
||||
Plugin.ApiType type = Plugin.getApiType(manifest);
|
||||
Attributes main = manifest.getMainAttributes();
|
||||
String sysName = main.getValue("Gerrit-Module");
|
||||
String sshName = main.getValue("Gerrit-SshModule");
|
||||
String httpName = main.getValue("Gerrit-HttpModule");
|
||||
|
||||
if (!Strings.isNullOrEmpty(sshName) && type != Plugin.ApiType.PLUGIN) {
|
||||
throw new InvalidPluginException(String.format(
|
||||
"Using Gerrit-SshModule requires Gerrit-ApiType: %s",
|
||||
Plugin.ApiType.PLUGIN));
|
||||
}
|
||||
|
||||
URL[] urls = {tmp.toURI().toURL()};
|
||||
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
|
||||
ClassLoader parentLoader = parentFor(type);
|
||||
ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
|
||||
cleanupHandles.put(
|
||||
new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue),
|
||||
@ -372,7 +380,7 @@ public class PluginLoader implements LifecycleListener {
|
||||
return new Plugin(name,
|
||||
srcJar, snapshot,
|
||||
jarFile, manifest,
|
||||
new File(dataDir, name), pluginLoader,
|
||||
new File(dataDir, name), type, pluginLoader,
|
||||
sysModule, sshModule, httpModule);
|
||||
} finally {
|
||||
if (!keep) {
|
||||
@ -381,6 +389,18 @@ public class PluginLoader implements LifecycleListener {
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassLoader parentFor(Plugin.ApiType type)
|
||||
throws InvalidPluginException {
|
||||
switch (type) {
|
||||
case EXTENSION:
|
||||
return PluginName.class.getClassLoader();
|
||||
case PLUGIN:
|
||||
return PluginLoader.class.getClassLoader();
|
||||
default:
|
||||
throw new InvalidPluginException("Unsupported ApiType " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private static String tempNameFor(String name) {
|
||||
SimpleDateFormat fmt = new SimpleDateFormat("yyMMdd_HHmm");
|
||||
return "plugin_" + name + "_" + fmt.format(new Date()) + "_";
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
package com.google.gerrit.sshd;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gerrit.extensions.annotations.PluginName;
|
@ -69,6 +69,6 @@ class SshAutoRegisterModuleGenerator
|
||||
@Override
|
||||
public Module create() throws InvalidPluginException {
|
||||
Preconditions.checkState(command != null, "pluginName must be provided");
|
||||
return this;
|
||||
return !commands.isEmpty() ? this : null;
|
||||
}
|
||||
}
|
||||
|
@ -15,25 +15,44 @@ case $VER in
|
||||
esac
|
||||
URL=s3://gerrit-api@commondatastorage.googleapis.com/$type
|
||||
|
||||
echo "Deploying API $VER to $URL"
|
||||
for module in gerrit-extension-api gerrit-plugin-api
|
||||
do
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=com.google.gerrit \
|
||||
-DartifactId=$module \
|
||||
-Dversion=$VER \
|
||||
-Dpackaging=jar \
|
||||
-Dfile=$module/target/$module-$VER.jar \
|
||||
-DrepositoryId=gerrit-api-repository \
|
||||
-Durl=$URL
|
||||
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=com.google.gerrit \
|
||||
-DartifactId=$module \
|
||||
-Dversion=$VER \
|
||||
-Dpackaging=java-source \
|
||||
-Dfile=$module/target/$module-$VER-sources.jar \
|
||||
-Djava-source=false \
|
||||
-DrepositoryId=gerrit-api-repository \
|
||||
-Durl=$URL
|
||||
done
|
||||
echo "Deploying $type gerrit-extension-api $VER"
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=com.google.gerrit \
|
||||
-DartifactId=gerrit-extension-api \
|
||||
-Dversion=$VER \
|
||||
-Dpackaging=jar \
|
||||
-Dfile=$module/target/gerrit-extension-api-$VER-all.jar \
|
||||
-DrepositoryId=gerrit-api-repository \
|
||||
-Durl=$URL
|
||||
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=com.google.gerrit \
|
||||
-DartifactId=gerrit-extension-api \
|
||||
-Dversion=$VER \
|
||||
-Dpackaging=java-source \
|
||||
-Dfile=$module/target/gerrit-extension-api-$VER-all-sources.jar \
|
||||
-Djava-source=false \
|
||||
-DrepositoryId=gerrit-api-repository \
|
||||
-Durl=$URL
|
||||
|
||||
|
||||
echo "Deploying $type gerrit-plugin-api $VER"
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=com.google.gerrit \
|
||||
-DartifactId=gerrit-plugin-api \
|
||||
-Dversion=$VER \
|
||||
-Dpackaging=jar \
|
||||
-Dfile=$module/target/gerrit-plugin-api-$VER.jar \
|
||||
-DrepositoryId=gerrit-api-repository \
|
||||
-Durl=$URL
|
||||
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=com.google.gerrit \
|
||||
-DartifactId=gerrit-plugin-api \
|
||||
-Dversion=$VER \
|
||||
-Dpackaging=java-source \
|
||||
-Dfile=$module/target/gerrit-plugin-api-$VER-sources.jar \
|
||||
-Djava-source=false \
|
||||
-DrepositoryId=gerrit-api-repository \
|
||||
-Durl=$URL
|
||||
|
Loading…
Reference in New Issue
Block a user