Add bitbucket webhook watch.
fixes gh-243
This commit is contained in:
@@ -618,8 +618,8 @@ initialize the same way as any other application.
|
||||
|
||||
== Push Notifications and Spring Cloud Bus
|
||||
|
||||
Many source code repository providers (like Github or Gitlab for
|
||||
instance) will notify you of changes in a repository through a
|
||||
Many source code repository providers (like Github, Gitlab or Bitbucket
|
||||
for instance) will notify you of changes in a repository through a
|
||||
webhook. You can configure the webhook via the provider's user
|
||||
interface as a URL and a set of events in which you are
|
||||
interested. For instance
|
||||
@@ -635,13 +635,17 @@ When the webhook is activated the Config Server will send a
|
||||
might have changed. The change detection can be strategized, but by
|
||||
default it just looks for changes in files that match the application
|
||||
name (e.g. "foo.properties" is targeted at the "foo" application, and
|
||||
"application.properties" is targeted at all applications). The strategy if you want to override the behaviour is `PropertyPathNotificationExtractor` which accepts the request headers and body as parameters and returns a list of file paths that changed.
|
||||
"application.properties" is targeted at all applications). The strategy
|
||||
if you want to override the behaviour is `PropertyPathNotificationExtractor`
|
||||
which accepts the request headers and body as parameters and returns a list
|
||||
of file paths that changed.
|
||||
|
||||
The default configuration works out of the box with Github or
|
||||
Gitlab. In addition to the JSON notifications from Github and Gitlab
|
||||
you can trigger a change notification by POSTing to "/monitor" with a
|
||||
form-encoded body parameters `path={name}`. This will broadcast to
|
||||
applications matching the "{name}" pattern (can contain wildcards).
|
||||
The default configuration works out of the box with Github, Gitlab or
|
||||
Bitbucket. In addition to the JSON notifications from Github, Gitlab
|
||||
or Bitbucket you can trigger a change notification by POSTing to
|
||||
"/monitor" with a form-encoded body parameters `path={name}`. This will
|
||||
broadcast to applications matching the "{name}" pattern (can contain
|
||||
wildcards).
|
||||
|
||||
NOTE: the `RefreshRemoteApplicationEvent` will only be transmitted if
|
||||
the `spring-cloud-bus` is activated in the Config Server and in the
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.config.monitor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 100)
|
||||
public class BitbucketPropertyPathNotificationExtractor
|
||||
implements PropertyPathNotificationExtractor {
|
||||
|
||||
@Override
|
||||
public PropertyPathNotification extract(MultiValueMap<String, String> headers,
|
||||
Map<String, Object> request) {
|
||||
if ("repo:push".equals(headers.getFirst("X-Event-Key")) &&
|
||||
StringUtils.hasText(headers.getFirst("X-Hook-UUID"))) {
|
||||
Object push = request.get("push");
|
||||
if (push instanceof Map && ((Map)push).get("changes") instanceof Collection) {
|
||||
// Bitbucket doesn't tell us the files that changed so this is a
|
||||
// broadcast to all apps
|
||||
return new PropertyPathNotification("application.yml");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,4 +54,10 @@ public class EnvironmentMonitorAutoConfiguration {
|
||||
return new GitlabPropertyPathNotificationExtractor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.bitbucket.enabled", havingValue="true", matchIfMissing=true)
|
||||
public BitbucketPropertyPathNotificationExtractor bitbucketPropertyPathNotificationExtractor() {
|
||||
return new BitbucketPropertyPathNotificationExtractor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.config.monitor;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BitbucketPropertyPathNotificationExtractorTests {
|
||||
|
||||
private BitbucketPropertyPathNotificationExtractor extractor = new BitbucketPropertyPathNotificationExtractor();
|
||||
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@Test
|
||||
public void bitbucketSample() throws Exception {
|
||||
// https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html#EventPayloads-Push
|
||||
Map<String, Object> value = readPayload("bitbucket.json");
|
||||
setHeaders("repo:push");
|
||||
PropertyPathNotification extracted = this.extractor.extract(this.headers, value);
|
||||
assertNotNull(extracted);
|
||||
assertEquals("application.yml", extracted.getPaths()[0]);
|
||||
}
|
||||
|
||||
private void setHeaders(String eventKey) {
|
||||
this.headers.set("X-Event-Key", eventKey);
|
||||
this.headers.set("X-Hook-UUID", UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAPushNotDetected() throws Exception {
|
||||
assertNotExtracted("bitbucket.json", "issue:created");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gitlabNotDetected() throws Exception {
|
||||
assertNotExtracted("gitlab.json", "repo:push");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void githubNotDetected() throws Exception {
|
||||
assertNotExtracted("github.json", "repo:push");
|
||||
}
|
||||
|
||||
private void assertNotExtracted(String path, String eventKey) throws java.io.IOException {
|
||||
Map<String, Object> value = readPayload(path);
|
||||
setHeaders(eventKey);
|
||||
PropertyPathNotification extracted = this.extractor.extract(this.headers, value);
|
||||
assertNull(extracted);
|
||||
}
|
||||
|
||||
private Map<String, Object> readPayload(String path) throws java.io.IOException {
|
||||
return new ObjectMapper().readValue(
|
||||
new ClassPathResource(path).getInputStream(),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class EnvironmentMonitorAutoConfigurationTests {
|
||||
PropertyPlaceholderAutoConfiguration.class).properties("server.port=-1")
|
||||
.run();
|
||||
PropertyPathEndpoint endpoint = context.getBean(PropertyPathEndpoint.class);
|
||||
assertEquals(3,
|
||||
assertEquals(4,
|
||||
((Collection<?>) ReflectionTestUtils.getField(
|
||||
ReflectionTestUtils.getField(endpoint, "extractor"),
|
||||
"extractors")).size());
|
||||
|
||||
206
spring-cloud-config-monitor/src/test/resources/bitbucket.json
Normal file
206
spring-cloud-config-monitor/src/test/resources/bitbucket.json
Normal file
@@ -0,0 +1,206 @@
|
||||
{
|
||||
"actor": {
|
||||
"username": "emmap1",
|
||||
"display_name": "Emma",
|
||||
"uuid": "{a54f16da-24e9-4d7f-a3a7-b1ba2cd98aa3}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/api/2.0/users/emmap1"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://api.bitbucket.org/emmap1"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket-api-assetroot.s3.amazonaws.com/c/photos/2015/Feb/26/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/api/2.0/repositories/bitbucket/bitbucket"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://api.bitbucket.org/bitbucket/bitbucket"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://api-staging-assetroot.s3.amazonaws.com/c/photos/2014/Aug/01/bitbucket-logo-2629490769-3_avatar.png"
|
||||
}
|
||||
},
|
||||
"uuid": "{673a6070-3421-46c9-9d48-90745f7bfe8e}",
|
||||
"full_name": "team_name/repo_name",
|
||||
"name": "repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
},
|
||||
"push": {
|
||||
"changes": [
|
||||
{
|
||||
"new": {
|
||||
"type": "branch",
|
||||
"name": "name-of-branch",
|
||||
"target": {
|
||||
"type": "commit",
|
||||
"hash": "709d658dc5b6d6afcd46049c2f332ee3f515a67d",
|
||||
"author": {
|
||||
"username": "emmap1",
|
||||
"display_name": "Emma",
|
||||
"uuid": "{a54f16da-24e9-4d7f-a3a7-b1ba2cd98aa3}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/api/2.0/users/emmap1"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://api.bitbucket.org/emmap1"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket-api-assetroot.s3.amazonaws.com/c/photos/2015/Feb/26/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "new commit message\n",
|
||||
"date": "2015-06-09T03:34:49+00:00",
|
||||
"parents": [
|
||||
{
|
||||
"type": "commit",
|
||||
"hash": "1e65c05c1d5171631d92438a13901ca7dae9618c",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commit/8cbbd65829c7ad834a97841e0defc965718036a0"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/commits/8cbbd65829c7ad834a97841e0defc965718036a0"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commit/c4b2b7914156a878aa7c9da452a09fb50c2091f2"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/commits/c4b2b7914156a878aa7c9da452a09fb50c2091f2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/refs/branches/master"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commits/master"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/branch/master"
|
||||
}
|
||||
}
|
||||
},
|
||||
"old": {
|
||||
"type": "branch",
|
||||
"name": "name-of-branch",
|
||||
"target": {
|
||||
"type": "commit",
|
||||
"hash": "1e65c05c1d5171631d92438a13901ca7dae9618c",
|
||||
"author": {
|
||||
"username": "emmap1",
|
||||
"display_name": "Emma",
|
||||
"uuid": "{a54f16da-24e9-4d7f-a3a7-b1ba2cd98aa3}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/api/2.0/users/emmap1"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://api.bitbucket.org/emmap1"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket-api-assetroot.s3.amazonaws.com/c/photos/2015/Feb/26/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "old commit message\n",
|
||||
"date": "2015-06-08T21:34:56+00:00",
|
||||
"parents": [
|
||||
{
|
||||
"type": "commit",
|
||||
"hash": "e0d0c2041e09746be5ce4b55067d5a8e3098c843",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commit/9c4a3452da3bc4f37af5a6bb9c784246f44406f7"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/commits/9c4a3452da3bc4f37af5a6bb9c784246f44406f7"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commit/b99ea6dad8f416e57c5ca78c1ccef590600d841b"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/commits/b99ea6dad8f416e57c5ca78c1ccef590600d841b"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/refs/branches/master"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commits/master"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/branch/master"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user_name/repo_name/branches/compare/c4b2b7914156a878aa7c9da452a09fb50c2091f2..b99ea6dad8f416e57c5ca78c1ccef590600d841b"
|
||||
},
|
||||
"diff": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/diff/c4b2b7914156a878aa7c9da452a09fb50c2091f2..b99ea6dad8f416e57c5ca78c1ccef590600d841b"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user_name/repo_name/commits?include=c4b2b7914156a878aa7c9da452a09fb50c2091f2&exclude=b99ea6dad8f416e57c5ca78c1ccef590600d841b"
|
||||
}
|
||||
},
|
||||
"created": false,
|
||||
"forced": false,
|
||||
"closed": false,
|
||||
"commits": [
|
||||
{
|
||||
"hash": "03f4a7270240708834de475bcf21532d6134777e",
|
||||
"type": "commit",
|
||||
"message": "commit message\n",
|
||||
"author": {
|
||||
"username": "emmap1",
|
||||
"display_name": "Emma",
|
||||
"uuid": "{a54f16da-24e9-4d7f-a3a7-b1ba2cd98aa3}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/api/2.0/users/emmap1"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://api.bitbucket.org/emmap1"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket-api-assetroot.s3.amazonaws.com/c/photos/2015/Feb/26/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/user/repo/commit/03f4a7270240708834de475bcf21532d6134777e"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/user/repo/commits/03f4a7270240708834de475bcf21532d6134777e"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"truncated": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user