blob: ad576a0a653049fc1fe64820e27e8ac067a6a2d8 [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
David Pursehouse2cf0cb52013-08-27 16:09:53 +090052. clone the sample plugin:
Edwin Kempinf878c4b2012-07-18 09:34:25 +020053+
David Pursehouse2cf0cb52013-08-27 16:09:53 +090054This is a project that demonstrates the various features of the
55plugin API. It can be taken as an example to develop an own plugin.
Edwin Kempinf878c4b2012-07-18 09:34:25 +020056+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070057----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090058$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070059----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020060+
61When starting from this example one should take care to adapt the
62`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
63the plugin is developed. If the plugin is developed for a released
64Gerrit version (no `SNAPSHOT` version) then the URL for the
65`gerrit-api-repository` in the `pom.xml` needs to be changed to
Shawn Pearced5005002013-06-21 11:01:45 -070066`https://gerrit-api.storage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070067
Edwin Kempinf878c4b2012-07-18 09:34:25 +020068[[API]]
69API
70---
71
72There are two different API formats offered against which plugins can
73be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070074
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075gerrit-extension-api.jar::
76 A stable but thin interface. Suitable for extensions that need
77 to be notified of events, but do not require tight coupling to
78 the internals of Gerrit. Extensions built against this API can
79 expect to be binary compatible across a wide range of server
80 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082gerrit-plugin-api.jar::
83 The complete internals of the Gerrit server, permitting a
84 plugin to tightly couple itself and provide additional
85 functionality that is not possible as an extension. Plugins
86 built against this API are expected to break at the source
87 code level between every major.minor Gerrit release. A plugin
88 that compiles against 2.5 will probably need source code level
89 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070090
91Manifest
92--------
93
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094Plugins may provide optional description information with standard
95manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070096
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070097====
98 Implementation-Title: Example plugin showing examples
99 Implementation-Version: 1.0
100 Implementation-Vendor: Example, Inc.
101 Implementation-URL: http://example.com/opensource/plugin-foo/
102====
Nasser Grainawie033b262012-05-09 17:54:21 -0700103
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104ApiType
105~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700106
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107Plugins using the tightly coupled `gerrit-plugin-api.jar` must
108declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200109internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110API will be assumed. This may cause ClassNotFoundExceptions when
111loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700112
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700113====
114 Gerrit-ApiType: plugin
115====
116
117Explicit Registration
118~~~~~~~~~~~~~~~~~~~~~
119
120Plugins that use explicit Guice registration must name the Guice
121modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200122manifest. `Gerrit-Module` supplies bindings to the core server;
123`Gerrit-SshModule` supplies SSH commands to the SSH server (if
124enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700125server (if enabled). If no modules are named automatic registration
126will be performed by scanning all classes in the plugin JAR for
127`@Listen` and `@Export("")` annotations.
128
129====
130 Gerrit-Module: tld.example.project.CoreModuleClassName
131 Gerrit-SshModule: tld.example.project.SshModuleClassName
132 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
133====
134
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200135[[plugin_name]]
136Plugin Name
137~~~~~~~~~~~
138
139Plugin can optionally provide its own plugin name.
140
141====
142 Gerrit-PluginName: replication
143====
144
145This is useful for plugins that contribute plugin-owned capabilities that
146are stored in the `project.config` file. Another use case is to be able to put
147project specific plugin configuration section in `project.config`. In this
148case it is advantageous to reserve the plugin name to access the configuration
149section in the `project.config` file.
150
151If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
152the plugin file name.
153
154If a plugin provides its own name, then that plugin cannot be deployed
155multiple times under different file names on one Gerrit site.
156
157For Maven driven plugins, the following line must be included in the pom.xml
158file:
159
160[source,xml]
161----
162<manifestEntries>
163 <Gerrit-PluginName>name</Gerrit-PluginName>
164</manifestEntries>
165----
166
167For Buck driven plugins, the following line must be included in the BUCK
168configuration file:
169
170[source,python]
171----
172 manifest_entries = [
173 'Gerrit-PluginName: name',
174 ]
175----
176
Edwin Kempinf7295742012-07-16 15:03:46 +0200177[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700178Reload Method
179~~~~~~~~~~~~~
180
181If a plugin holds an exclusive resource that must be released before
182loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200183acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700184to be `restart`. Otherwise the preferred method of `reload` will
185be used, as it enables the server to hot-patch an updated plugin
186with no down time.
187
188====
189 Gerrit-ReloadMode: restart
190====
191
192In either mode ('restart' or 'reload') any plugin or extension can
193be updated without restarting the Gerrit server. The difference is
194how Gerrit handles the upgrade:
195
196restart::
197 The old plugin is completely stopped. All registrations of SSH
198 commands and HTTP servlets are removed. All registrations of any
199 extension points are removed. All registered LifecycleListeners
200 have their `stop()` method invoked in reverse order. The new
201 plugin is started, and registrations are made from the new
202 plugin. There is a brief window where neither the old nor the
203 new plugin is connected to the server. This means SSH commands
204 and HTTP servlets will return not found errors, and the plugin
205 will not be notified of events that occurred during the restart.
206
207reload::
208 The new plugin is started. Its LifecycleListeners are permitted
209 to perform their `start()` methods. All SSH and HTTP registrations
210 are atomically swapped out from the old plugin to the new plugin,
211 ensuring the server never returns a not found error. All extension
212 point listeners are atomically swapped out from the old plugin to
213 the new plugin, ensuring no events are missed (however some events
214 may still route to the old plugin if the swap wasn't complete yet).
215 The old plugin is stopped.
216
Edwin Kempinf7295742012-07-16 15:03:46 +0200217To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
218command can be used.
219
Luca Milanesio737285d2012-09-25 14:26:43 +0100220[[init_step]]
221Init step
222~~~~~~~~~
223
224Plugins can contribute their own "init step" during the Gerrit init
225wizard. This is useful for guiding the Gerrit administrator through
226the settings needed by the plugin to work propertly.
227
228For instance plugins to integrate Jira issues to Gerrit changes may
229contribute their own "init step" to allow configuring the Jira URL,
230credentials and possibly verify connectivity to validate them.
231
232====
233 Gerrit-InitStep: tld.example.project.MyInitStep
234====
235
236MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900237and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100238and accessing / changing configuration settings using Section.Factory.
239
240In addition to the standard Gerrit init injections, plugins receive
241the @PluginName String injection containing their own plugin name.
242
243Bear in mind that the Plugin's InitStep class will be loaded but
244the standard Gerrit runtime environment is not available and the plugin's
245own Guice modules were not initialized.
246This means the InitStep for a plugin is not executed in the same way that
247the plugin executes within the server, and may mean a plugin author cannot
248trivially reuse runtime code during init.
249
250For instance a plugin that wants to verify connectivity may need to statically
251call the constructor of their connection class, passing in values obtained
252from the Section.Factory rather than from an injected Config object.
253
254Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
255the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
256and before the DB Schema initialization or upgrade.
257Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
258objects injected at startup.
259
David Pursehouse68153d72013-09-04 10:09:17 +0900260[source,java]
261----
262public class MyInitStep implements InitStep {
263 private final ConsoleUI ui;
264 private final Section.Factory sections;
265 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100266
David Pursehouse68153d72013-09-04 10:09:17 +0900267 @Inject
268 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
269 this.ui = ui;
270 this.sections = sections;
271 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100272 }
David Pursehouse68153d72013-09-04 10:09:17 +0900273
274 @Override
275 public void run() throws Exception {
276 ui.header("\nMy plugin");
277
278 Section mySection = getSection("myplugin", null);
279 mySection.string("Link name", "linkname", "MyLink");
280 }
281}
282----
Luca Milanesio737285d2012-09-25 14:26:43 +0100283
Edwin Kempinf5a77332012-07-18 11:17:53 +0200284[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700285Classpath
286---------
287
288Each plugin is loaded into its own ClassLoader, isolating plugins
289from each other. A plugin or extension inherits the Java runtime
290and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
291from the hosting server.
292
293Plugins are loaded from a single JAR file. If a plugin needs
294additional libraries, it must include those dependencies within
295its own JAR. Plugins built using Maven may be able to use the
296link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
297to package additional dependencies. Relocating (or renaming) classes
298should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700299
Edwin Kempin98202662013-09-18 16:03:03 +0200300[[events]]
301Listening to Events
302-------------------
303
304Certain operations in Gerrit trigger events. Plugins may receive
305notifications of these events by implementing the corresponding
306listeners.
307
308* `com.google.gerrit.extensions.events.LifecycleListener`:
309+
310Gerrit server startup and shutdown
311
312* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
313+
314Project creation
315
316* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
317+
318Project deletion
319
Edwin Kempinf5a77332012-07-18 11:17:53 +0200320[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700321SSH Commands
322------------
323
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700324Plugins may provide commands that can be accessed through the SSH
325interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700326
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700327Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700328
David Pursehouse68153d72013-09-04 10:09:17 +0900329[source,java]
330----
331import com.google.gerrit.sshd.SshCommand;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700332
David Pursehouse68153d72013-09-04 10:09:17 +0900333class PrintHello extends SshCommand {
334 protected abstract void run() {
335 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700336 }
David Pursehouse68153d72013-09-04 10:09:17 +0900337}
338----
Nasser Grainawie033b262012-05-09 17:54:21 -0700339
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700340If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200341use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700342
David Pursehouse68153d72013-09-04 10:09:17 +0900343[source,java]
344----
345import com.google.gerrit.extensions.annotations.Export;
346import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700347
David Pursehouse68153d72013-09-04 10:09:17 +0900348@Export("print")
349class PrintHello extends SshCommand {
350 protected abstract void run() {
351 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700352 }
David Pursehouse68153d72013-09-04 10:09:17 +0900353}
354----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700355
356If explicit registration is being used, a Guice module must be
357supplied to register the SSH command and declared in the manifest
358with the `Gerrit-SshModule` attribute:
359
David Pursehouse68153d72013-09-04 10:09:17 +0900360[source,java]
361----
362import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700363
David Pursehouse68153d72013-09-04 10:09:17 +0900364class MyCommands extends PluginCommandModule {
365 protected void configureCommands() {
366 command("print").to(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700367 }
David Pursehouse68153d72013-09-04 10:09:17 +0900368}
369----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700370
371For a plugin installed as name `helloworld`, the command implemented
372by PrintHello class will be available to users as:
373
374----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600375$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700376----
377
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200378[[configuration]]
379Configuration
380-------------
381
382In Gerrit, global configuration is stored in the `gerrit.config` file.
383If a plugin needs global configuration, this configuration should be
384stored in a `plugin` subsection in the `gerrit.config` file.
385
386To avoid conflicts with other plugins, it is recommended that plugins
387only use the `plugin` subsection with their own name. For example the
388`helloworld` plugin should store its configuration in the
389`plugin.helloworld` subsection:
390
391----
392[plugin "helloworld"]
393 language = Latin
394----
395
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200396Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200397plugin can easily access its configuration and there is no need for a
398plugin to parse the `gerrit.config` file on its own:
399
400[source,java]
401----
402 @Inject
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200403 private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200404
405 ...
406
407 String language = cfg.get("helloworld")
408 .getString("language", "English");
409----
410
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200411[[project-specific-configuration]]
412Project Specific Configuration
413------------------------------
414
415In Gerrit, project specific configuration is stored in the project's
416`project.config` file on the `refs/meta/config` branch. If a plugin
417needs configuration on project level (e.g. to enable its functionality
418only for certain projects), this configuration should be stored in a
419`plugin` subsection in the project's `project.config` file.
420
421To avoid conflicts with other plugins, it is recommended that plugins
422only use the `plugin` subsection with their own name. For example the
423`helloworld` plugin should store its configuration in the
424`plugin.helloworld` subsection:
425
426----
427 [plugin "helloworld"]
428 enabled = true
429----
430
431Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
432plugin can easily access its project specific configuration and there
433is no need for a plugin to parse the `project.config` file on its own:
434
435[source,java]
436----
437 @Inject
438 private com.google.gerrit.server.config.PluginConfigFactory cfg;
439
440 ...
441
442 boolean enabled = cfg.get(project, "helloworld")
443 .getBoolean("enabled", false);
444----
445
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200446It is also possible to get missing configuration parameters inherited
447from the parent projects:
448
449[source,java]
450----
451 @Inject
452 private com.google.gerrit.server.config.PluginConfigFactory cfg;
453
454 ...
455
456 boolean enabled = cfg.getWithInheritance(project, "helloworld")
457 .getBoolean("enabled", false);
458----
459
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200460Project owners can edit the project configuration by fetching the
461`refs/meta/config` branch, editing the `project.config` file and
462pushing the commit back.
463
David Ostrovsky7066cc02013-06-15 14:46:23 +0200464[[capabilities]]
465Plugin Owned Capabilities
466-------------------------
467
468Plugins may provide their own capabilities and restrict usage of SSH
469commands to the users who are granted those capabilities.
470
471Plugins define the capabilities by overriding the `CapabilityDefinition`
472abstract class:
473
David Pursehouse68153d72013-09-04 10:09:17 +0900474[source,java]
475----
476public class PrintHelloCapability extends CapabilityDefinition {
477 @Override
478 public String getDescription() {
479 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200480 }
David Pursehouse68153d72013-09-04 10:09:17 +0900481}
482----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200483
David Ostrovskyf86bae52013-09-01 09:10:39 +0200484If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200485use auto-registration by providing an `@Export` annotation:
486
David Pursehouse68153d72013-09-04 10:09:17 +0900487[source,java]
488----
489@Export("printHello")
490public class PrintHelloCapability extends CapabilityDefinition {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200491 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900492}
493----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200494
495Otherwise the capability must be bound in a plugin module:
496
David Pursehouse68153d72013-09-04 10:09:17 +0900497[source,java]
498----
499public class HelloWorldModule extends AbstractModule {
500 @Override
501 protected void configure() {
502 bind(CapabilityDefinition.class)
503 .annotatedWith(Exports.named("printHello"))
504 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200505 }
David Pursehouse68153d72013-09-04 10:09:17 +0900506}
507----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200508
509With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200510usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200511this capability in the usual way, using the `RequiresCapability` annotation:
512
David Pursehouse68153d72013-09-04 10:09:17 +0900513[source,java]
514----
515@RequiresCapability("printHello")
516@CommandMetaData(name="print", description="Print greeting in different languages")
517public final class PrintHelloWorldCommand extends SshCommand {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200518 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900519}
520----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200521
David Ostrovskyf86bae52013-09-01 09:10:39 +0200522Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200523
David Pursehouse68153d72013-09-04 10:09:17 +0900524[source,java]
525----
526@RequiresCapability("printHello")
527public class SayHelloAction extends UiAction<RevisionResource>
528 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200529 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900530}
531----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200532
533Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900534capabilities and core capabilities. Per default the scope of the
535`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
536
David Ostrovsky7066cc02013-06-15 14:46:23 +0200537* when `@RequiresCapability` is used within a plugin the scope of the
538capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900539
David Ostrovsky7066cc02013-06-15 14:46:23 +0200540* If `@RequiresCapability` is used within the core Gerrit Code Review server
541(and thus is outside of a plugin) the scope is the core server and will use
542the `GlobalCapability` known to Gerrit Code Review server.
543
544If a plugin needs to use a core capability name (e.g. "administrateServer")
545this can be specified by setting `scope = CapabilityScope.CORE`:
546
David Pursehouse68153d72013-09-04 10:09:17 +0900547[source,java]
548----
549@RequiresCapability(value = "administrateServer", scope =
550 CapabilityScope.CORE)
David Ostrovsky7066cc02013-06-15 14:46:23 +0200551 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900552----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200553
David Ostrovskyf86bae52013-09-01 09:10:39 +0200554[[ui_extension]]
555UI Extension
556------------
557
558Plugins can contribute their own UI commands on core Gerrit pages.
559This is useful for workflow customization or exposing plugin functionality
560through the UI in addition to SSH commands and the REST API.
561
562For instance a plugin to integrate Jira with Gerrit changes may contribute its
563own "File bug" button to allow filing a bug from the change page or plugins to
564integrate continuous integration systems may contribute a "Schedule" button to
565allow a CI build to be scheduled manually from the patch set panel.
566
567Two different places on core Gerrit pages are currently supported:
568
569* Change screen
570* Project info screen
571
572Plugins contribute UI actions by implementing the `UiAction` interface:
573
David Pursehouse68153d72013-09-04 10:09:17 +0900574[source,java]
575----
576@RequiresCapability("printHello")
577class HelloWorldAction implements UiAction<RevisionResource>,
578 RestModifyView<RevisionResource, HelloWorldAction.Input> {
579 static class Input {
580 boolean french;
581 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200582 }
David Pursehouse68153d72013-09-04 10:09:17 +0900583
584 private Provider<CurrentUser> user;
585
586 @Inject
587 HelloWorldAction(Provider<CurrentUser> user) {
588 this.user = user;
589 }
590
591 @Override
592 public String apply(RevisionResource rev, Input input) {
593 final String greeting = input.french
594 ? "Bonjour"
595 : "Hello";
596 return String.format("%s %s from change %s, patch set %d!",
597 greeting,
598 Strings.isNullOrEmpty(input.message)
599 ? Objects.firstNonNull(user.get().getUserName(), "world")
600 : input.message,
601 rev.getChange().getId().toString(),
602 rev.getPatchSet().getPatchSetId());
603 }
604
605 @Override
606 public Description getDescription(
607 RevisionResource resource) {
608 return new Description()
609 .setLabel("Say hello")
610 .setTitle("Say hello in different languages");
611 }
612}
613----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200614
615`UiAction` must be bound in a plugin module:
616
David Pursehouse68153d72013-09-04 10:09:17 +0900617[source,java]
618----
619public class Module extends AbstractModule {
620 @Override
621 protected void configure() {
622 install(new RestApiModule() {
623 @Override
624 protected void configure() {
625 post(REVISION_KIND, "say-hello")
626 .to(HelloWorldAction.class);
627 }
628 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200629 }
David Pursehouse68153d72013-09-04 10:09:17 +0900630}
631----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200632
633The module above must be declared in pom.xml for Maven driven plugins:
634
David Pursehouse68153d72013-09-04 10:09:17 +0900635[source,xml]
636----
637<manifestEntries>
638 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
639</manifestEntries>
640----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200641
642or in the BUCK configuration file for Buck driven plugins:
643
David Pursehouse68153d72013-09-04 10:09:17 +0900644[source,python]
645----
646manifest_entries = [
647 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
648]
649----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200650
651In some use cases more user input must be gathered, for that `UiAction` can be
652combined with the JavaScript API. This would display a small popup near the
653activation button to gather additional input from the user. The JS file is
654typically put in the `static` folder within the plugin's directory:
655
David Pursehouse68153d72013-09-04 10:09:17 +0900656[source,javascript]
657----
658Gerrit.install(function(self) {
659 function onSayHello(c) {
660 var f = c.textfield();
661 var t = c.checkbox();
662 var b = c.button('Say hello', {onclick: function(){
663 c.call(
664 {message: f.value, french: t.checked},
665 function(r) {
666 c.hide();
667 window.alert(r);
668 c.refresh();
669 });
670 }});
671 c.popup(c.div(
672 c.prependLabel('Greeting message', f),
673 c.br(),
674 c.label(t, 'french'),
675 c.br(),
676 b));
677 f.focus();
678 }
679 self.onAction('revision', 'say-hello', onSayHello);
680});
681----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200682
683The JS module must be exposed as a `WebUiPlugin` and bound as
684an HTTP Module:
685
David Pursehouse68153d72013-09-04 10:09:17 +0900686[source,java]
687----
688public class HttpModule extends HttpPluginModule {
689 @Override
690 protected void configureServlets() {
691 DynamicSet.bind(binder(), WebUiPlugin.class)
692 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +0200693 }
David Pursehouse68153d72013-09-04 10:09:17 +0900694}
695----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200696
697The HTTP module above must be declared in pom.xml for Maven driven plugins:
698
David Pursehouse68153d72013-09-04 10:09:17 +0900699[source,xml]
700----
701<manifestEntries>
702 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
703</manifestEntries>
704----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200705
706or in the BUCK configuration file for Buck driven plugins
707
David Pursehouse68153d72013-09-04 10:09:17 +0900708[source,python]
709----
710manifest_entries = [
711 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
712]
713----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200714
715If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
716capability check is done during the `UiAction` gathering, so the plugin author
717doesn't have to set `UiAction.Description.setVisible()` explicitly in this
718case.
719
720The following prerequisities must be met, to satisfy the capability check:
721
722* user is authenticated
723* user is a member of the Administrators group, or
724* user is a member of a group which has the required capability
725
726The `apply` method is called when the button is clicked. If `UiAction` is
727combined with JavaScript API (its own JavaScript function is provided),
728then a popup dialog is normally opened to gather additional user input.
729A new button is placed on the popup dialog to actually send the request.
730
731Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
732can be accessed from any REST client, i. e.:
733
734====
735 curl -X POST -H "Content-Type: application/json" \
736 -d '{message: "François", french: true}' \
737 --digest --user joe:secret \
738 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
739 "Bonjour François from change 1, patch set 1!"
740====
741
David Pursehouse42245822013-09-24 09:48:20 +0900742A special case is to bind an endpoint without a view name. This is
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200743particularly useful for DELETE requests:
744
745[source,java]
746----
747public class Module extends AbstractModule {
748 @Override
749 protected void configure() {
750 install(new RestApiModule() {
751 @Override
752 protected void configure() {
753 delete(PROJECT_KIND)
754 .to(DeleteProject.class);
755 }
756 });
757 }
758}
759----
760
David Pursehouse42245822013-09-24 09:48:20 +0900761For a `UiAction` bound this way, a JS API function can be provided.
762
763Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200764can be bound per resource without view name. To define a JS function
765for the `UiAction`, "/" must be used as the name:
766
767[source,javascript]
768----
769Gerrit.install(function(self) {
770 function onDeleteProject(c) {
771 [...]
772 }
773 self.onAction('project', '/', onDeleteProject);
774});
775----
776
Edwin Kempinf5a77332012-07-18 11:17:53 +0200777[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700778HTTP Servlets
779-------------
780
781Plugins or extensions may register additional HTTP servlets, and
782wrap them with HTTP filters.
783
784Servlets may use auto-registration to declare the URL they handle:
785
David Pursehouse68153d72013-09-04 10:09:17 +0900786[source,java]
787----
788import com.google.gerrit.extensions.annotations.Export;
789import com.google.inject.Singleton;
790import javax.servlet.http.HttpServlet;
791import javax.servlet.http.HttpServletRequest;
792import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700793
David Pursehouse68153d72013-09-04 10:09:17 +0900794@Export("/print")
795@Singleton
796class HelloServlet extends HttpServlet {
797 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
798 res.setContentType("text/plain");
799 res.setCharacterEncoding("UTF-8");
800 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700801 }
David Pursehouse68153d72013-09-04 10:09:17 +0900802}
803----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700804
Edwin Kempin8aa650f2012-07-18 11:25:48 +0200805The auto registration only works for standard servlet mappings like
806`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
807to register the HTTP servlets and declare it explicitly in the manifest
808with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700809
David Pursehouse68153d72013-09-04 10:09:17 +0900810[source,java]
811----
812import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700813
David Pursehouse68153d72013-09-04 10:09:17 +0900814class MyWebUrls extends ServletModule {
815 protected void configureServlets() {
816 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700817 }
David Pursehouse68153d72013-09-04 10:09:17 +0900818}
819----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700820
821For a plugin installed as name `helloworld`, the servlet implemented
822by HelloServlet class will be available to users as:
823
824----
825$ curl http://review.example.com/plugins/helloworld/print
826----
Nasser Grainawie033b262012-05-09 17:54:21 -0700827
Edwin Kempinf5a77332012-07-18 11:17:53 +0200828[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +0200829Data Directory
830--------------
831
832Plugins can request a data directory with a `@PluginData` File
833dependency. A data directory will be created automatically by the
834server in `$site_path/data/$plugin_name` and passed to the plugin.
835
836Plugins can use this to store any data they want.
837
David Pursehouse68153d72013-09-04 10:09:17 +0900838[source,java]
839----
840@Inject
841MyType(@PluginData java.io.File myDir) {
842 new FileInputStream(new File(myDir, "my.config"));
843}
844----
Edwin Kempin41f63912012-07-17 12:33:55 +0200845
Edwin Kempinf5a77332012-07-18 11:17:53 +0200846[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700847Documentation
848-------------
849
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700850If a plugin does not register a filter or servlet to handle URLs
851`/Documentation/*` or `/static/*`, the core Gerrit server will
852automatically export these resources over HTTP from the plugin JAR.
853
David Pursehouse6853b5a2013-07-10 11:38:03 +0900854Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -0400855available as `/plugins/helloworld/static/resource`. This prefix is
856configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700857
David Pursehouse6853b5a2013-07-10 11:38:03 +0900858Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -0400859will be available as `/plugins/helloworld/Documentation/resource`. This
860prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
861attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700862
863Documentation may be written in
864link:http://daringfireball.net/projects/markdown/[Markdown] style
865if the file name ends with `.md`. Gerrit will automatically convert
866Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -0700867
Edwin Kempinf5a77332012-07-18 11:17:53 +0200868[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +0200869Within the Markdown documentation files macros can be used that allow
870to write documentation with reasonably accurate examples that adjust
871automatically based on the installation.
872
873The following macros are supported:
874
875[width="40%",options="header"]
876|===================================================
877|Macro | Replacement
878|@PLUGIN@ | name of the plugin
879|@URL@ | Gerrit Web URL
880|@SSH_HOST@ | SSH Host
881|@SSH_PORT@ | SSH Port
882|===================================================
883
884The macros will be replaced when the documentation files are rendered
885from Markdown to HTML.
886
887Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
888even if there is an expansion for `KEEP` in the future.
889
Edwin Kempinf5a77332012-07-18 11:17:53 +0200890[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700891Automatic Index
892~~~~~~~~~~~~~~~
893
894If a plugin does not handle its `/` URL itself, Gerrit will
895redirect clients to the plugin's `/Documentation/index.html`.
896Requests for `/Documentation/` (bare directory) will also redirect
897to `/Documentation/index.html`.
898
899If neither resource `Documentation/index.html` or
900`Documentation/index.md` exists in the plugin JAR, Gerrit will
901automatically generate an index page for the plugin's documentation
902tree by scanning every `*.md` and `*.html` file in the Documentation/
903directory.
904
905For any discovered Markdown (`*.md`) file, Gerrit will parse the
906header of the file and extract the first level one title. This
907title text will be used as display text for a link to the HTML
908version of the page.
909
910For any discovered HTML (`*.html`) file, Gerrit will use the name
911of the file, minus the `*.html` extension, as the link text. Any
912hyphens in the file name will be replaced with spaces.
913
David Pursehouse6853b5a2013-07-10 11:38:03 +0900914If a discovered file is named `about.md` or `about.html`, its
915content will be inserted in an 'About' section at the top of the
916auto-generated index page. If both `about.md` and `about.html`
917exist, only the first discovered file will be used.
918
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700919If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +0900920into a 'Commands' section of the generated index page.
921
David Pursehousefe529152013-08-14 16:35:06 +0900922If a discovered file name beings with `servlet-` it will be clustered
923into a 'Servlets' section of the generated index page.
924
925If a discovered file name beings with `rest-api-` it will be clustered
926into a 'REST APIs' section of the generated index page.
927
David Pursehouse6853b5a2013-07-10 11:38:03 +0900928All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700929
930Some optional information from the manifest is extracted and
931displayed as part of the index page, if present in the manifest:
932
933[width="40%",options="header"]
934|===================================================
935|Field | Source Attribute
936|Name | Implementation-Title
937|Vendor | Implementation-Vendor
938|Version | Implementation-Version
939|URL | Implementation-URL
940|API Version | Gerrit-ApiVersion
941|===================================================
942
Edwin Kempinf5a77332012-07-18 11:17:53 +0200943[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700944Deployment
945----------
946
Edwin Kempinf7295742012-07-16 15:03:46 +0200947Compiled plugins and extensions can be deployed to a running Gerrit
948server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700949
950Plugins can also be copied directly into the server's
951directory at `$site_path/plugins/$name.jar`. The name of
952the JAR file, minus the `.jar` extension, will be used as the
953plugin name. Unless disabled, servers periodically scan this
954directory for updated plugins. The time can be adjusted by
955link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700956
Edwin Kempinf7295742012-07-16 15:03:46 +0200957For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
958command can be used.
959
Brad Larsond5e87c32012-07-11 12:18:49 -0500960Disabled plugins can be re-enabled using the
961link:cmd-plugin-enable.html[plugin enable] command.
962
David Ostrovskyf86bae52013-09-01 09:10:39 +0200963SEE ALSO
964--------
965
966* link:js-api.html[JavaScript API]
967* link:dev-rest-api.html[REST API Developers' Notes]
968
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700969GERRIT
970------
971Part of link:index.html[Gerrit Code Review]