d500500b5e
Per [1] the correct URL is <bucket>.storage.googleapis.com/<object>. Update existing references to project resources to use this domain name instead of the old name. [1] https://developers.google.com/storage/docs/reference-uris Change-Id: I3788cfb4504b3908d2b5eccbdf52beffc3d18387
487 lines
16 KiB
Plaintext
487 lines
16 KiB
Plaintext
Gerrit Code Review - Plugin Development
|
|
=======================================
|
|
|
|
The Gerrit server functionality can be extended by installing plugins.
|
|
This page describes how plugins for Gerrit can be developed.
|
|
|
|
Depending on how tightly the extension code is coupled with the Gerrit
|
|
server code, there is a distinction between `plugins` and `extensions`.
|
|
|
|
[[plugin]]
|
|
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.
|
|
|
|
[[extension]]
|
|
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 visibility reduces the extension's
|
|
dependencies, enabling it to be compatible across a wider range
|
|
of server versions.
|
|
|
|
Most of this documentation refers to either type as a plugin.
|
|
|
|
[[getting-started]]
|
|
Getting started
|
|
---------------
|
|
|
|
To get started with the development of a plugin there are two
|
|
recommended ways:
|
|
|
|
. use the Gerrit Plugin Maven archetype to create a new plugin project:
|
|
+
|
|
With the Gerrit Plugin Maven archetype you can create a skeleton for a
|
|
plugin project.
|
|
+
|
|
----
|
|
mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
|
|
-DarchetypeArtifactId=gerrit-plugin-archetype \
|
|
-DarchetypeVersion=2.5-SNAPSHOT \
|
|
-DgroupId=com.google.gerrit \
|
|
-DartifactId=testPlugin
|
|
----
|
|
+
|
|
Maven will ask for additional properties and then create the plugin in
|
|
the current directory. To change the default property values answer 'n'
|
|
when Maven asks to confirm the properties configuration. It will then
|
|
ask again for all properties including those with predefined default
|
|
values.
|
|
|
|
. clone the sample helloworld plugin:
|
|
+
|
|
This is a Maven project that adds an SSH command to Gerrit to print
|
|
out a hello world message. It can be taken as an example to develop
|
|
an own plugin.
|
|
+
|
|
----
|
|
$ git clone https://gerrit.googlesource.com/plugins/helloworld
|
|
----
|
|
+
|
|
When starting from this example one should take care to adapt the
|
|
`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
|
|
the plugin is developed. If the plugin is developed for a released
|
|
Gerrit version (no `SNAPSHOT` version) then the URL for the
|
|
`gerrit-api-repository` in the `pom.xml` needs to be changed to
|
|
`https://gerrit-api.storage.googleapis.com/release/`.
|
|
|
|
[[API]]
|
|
API
|
|
---
|
|
|
|
There are two different API formats offered against which plugins can
|
|
be developed:
|
|
|
|
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 may provide optional description information with standard
|
|
manifest fields:
|
|
|
|
====
|
|
Implementation-Title: Example plugin showing examples
|
|
Implementation-Version: 1.0
|
|
Implementation-Vendor: Example, Inc.
|
|
Implementation-URL: http://example.com/opensource/plugin-foo/
|
|
====
|
|
|
|
ApiType
|
|
~~~~~~~
|
|
|
|
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.
|
|
|
|
====
|
|
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]]
|
|
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.
|
|
|
|
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
|
|
---------
|
|
|
|
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]]
|
|
SSH Commands
|
|
------------
|
|
|
|
Plugins may provide commands that can be accessed through the SSH
|
|
interface (extensions do not have this option).
|
|
|
|
Command implementations must extend the base class SshCommand:
|
|
|
|
====
|
|
import com.google.gerrit.sshd.SshCommand;
|
|
|
|
class PrintHello extends SshCommand {
|
|
protected abstract void run() {
|
|
stdout.print("Hello\n");
|
|
}
|
|
}
|
|
====
|
|
|
|
If no Guice modules are declared in the manifest, SSH commands may
|
|
use auto-registration by providing an `@Export` annotation:
|
|
|
|
====
|
|
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]]
|
|
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");
|
|
}
|
|
}
|
|
====
|
|
|
|
The auto registration only works for standard servlet mappings like
|
|
`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
|
|
to register the HTTP servlets and declare it explicitly 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
|
|
----
|
|
|
|
[[data-directory]]
|
|
Data Directory
|
|
--------------
|
|
|
|
Plugins can request a data directory with a `@PluginData` File
|
|
dependency. A data directory will be created automatically by the
|
|
server in `$site_path/data/$plugin_name` and passed to the plugin.
|
|
|
|
Plugins can use this to store any data they want.
|
|
|
|
====
|
|
@Inject
|
|
MyType(@PluginData java.io.File myDir) {
|
|
new FileInputStream(new File(myDir, "my.config"));
|
|
}
|
|
====
|
|
|
|
[[documentation]]
|
|
Documentation
|
|
-------------
|
|
|
|
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`. This prefix is
|
|
configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
|
|
|
|
Documentation files under `Documentation/` directory in the JAR
|
|
will be available as `/plugins/helloworld/Documentation/resource`. This
|
|
prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
|
|
attribute.
|
|
|
|
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`.
|
|
|
|
[[macros]]
|
|
Within the Markdown documentation files macros can be used that allow
|
|
to write documentation with reasonably accurate examples that adjust
|
|
automatically based on the installation.
|
|
|
|
The following macros are supported:
|
|
|
|
[width="40%",options="header"]
|
|
|===================================================
|
|
|Macro | Replacement
|
|
|@PLUGIN@ | name of the plugin
|
|
|@URL@ | Gerrit Web URL
|
|
|@SSH_HOST@ | SSH Host
|
|
|@SSH_PORT@ | SSH Port
|
|
|===================================================
|
|
|
|
The macros will be replaced when the documentation files are rendered
|
|
from Markdown to HTML.
|
|
|
|
Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
|
|
even if there is an expansion for `KEEP` in the future.
|
|
|
|
[[auto-index]]
|
|
Automatic Index
|
|
~~~~~~~~~~~~~~~
|
|
|
|
If a plugin does not handle its `/` URL itself, Gerrit will
|
|
redirect clients to the plugin's `/Documentation/index.html`.
|
|
Requests for `/Documentation/` (bare directory) will also redirect
|
|
to `/Documentation/index.html`.
|
|
|
|
If neither resource `Documentation/index.html` or
|
|
`Documentation/index.md` exists in the plugin JAR, Gerrit will
|
|
automatically generate an index page for the plugin's documentation
|
|
tree by scanning every `*.md` and `*.html` file in the Documentation/
|
|
directory.
|
|
|
|
For any discovered Markdown (`*.md`) file, Gerrit will parse the
|
|
header of the file and extract the first level one title. This
|
|
title text will be used as display text for a link to the HTML
|
|
version of the page.
|
|
|
|
For any discovered HTML (`*.html`) file, Gerrit will use the name
|
|
of the file, minus the `*.html` extension, as the link text. Any
|
|
hyphens in the file name will be replaced with spaces.
|
|
|
|
If a discovered file name beings with `cmd-` it will be clustered
|
|
into a 'Commands' section of the generated index page. All other
|
|
files are clustered under a 'Documentation' section.
|
|
|
|
Some optional information from the manifest is extracted and
|
|
displayed as part of the index page, if present in the manifest:
|
|
|
|
[width="40%",options="header"]
|
|
|===================================================
|
|
|Field | Source Attribute
|
|
|Name | Implementation-Title
|
|
|Vendor | Implementation-Vendor
|
|
|Version | Implementation-Version
|
|
|URL | Implementation-URL
|
|
|API Version | Gerrit-ApiVersion
|
|
|===================================================
|
|
|
|
[[deployment]]
|
|
Deployment
|
|
----------
|
|
|
|
Compiled plugins and extensions can be deployed to a running Gerrit
|
|
server using the link:cmd-plugin-install.html[plugin install] command.
|
|
|
|
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].
|
|
|
|
For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
|
|
command can be used.
|
|
|
|
Disabled plugins can be re-enabled using the
|
|
link:cmd-plugin-enable.html[plugin enable] command.
|
|
|
|
GERRIT
|
|
------
|
|
Part of link:index.html[Gerrit Code Review]
|