blob: 5af9d1c40ef218354cf35fca83f78ea2484136a7 [file] [log] [blame]
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001Gerrit Code Review - Plugin Development
2=======================================
3
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
5This page describes how plugins for Gerrit can be developed.
6
7Depending on how tightly the extension code is coupled with the Gerrit
8server code, there is a distinction between `plugins` and `extensions`.
9
Edwin Kempinf5a77332012-07-18 11:17:53 +020010[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020011A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070012JVM as Gerrit. It has full access to all server internals. Plugins
13are tightly coupled to a specific major.minor server version and
14may require source code changes to compile against a different
15server version.
16
Edwin Kempinf5a77332012-07-18 11:17:53 +020017[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020018An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070019in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020020server's internals. The limited visibility reduces the extension's
21dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070022of server versions.
23
24Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070025
Edwin Kempinf878c4b2012-07-18 09:34:25 +020026[[getting-started]]
27Getting started
28---------------
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030To get started with the development of a plugin there are two
31recommended ways:
Dave Borowitz5cc8f662012-05-21 09:51:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033. use the Gerrit Plugin Maven archetype to create a new plugin project:
34+
35With the Gerrit Plugin Maven archetype you can create a skeleton for a
36plugin project.
37+
38----
39mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
40 -DarchetypeArtifactId=gerrit-plugin-archetype \
41 -DarchetypeVersion=2.5-SNAPSHOT \
42 -DgroupId=com.google.gerrit \
43 -DartifactId=testPlugin
44----
45+
46Maven will ask for additional properties and then create the plugin in
47the current directory. To change the default property values answer 'n'
48when Maven asks to confirm the properties configuration. It will then
49ask again for all properties including those with predefined default
50values.
51
52. clone the sample helloworld plugin:
53+
54This is a Maven project that adds an SSH command to Gerrit to print
55out a hello world message. It can be taken as an example to develop
56an own plugin.
57+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070058----
59$ git clone https://gerrit.googlesource.com/plugins/helloworld
60----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020061+
62When starting from this example one should take care to adapt the
63`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
64the plugin is developed. If the plugin is developed for a released
65Gerrit version (no `SNAPSHOT` version) then the URL for the
66`gerrit-api-repository` in the `pom.xml` needs to be changed to
67`https://gerrit-api.commondatastorage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070068
Edwin Kempinf878c4b2012-07-18 09:34:25 +020069[[API]]
70API
71---
72
73There are two different API formats offered against which plugins can
74be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070075
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070076gerrit-extension-api.jar::
77 A stable but thin interface. Suitable for extensions that need
78 to be notified of events, but do not require tight coupling to
79 the internals of Gerrit. Extensions built against this API can
80 expect to be binary compatible across a wide range of server
81 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070082
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070083gerrit-plugin-api.jar::
84 The complete internals of the Gerrit server, permitting a
85 plugin to tightly couple itself and provide additional
86 functionality that is not possible as an extension. Plugins
87 built against this API are expected to break at the source
88 code level between every major.minor Gerrit release. A plugin
89 that compiles against 2.5 will probably need source code level
90 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070091
92Manifest
93--------
94
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070095Plugins may provide optional description information with standard
96manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070097
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070098====
99 Implementation-Title: Example plugin showing examples
100 Implementation-Version: 1.0
101 Implementation-Vendor: Example, Inc.
102 Implementation-URL: http://example.com/opensource/plugin-foo/
103====
Nasser Grainawie033b262012-05-09 17:54:21 -0700104
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700105ApiType
106~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700107
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700108Plugins using the tightly coupled `gerrit-plugin-api.jar` must
109declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200110internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700111API will be assumed. This may cause ClassNotFoundExceptions when
112loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700113
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700114====
115 Gerrit-ApiType: plugin
116====
117
118Explicit Registration
119~~~~~~~~~~~~~~~~~~~~~
120
121Plugins that use explicit Guice registration must name the Guice
122modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200123manifest. `Gerrit-Module` supplies bindings to the core server;
124`Gerrit-SshModule` supplies SSH commands to the SSH server (if
125enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700126server (if enabled). If no modules are named automatic registration
127will be performed by scanning all classes in the plugin JAR for
128`@Listen` and `@Export("")` annotations.
129
130====
131 Gerrit-Module: tld.example.project.CoreModuleClassName
132 Gerrit-SshModule: tld.example.project.SshModuleClassName
133 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
134====
135
Edwin Kempinf7295742012-07-16 15:03:46 +0200136[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700137Reload Method
138~~~~~~~~~~~~~
139
140If a plugin holds an exclusive resource that must be released before
141loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200142acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700143to be `restart`. Otherwise the preferred method of `reload` will
144be used, as it enables the server to hot-patch an updated plugin
145with no down time.
146
147====
148 Gerrit-ReloadMode: restart
149====
150
151In either mode ('restart' or 'reload') any plugin or extension can
152be updated without restarting the Gerrit server. The difference is
153how Gerrit handles the upgrade:
154
155restart::
156 The old plugin is completely stopped. All registrations of SSH
157 commands and HTTP servlets are removed. All registrations of any
158 extension points are removed. All registered LifecycleListeners
159 have their `stop()` method invoked in reverse order. The new
160 plugin is started, and registrations are made from the new
161 plugin. There is a brief window where neither the old nor the
162 new plugin is connected to the server. This means SSH commands
163 and HTTP servlets will return not found errors, and the plugin
164 will not be notified of events that occurred during the restart.
165
166reload::
167 The new plugin is started. Its LifecycleListeners are permitted
168 to perform their `start()` methods. All SSH and HTTP registrations
169 are atomically swapped out from the old plugin to the new plugin,
170 ensuring the server never returns a not found error. All extension
171 point listeners are atomically swapped out from the old plugin to
172 the new plugin, ensuring no events are missed (however some events
173 may still route to the old plugin if the swap wasn't complete yet).
174 The old plugin is stopped.
175
Edwin Kempinf7295742012-07-16 15:03:46 +0200176To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
177command can be used.
178
Luca Milanesio737285d2012-09-25 14:26:43 +0100179[[init_step]]
180Init step
181~~~~~~~~~
182
183Plugins can contribute their own "init step" during the Gerrit init
184wizard. This is useful for guiding the Gerrit administrator through
185the settings needed by the plugin to work propertly.
186
187For instance plugins to integrate Jira issues to Gerrit changes may
188contribute their own "init step" to allow configuring the Jira URL,
189credentials and possibly verify connectivity to validate them.
190
191====
192 Gerrit-InitStep: tld.example.project.MyInitStep
193====
194
195MyInitStep needs to follow the standard Gerrit InitStep syntax
196and behaviour: writing to the console using the injected ConsoleUI
197and accessing / changing configuration settings using Section.Factory.
198
199In addition to the standard Gerrit init injections, plugins receive
200the @PluginName String injection containing their own plugin name.
201
202Bear in mind that the Plugin's InitStep class will be loaded but
203the standard Gerrit runtime environment is not available and the plugin's
204own Guice modules were not initialized.
205This means the InitStep for a plugin is not executed in the same way that
206the plugin executes within the server, and may mean a plugin author cannot
207trivially reuse runtime code during init.
208
209For instance a plugin that wants to verify connectivity may need to statically
210call the constructor of their connection class, passing in values obtained
211from the Section.Factory rather than from an injected Config object.
212
213Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
214the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
215and before the DB Schema initialization or upgrade.
216Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
217objects injected at startup.
218
219====
220public class MyInitStep implements InitStep {
221 private final ConsoleUI ui;
222 private final Section.Factory sections;
223 private final String pluginName;
224
225 @Inject
226 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
227 this.ui = ui;
228 this.sections = sections;
229 this.pluginName = pluginName;
230 }
231
232 @Override
233 public void run() throws Exception {
234 ui.header("\nMy plugin");
235
236 Section mySection = getSection("myplugin", null);
237 mySection.string("Link name", "linkname", "MyLink");
238 }
239}
240====
241
Edwin Kempinf5a77332012-07-18 11:17:53 +0200242[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700243Classpath
244---------
245
246Each plugin is loaded into its own ClassLoader, isolating plugins
247from each other. A plugin or extension inherits the Java runtime
248and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
249from the hosting server.
250
251Plugins are loaded from a single JAR file. If a plugin needs
252additional libraries, it must include those dependencies within
253its own JAR. Plugins built using Maven may be able to use the
254link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
255to package additional dependencies. Relocating (or renaming) classes
256should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700257
Edwin Kempinf5a77332012-07-18 11:17:53 +0200258[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700259SSH Commands
260------------
261
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700262Plugins may provide commands that can be accessed through the SSH
263interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700264
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700265Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700266
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700267====
268 import com.google.gerrit.sshd.SshCommand;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700269
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700270 class PrintHello extends SshCommand {
271 protected abstract void run() {
272 stdout.print("Hello\n");
273 }
274 }
275====
Nasser Grainawie033b262012-05-09 17:54:21 -0700276
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700277If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200278use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700279
280====
281 import com.google.gerrit.extensions.annotations.Export;
282 import com.google.gerrit.sshd.SshCommand;
283
284 @Export("print")
285 class PrintHello extends SshCommand {
286 protected abstract void run() {
287 stdout.print("Hello\n");
288 }
289 }
290====
291
292If explicit registration is being used, a Guice module must be
293supplied to register the SSH command and declared in the manifest
294with the `Gerrit-SshModule` attribute:
295
296====
297 import com.google.gerrit.sshd.PluginCommandModule;
298
299 class MyCommands extends PluginCommandModule {
300 protected void configureCommands() {
301 command("print").to(PrintHello.class);
302 }
303 }
304====
305
306For a plugin installed as name `helloworld`, the command implemented
307by PrintHello class will be available to users as:
308
309----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600310$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700311----
312
Edwin Kempinf5a77332012-07-18 11:17:53 +0200313[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700314HTTP Servlets
315-------------
316
317Plugins or extensions may register additional HTTP servlets, and
318wrap them with HTTP filters.
319
320Servlets may use auto-registration to declare the URL they handle:
321
322====
323 import com.google.gerrit.extensions.annotations.Export;
324 import com.google.inject.Singleton;
325 import javax.servlet.http.HttpServlet;
326 import javax.servlet.http.HttpServletRequest;
327 import javax.servlet.http.HttpServletResponse;
328
329 @Export("/print")
330 @Singleton
331 class HelloServlet extends HttpServlet {
332 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
333 res.setContentType("text/plain");
334 res.setCharacterEncoding("UTF-8");
335 res.getWriter().write("Hello");
336 }
337 }
338====
339
Edwin Kempin8aa650f2012-07-18 11:25:48 +0200340The auto registration only works for standard servlet mappings like
341`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
342to register the HTTP servlets and declare it explicitly in the manifest
343with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700344
345====
346 import com.google.inject.servlet.ServletModule;
347
348 class MyWebUrls extends ServletModule {
349 protected void configureServlets() {
350 serve("/print").with(HelloServlet.class);
351 }
352 }
353====
354
355For a plugin installed as name `helloworld`, the servlet implemented
356by HelloServlet class will be available to users as:
357
358----
359$ curl http://review.example.com/plugins/helloworld/print
360----
Nasser Grainawie033b262012-05-09 17:54:21 -0700361
Edwin Kempinf5a77332012-07-18 11:17:53 +0200362[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +0200363Data Directory
364--------------
365
366Plugins can request a data directory with a `@PluginData` File
367dependency. A data directory will be created automatically by the
368server in `$site_path/data/$plugin_name` and passed to the plugin.
369
370Plugins can use this to store any data they want.
371
372====
373 @Inject
374 MyType(@PluginData java.io.File myDir) {
375 new FileInputStream(new File(myDir, "my.config"));
376 }
377====
378
Edwin Kempinf5a77332012-07-18 11:17:53 +0200379[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700380Documentation
381-------------
382
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700383If a plugin does not register a filter or servlet to handle URLs
384`/Documentation/*` or `/static/*`, the core Gerrit server will
385automatically export these resources over HTTP from the plugin JAR.
386
387Static resources under `static/` directory in the JAR will be
388available as `/plugins/helloworld/static/resource`.
389
390Documentation files under `Documentation/` directory in the JAR
391will be available as `/plugins/helloworld/Documentation/resource`.
392
393Documentation may be written in
394link:http://daringfireball.net/projects/markdown/[Markdown] style
395if the file name ends with `.md`. Gerrit will automatically convert
396Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -0700397
Edwin Kempinf5a77332012-07-18 11:17:53 +0200398[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +0200399Within the Markdown documentation files macros can be used that allow
400to write documentation with reasonably accurate examples that adjust
401automatically based on the installation.
402
403The following macros are supported:
404
405[width="40%",options="header"]
406|===================================================
407|Macro | Replacement
408|@PLUGIN@ | name of the plugin
409|@URL@ | Gerrit Web URL
410|@SSH_HOST@ | SSH Host
411|@SSH_PORT@ | SSH Port
412|===================================================
413
414The macros will be replaced when the documentation files are rendered
415from Markdown to HTML.
416
417Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
418even if there is an expansion for `KEEP` in the future.
419
Edwin Kempinf5a77332012-07-18 11:17:53 +0200420[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700421Automatic Index
422~~~~~~~~~~~~~~~
423
424If a plugin does not handle its `/` URL itself, Gerrit will
425redirect clients to the plugin's `/Documentation/index.html`.
426Requests for `/Documentation/` (bare directory) will also redirect
427to `/Documentation/index.html`.
428
429If neither resource `Documentation/index.html` or
430`Documentation/index.md` exists in the plugin JAR, Gerrit will
431automatically generate an index page for the plugin's documentation
432tree by scanning every `*.md` and `*.html` file in the Documentation/
433directory.
434
435For any discovered Markdown (`*.md`) file, Gerrit will parse the
436header of the file and extract the first level one title. This
437title text will be used as display text for a link to the HTML
438version of the page.
439
440For any discovered HTML (`*.html`) file, Gerrit will use the name
441of the file, minus the `*.html` extension, as the link text. Any
442hyphens in the file name will be replaced with spaces.
443
444If a discovered file name beings with `cmd-` it will be clustered
445into a 'Commands' section of the generated index page. All other
446files are clustered under a 'Documentation' section.
447
448Some optional information from the manifest is extracted and
449displayed as part of the index page, if present in the manifest:
450
451[width="40%",options="header"]
452|===================================================
453|Field | Source Attribute
454|Name | Implementation-Title
455|Vendor | Implementation-Vendor
456|Version | Implementation-Version
457|URL | Implementation-URL
458|API Version | Gerrit-ApiVersion
459|===================================================
460
Edwin Kempinf5a77332012-07-18 11:17:53 +0200461[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700462Deployment
463----------
464
Edwin Kempinf7295742012-07-16 15:03:46 +0200465Compiled plugins and extensions can be deployed to a running Gerrit
466server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700467
468Plugins can also be copied directly into the server's
469directory at `$site_path/plugins/$name.jar`. The name of
470the JAR file, minus the `.jar` extension, will be used as the
471plugin name. Unless disabled, servers periodically scan this
472directory for updated plugins. The time can be adjusted by
473link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700474
Edwin Kempinf7295742012-07-16 15:03:46 +0200475For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
476command can be used.
477
Brad Larsond5e87c32012-07-11 12:18:49 -0500478Disabled plugins can be re-enabled using the
479link:cmd-plugin-enable.html[plugin enable] command.
480
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700481GERRIT
482------
483Part of link:index.html[Gerrit Code Review]