webhooks plugin, initial commit This plugin sends HTTP POST request to configured URL(s) when a project event happens. The target URL(s) are configurable on project level. The configuration is inheritable. This can be used as an alternative to the SSH stream-events. Motivated by Github's webhooks [1]. Most of code was copied from the high-availability plugin [2]. [1] https://developer.github.com/webhooks/ [2] https://gerrit-review.googlesource.com/#/admin/projects/plugins/high-availability Change-Id: I902c8d25a25dd54a0b929c6e383837135737e6cb
diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 0000000..81f137b --- /dev/null +++ b/.buckconfig
@@ -0,0 +1,16 @@ +[alias] + webhooks = //:webhooks + plugin = //:webhooks + src = //:webhooks-sources + +[java] + jar_spool_mode = direct_to_jar + src_roots = java, resources + +[project] + ignore = .git, eclipse-out/ + parallel_parsing = true + +[cache] + mode = dir + dir = buck-out/cache
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c27e17f --- /dev/null +++ b/.gitignore
@@ -0,0 +1,9 @@ +/.buckd/ +/.buckversion +/.classpath +/.project +/.settings/ +/.watchmanconfig +/buck-out/ +/bucklets +/eclipse-out/
diff --git a/BUCK b/BUCK new file mode 100644 index 0000000..aa5ea84 --- /dev/null +++ b/BUCK
@@ -0,0 +1,42 @@ +include_defs('//bucklets/gerrit_plugin.bucklet') +include_defs('//bucklets/java_sources.bucklet') +include_defs('//bucklets/maven_jar.bucklet') + +SOURCES = glob(['src/main/java/**/*.java']) +RESOURCES = glob(['src/main/resources/**/*']) + +TEST_DEPS = GERRIT_PLUGIN_API + GERRIT_TESTS + [ + ':webhooks__plugin', +] + +gerrit_plugin( + name = 'webhooks', + srcs = SOURCES, + resources = RESOURCES, + manifest_entries = [ + 'Gerrit-PluginName: webhooks', + 'Gerrit-ApiType: plugin', + 'Gerrit-Module: com.googlesource.gerrit.plugins.webhooks.Module', + 'Implementation-Title: webhooks plugin', + 'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/webhooks', + 'Implementation-Vendor: Gerrit Code Review', + ], + provided_deps = GERRIT_TESTS, +) + +java_sources( + name = 'webhooks-sources', + srcs = SOURCES + RESOURCES, +) + +java_library( + name = 'classpath', + deps = TEST_DEPS, +) + +java_test( + name = 'webhooks_tests', + srcs = glob(['src/test/java/**/*.java']), + labels = ['webhooks'], + deps = TEST_DEPS, +)
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK new file mode 100644 index 0000000..8a21820 --- /dev/null +++ b/lib/gerrit/BUCK
@@ -0,0 +1,22 @@ +include_defs('//bucklets/maven_jar.bucklet') + +VER = '2.13.6' +REPO = MAVEN_CENTRAL + +maven_jar( + name = 'acceptance-framework', + id = 'com.google.gerrit:gerrit-acceptance-framework:' + VER, + sha1 = '53a5ffbc3ce6842b7145fd11abcc1dc8503b124f', + license = 'Apache2.0', + attach_source = False, + repository = REPO, +) + +maven_jar( + name = 'plugin-api', + id = 'com.google.gerrit:gerrit-plugin-api:' + VER, + sha1 = '1a5d650c72ebc36f4ad522d5481d5ed690bec8bf', + license = 'Apache2.0', + attach_source = False, + repository = REPO, +)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java new file mode 100644 index 0000000..1045a1b --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java
@@ -0,0 +1,82 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.server.config.PluginConfig; +import com.google.gerrit.server.config.PluginConfigFactory; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class Configuration { + private static final Logger log = LoggerFactory.getLogger(Configuration.class); + + private static final int DEFAULT_TIMEOUT_MS = 5000; + private static final int DEFAULT_MAX_TRIES = 5; + private static final int DEFAULT_RETRY_INTERVAL = 1000; + private static final int DEFAULT_THREAD_POOL_SIZE = 1; + + private final int connectionTimeout; + private final int socketTimeout; + private final int maxTries; + private final int retryInterval; + private final int threadPoolSize; + + @Inject + Configuration(PluginConfigFactory config, + @PluginName String pluginName) { + PluginConfig cfg = config.getFromGerritConfig(pluginName, true); + connectionTimeout = getInt(cfg, "connectionTimeout", DEFAULT_TIMEOUT_MS); + socketTimeout = getInt(cfg, "socketTimeout", DEFAULT_TIMEOUT_MS); + maxTries = getInt(cfg, "maxTries", DEFAULT_MAX_TRIES); + retryInterval = getInt(cfg, "retryInterval", DEFAULT_RETRY_INTERVAL); + threadPoolSize = getInt(cfg, "threadPoolSize", DEFAULT_THREAD_POOL_SIZE); + } + + private int getInt(PluginConfig cfg, String name, int defaultValue) { + try { + return cfg.getInt(name, defaultValue); + } catch (IllegalArgumentException e) { + log.error(String.format( + "invalid value for %s; using default value %d", name, defaultValue)); + log.debug("Failed retrieve integer value: " + e.getMessage(), e); + return defaultValue; + } + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public int getMaxTries() { + return maxTries; + } + + public int getRetryInterval() { + return retryInterval; + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public int getThreadPoolSize() { + return threadPoolSize; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java new file mode 100644 index 0000000..0947f38 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java
@@ -0,0 +1,121 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import java.io.IOException; +import java.util.concurrent.Executor; + +import org.eclipse.jgit.lib.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.gerrit.common.EventListener; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.server.config.PluginConfigFactory; +import com.google.gerrit.server.events.Event; +import com.google.gerrit.server.events.ProjectEvent; +import com.google.gerrit.server.events.SupplierSerializer; +import com.google.gerrit.server.project.NoSuchProjectException; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.inject.Inject; + +class EventHandler implements EventListener { + private static final Logger log = LoggerFactory + .getLogger(EventHandler.class); + + private final HttpSession session; + private final PluginConfigFactory configFactory; + private final String pluginName; + private final Executor executor; + + private final Gson gson; + + @Inject + EventHandler(HttpSession session, + PluginConfigFactory configFactory, + @PluginName String pluginName, + @WebHooksExecutor Executor executor) { + this.session = session; + this.configFactory = configFactory; + this.pluginName = pluginName; + this.executor = executor; + this.gson = new GsonBuilder() + .registerTypeAdapter(Supplier.class, new SupplierSerializer()) + .create(); + } + + @Override + public void onEvent(Event event) { + if (!(event instanceof ProjectEvent)) { + return; + } + + ProjectEvent projectEvent = (ProjectEvent) event; + Config cfg; + try { + cfg = configFactory.getProjectPluginConfigWithInheritance( + projectEvent.getProjectNameKey(), pluginName); + } catch (NoSuchProjectException e) { + log.warn("Ignoring event for a non-existing project {}, {}", + projectEvent.getProjectNameKey().get(), projectEvent); + return; + } + + for (String name : cfg.getSubsections("remote")) { + String url = cfg.getString("remote", name, "url"); + if (Strings.isNullOrEmpty(url)) { + continue; + } + + String[] eventTypes = cfg.getStringList("remote", name, "event"); + + if (eventTypes.length == 0) { + post(url, projectEvent); + } + + for (String type : eventTypes) { + if (Strings.isNullOrEmpty(type)) { + continue; + } + if (type.equals(projectEvent.getType())) { + post(url, projectEvent); + } + } + } + } + + private void post(final String url, final ProjectEvent projectEvent) { + executor.execute(new Runnable() { + @Override + public void run() { + String serializedEvent = gson.toJson(projectEvent); + try { + session.post(url, serializedEvent); + } catch (IOException e) { + log.error("Coulnd't post event: " + projectEvent, e); + } + } + + @Override + public String toString() { + return String.format("%s:%s > %s", + projectEvent.type, projectEvent.getProjectNameKey().get(), url); + } + }); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/ExecutorProvider.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/ExecutorProvider.java new file mode 100644 index 0000000..8a5a630 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/ExecutorProvider.java
@@ -0,0 +1,52 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.server.git.WorkQueue; +import com.google.inject.Inject; +import com.google.inject.Provider; + +class ExecutorProvider + implements Provider<ScheduledThreadPoolExecutor>, LifecycleListener { + private WorkQueue.Executor executor; + + @Inject + ExecutorProvider(WorkQueue workQueue, + Configuration cfg, + @PluginName String name) { + executor = workQueue.createQueue(cfg.getThreadPoolSize(), name); + } + + @Override + public void start() { + // do nothing + } + + @Override + public void stop() { + executor.shutdown(); + executor.unregisterWorkQueue(); + executor = null; + } + + @Override + public ScheduledThreadPoolExecutor get() { + return executor; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpClientProvider.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpClientProvider.java new file mode 100644 index 0000000..047105e --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpClientProvider.java
@@ -0,0 +1,183 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.ServiceUnavailableRetryStrategy; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.protocol.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.inject.Inject; +import com.google.inject.Provider; + +/** + * Provides an HTTP client with SSL capabilities. + */ +class HttpClientProvider implements Provider<CloseableHttpClient> { + private static final Logger log = LoggerFactory + .getLogger(HttpClientProvider.class); + private static final int CONNECTIONS_PER_ROUTE = 100; + // Up to 2 target instances with the max number of connections per host: + private static final int MAX_CONNECTIONS = 2 * CONNECTIONS_PER_ROUTE; + private static final int ERROR_CODES = 500; + private static final int MAX_CONNECTION_INACTIVITY = 10000; + + private final Configuration cfg; + private final SSLConnectionSocketFactory sslSocketFactory; + + @Inject + HttpClientProvider(Configuration cfg) { + this.cfg = cfg; + this.sslSocketFactory = buildSslSocketFactory(); + } + + @Override + public CloseableHttpClient get() { + return HttpClients.custom().setSSLSocketFactory(sslSocketFactory) + .setConnectionManager(customConnectionManager()) + .setDefaultRequestConfig(customRequestConfig()) + .setRetryHandler(customRetryHandler()) + .setServiceUnavailableRetryStrategy(customServiceUnavailRetryStrategy()) + .build(); + } + + private RequestConfig customRequestConfig() { + return RequestConfig.custom().setConnectTimeout(cfg.getConnectionTimeout()) + .setSocketTimeout(cfg.getSocketTimeout()) + .setConnectionRequestTimeout(cfg.getConnectionTimeout()) + .build(); + } + + private HttpRequestRetryHandler customRetryHandler() { + return new HttpRequestRetryHandler() { + + @Override + public boolean retryRequest(IOException exception, int executionCount, + HttpContext context) { + if (executionCount > cfg.getMaxTries() + || exception instanceof SSLException) { + return false; + } + logRetry(exception.getMessage(), context); + try { + Thread.sleep(cfg.getRetryInterval()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + return true; + } + }; + } + + private ServiceUnavailableRetryStrategy customServiceUnavailRetryStrategy() { + return new ServiceUnavailableRetryStrategy() { + @Override + public boolean retryRequest(HttpResponse response, int executionCount, + HttpContext context) { + if (executionCount > cfg.getMaxTries()) { + return false; + } + if (response.getStatusLine().getStatusCode() >= ERROR_CODES) { + logRetry(response.getStatusLine().getReasonPhrase(), context); + return true; + } + return false; + } + + @Override + public long getRetryInterval() { + return cfg.getRetryInterval(); + } + }; + } + + private void logRetry(String cause, HttpContext context) { + if (log.isDebugEnabled()){ + log.debug("Retrying request caused by '" + cause + "', request: '" + + context.getAttribute("http.request") + "'"); + } + } + + private HttpClientConnectionManager customConnectionManager() { + Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder + .<ConnectionSocketFactory> create().register("https", sslSocketFactory) + .register("http", PlainConnectionSocketFactory.INSTANCE).build(); + PoolingHttpClientConnectionManager connManager = + new PoolingHttpClientConnectionManager(socketFactoryRegistry); + connManager.setDefaultMaxPerRoute(CONNECTIONS_PER_ROUTE); + connManager.setMaxTotal(MAX_CONNECTIONS); + connManager.setValidateAfterInactivity(MAX_CONNECTION_INACTIVITY); + return connManager; + } + + private SSLConnectionSocketFactory buildSslSocketFactory() { + return new SSLConnectionSocketFactory(buildSslContext(), + NoopHostnameVerifier.INSTANCE); + } + + private SSLContext buildSslContext() { + try { + TrustManager[] trustAllCerts = + new TrustManager[] {new DummyX509TrustManager()}; + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustAllCerts, null); + return context; + } catch (KeyManagementException | NoSuchAlgorithmException e) { + log.warn("Error building SSLContext object", e); + return null; + } + } + + private static class DummyX509TrustManager implements X509TrustManager { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + // no check + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + // no check + } + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpResponseHandler.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpResponseHandler.java new file mode 100644 index 0000000..73d9f28 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpResponseHandler.java
@@ -0,0 +1,65 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; + +import com.googlesource.gerrit.plugins.webhooks.HttpResponseHandler.HttpResult; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ResponseHandler; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +class HttpResponseHandler implements ResponseHandler<HttpResult> { + + static class HttpResult { + final boolean successful; + final String message; + + HttpResult(boolean successful, String message) { + this.successful = successful; + this.message = message; + } + } + + private static final Logger log = + LoggerFactory.getLogger(HttpResponseHandler.class); + + @Override + public HttpResult handleResponse(HttpResponse response) { + return new HttpResult(isSuccessful(response), parseResponse(response)); + } + + private boolean isSuccessful(HttpResponse response) { + return response.getStatusLine().getStatusCode() == SC_NO_CONTENT; + } + + private String parseResponse(HttpResponse response) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + return EntityUtils.toString(entity); + } catch (IOException e) { + log.error("Error parsing entity", e); + } + } + return ""; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpSession.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpSession.java new file mode 100644 index 0000000..14c2edf --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/HttpSession.java
@@ -0,0 +1,42 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; + +import com.google.common.net.MediaType; +import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.webhooks.HttpResponseHandler.HttpResult; + +class HttpSession { + private final CloseableHttpClient httpClient; + + @Inject + HttpSession(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + + HttpResult post(String endpoint, String content) throws IOException { + HttpPost post = new HttpPost(endpoint); + post.addHeader("Content-Type", MediaType.JSON_UTF_8.toString()); + post.setEntity(new StringEntity(content, StandardCharsets.UTF_8)); + return httpClient.execute(post, new HttpResponseHandler()); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/Module.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/Module.java new file mode 100644 index 0000000..d749e9c --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/Module.java
@@ -0,0 +1,38 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import java.util.concurrent.Executor; + +import org.apache.http.impl.client.CloseableHttpClient; + +import com.google.gerrit.common.EventListener; +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.inject.AbstractModule; +import com.google.inject.Scopes; + +public class Module extends AbstractModule { + + @Override + protected void configure() { + bind(Executor.class) + .annotatedWith(WebHooksExecutor.class) + .toProvider(ExecutorProvider.class); + bind(Configuration.class).in(Scopes.SINGLETON); + bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class) + .in(Scopes.SINGLETON); + DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/WebHooksExecutor.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/WebHooksExecutor.java new file mode 100644 index 0000000..45b25cd --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/WebHooksExecutor.java
@@ -0,0 +1,26 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.webhooks; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; + +import com.google.inject.BindingAnnotation; + +@Retention(RUNTIME) +@BindingAnnotation +@interface WebHooksExecutor { +}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md new file mode 100644 index 0000000..62783c9 --- /dev/null +++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1 @@ +The @PLUGIN@ plugin allows to propagate Gerrit events to remote http endpoints.
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md new file mode 100644 index 0000000..3babed2 --- /dev/null +++ b/src/main/resources/Documentation/build.md
@@ -0,0 +1,106 @@ +Build +===== + +This plugin is built with Buck. + +Two build modes are supported: Standalone and in Gerrit tree. Standalone +build mode is recommended, as this mode doesn't require local Gerrit +tree to exist. + +Build standalone +---------------- + +Clone bucklets library: + +``` + git clone https://gerrit.googlesource.com/bucklets + +``` +and link it to @PLUGIN@ directory: + +``` + cd @PLUGIN@ && ln -s ../bucklets . +``` + +Add link to the .buckversion file: + +``` + cd @PLUGIN@ && ln -s bucklets/buckversion .buckversion +``` + +Add link to the .watchmanconfig file: + +``` + cd @PLUGIN@ && ln -s bucklets/watchmanconfig .watchmanconfig +``` + +To build the plugin, issue the following command: + +``` + buck build plugin +``` + +The output is created in: + +``` + buck-out/gen/@PLUGIN@.jar +``` + +This project can be imported into the Eclipse IDE: + +``` + ./bucklets/tools/eclipse.py +``` + +To execute the tests run: + +``` + buck test +``` + +To build plugin sources run: + +``` + buck build src +``` + +The output is created in: + +``` + buck-out/gen/@PLUGIN@-sources.jar +``` + +Build in Gerrit tree +-------------------- + +Clone or link this plugin to the plugins directory of Gerrit's source +tree, and issue the command: + +``` + buck build plugins/@PLUGIN@ +``` + +The output is created in: + +``` + buck-out/gen/plugins/@PLUGIN@/@PLUGIN@.jar +``` + +This project can be imported into the Eclipse IDE: + +``` + ./tools/eclipse/project.py +``` + +To execute the tests run: + +``` + buck test --include @PLUGIN@ +``` + +How to build the Gerrit Plugin API is described in the [Gerrit +documentation](../../../Documentation/dev-buck.html#_extension_and_plugin_api_jar_files). + +[Back to @PLUGIN@ documentation index][index] + +[index]: index.html
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md new file mode 100644 index 0000000..137c260 --- /dev/null +++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,45 @@ +@PLUGIN@ Configuration +========================= + +The @PLUGIN@ plugin's per project configuration is stored in the +`webhooks.config` file in project's `refs/meta/config` branch. +The configuration is inheritable. + +Global @PLUGIN@ plugin configuration is stored in the `gerrit.config` file. + +File 'webhooks.config' +---------------------- + +remote.NAME.url +: Address of the remote server to post events to. + +remote.NAME.event +: Type of the event which will be posted to the remote url. Multiple event + types can be specified, listing event types which should be posted. + When no event type is configured, all events will be posted. + +File 'gerrit.config' +-------------------- + +@PLUGIN@.connectionTimeout +: Maximum interval of time in milliseconds the plugin waits for a connection + to the target instance. When not specified, the default value is set to 5000ms. + +@PLUGIN@.socketTimeout +: Maximum interval of time in milliseconds the plugin waits for a response from the + target instance once the connection has been established. When not specified, + the default value is set to 5000ms. + +@PLUGIN@.maxTries +: Maximum number of times the plugin should attempt when posting an event to + the target url. Setting this value to 0 will disable retries. When not + specified, the default value is 5. After this number of failed tries, an + error is logged. + +@PLUGIN@.retryInterval +: The interval of time in milliseconds between the subsequent auto-retries. + When not specified, the default value is set to 1000ms. + +@PLUGIN@.threadPoolSize +: Maximum number of threads used to send events to the target instance. + Defaults to 1. \ No newline at end of file