Skip to content

Commit 8d8e07a

Browse files
author
Dave Syer
committed
Push events for config changes
Works from local repos or with explicit POST to /monitor with path=<serviceId>, also supports webhooks from github and gitlab.
1 parent dc5097b commit 8d8e07a

19 files changed

Lines changed: 1339 additions & 9 deletions

docs/src/main/asciidoc/spring-cloud-config.adoc

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ TIP: the `{name:value}` prefixes can also be added to plaintext posted
489489
to the `/encrypt` endpoint, if you want to let the Config Server
490490
handle all encryption as well as decryption.
491491

492-
=== Embedding the Config Server
492+
== Embedding the Config Server
493493

494494
The Config Server runs best as a standalone application, but if you
495495
need to you can embed it in another application. Just use the
@@ -505,6 +505,41 @@ own remote repository. The flag is off by default because it can delay
505505
startup, but when embedded in another application it makes sense to
506506
initialize the same way as any other application.
507507

508+
== Push Notifications and Spring Cloud Bus
509+
510+
Many source code repository providers (like Github or Gitlab for
511+
instance) will notify you of changes in a repository through a
512+
webhook. You can configure the webhook via the provider's user
513+
interface as a URL and a set of events in which you are
514+
interested. For instance
515+
https://developer.github.com/v3/activity/events/types/#pushevent[Github]
516+
will POST to the webhook with a JSON body containing a list of
517+
commits, and a header "X-Github-Event" equal to "push". If you add a
518+
dependency on the `spring-cloud-config-monitor` library and activate
519+
the Spring Cloud Bus in your Config Server, then a "/monitor" endpoint
520+
is enabled.
521+
522+
When the webhook is activated the Config Server will send a
523+
`RefreshRemoteApplicationEvent` targeted at the applications it thinks
524+
might have changed. The change detection can be strategized, but by
525+
default it just looks for changes in files that match the application
526+
name (e.g. "foo.properties" is targeted at the "foo" application, and
527+
"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.
528+
529+
The default configuration works out of the box with Github or
530+
Gitlab. In addition to the JSON notifications from Github and Gitlab
531+
you can trigger a change notification by POSTing to "/monitor" with a
532+
form-encoded body parameters `path={name}`. This will broadcast to
533+
applications matching the "{name}" pattern (can contain wildcards).
534+
535+
NOTE: the `RefreshRemoteApplicationEvent` will only be transmitted if
536+
the `spring-cloud-bus` is activated in the Config Server and in the
537+
client application.
538+
539+
NOTE: the default configuration also detects filesystem changes in
540+
local git repositories (the webhook is not used in that case but as
541+
soon as you edit a config file a refresh will be broadcast).
542+
508543
== Spring Cloud Config Client
509544

510545
A Spring Boot application can take immediate advantage of the Spring

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
</scm>
2323
<properties>
2424
<bintray.package>config</bintray.package>
25+
<spring-cloud-bus.version>1.1.0.BUILD-SNAPSHOT</spring-cloud-bus.version>
2526
<spring-cloud-commons.version>1.1.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
2627
</properties>
2728
<modules>
2829
<module>spring-cloud-config-client</module>
2930
<module>spring-cloud-config-server</module>
31+
<module>spring-cloud-config-monitor</module>
3032
<module>spring-cloud-config-sample</module>
31-
<module>spring-cloud-starter-config</module>
33+
<module>spring-cloud-starter-config</module>
3234
<module>docs</module>
3335
</modules>
3436
<dependencyManagement>
@@ -55,6 +57,11 @@
5557
<artifactId>spring-cloud-config-server</artifactId>
5658
<version>1.1.0.BUILD-SNAPSHOT</version>
5759
</dependency>
60+
<dependency>
61+
<groupId>org.springframework.cloud</groupId>
62+
<artifactId>spring-cloud-config-monitor</artifactId>
63+
<version>1.1.0.BUILD-SNAPSHOT</version>
64+
</dependency>
5865
<dependency>
5966
<groupId>org.springframework.retry</groupId>
6067
<artifactId>spring-retry</artifactId>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.cloud</groupId>
7+
<artifactId>spring-cloud-config</artifactId>
8+
<version>1.1.0.BUILD-SNAPSHOT</version>
9+
<relativePath>..</relativePath>
10+
</parent>
11+
<artifactId>spring-cloud-config-monitor</artifactId>
12+
<name>spring-cloud-config-monitor</name>
13+
<description>Spring Cloud Config Monitor</description>
14+
<properties>
15+
<main.basedir>${basedir}/../..</main.basedir>
16+
</properties>
17+
<dependencyManagement>
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.springframework.cloud</groupId>
21+
<artifactId>spring-cloud-bus-parent</artifactId>
22+
<version>${spring-cloud-bus.version}</version>
23+
<type>pom</type>
24+
<scope>import</scope>
25+
</dependency>
26+
</dependencies>
27+
</dependencyManagement>
28+
<dependencies>
29+
<dependency>
30+
<groupId>org.springframework.cloud</groupId>
31+
<artifactId>spring-cloud-config-server</artifactId>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.springframework.cloud</groupId>
35+
<artifactId>spring-cloud-bus</artifactId>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.projectlombok</groupId>
39+
<artifactId>lombok</artifactId>
40+
<!-- Only needed at compile time -->
41+
<optional>true</optional>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-test</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
</dependencies>
49+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.monitor;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import org.springframework.core.Ordered;
25+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
26+
import org.springframework.core.annotation.Order;
27+
import org.springframework.util.MultiValueMap;
28+
29+
/**
30+
* A {@link PropertyPathNotificationExtractor} that cycles through a set of (ordered) delegates,
31+
* looking for the first non-null outcome.
32+
*
33+
* @author Dave Syer
34+
*
35+
*/
36+
public class CompositePropertyPathNotificationExtractor
37+
implements PropertyPathNotificationExtractor {
38+
39+
private List<PropertyPathNotificationExtractor> extractors;
40+
41+
public CompositePropertyPathNotificationExtractor(
42+
List<PropertyPathNotificationExtractor> extractors) {
43+
this.extractors = new ArrayList<>();
44+
if (extractors != null) {
45+
this.extractors.addAll(extractors);
46+
}
47+
this.extractors.add(new SimplePropertyPathNotificationExtractor());
48+
AnnotationAwareOrderComparator.sort(this.extractors);
49+
}
50+
51+
@Override
52+
public PropertyPathNotification extract(MultiValueMap<String, String> headers,
53+
Map<String, Object> request) {
54+
for (PropertyPathNotificationExtractor extractor : this.extractors) {
55+
PropertyPathNotification result = extractor.extract(headers, request);
56+
if (result != null) {
57+
return result;
58+
}
59+
}
60+
return null;
61+
}
62+
63+
@Order(Ordered.LOWEST_PRECEDENCE - 200)
64+
private static class SimplePropertyPathNotificationExtractor
65+
implements PropertyPathNotificationExtractor {
66+
67+
@Override
68+
public PropertyPathNotification extract(MultiValueMap<String, String> headers,
69+
Map<String, Object> request) {
70+
Object object = request.get("path");
71+
if (object instanceof String) {
72+
return new PropertyPathNotification((String) object);
73+
}
74+
if (object instanceof Collection) {
75+
@SuppressWarnings("unchecked")
76+
Collection<String> collection = (Collection<String>) object;
77+
return new PropertyPathNotification(collection.toArray(new String[0]));
78+
}
79+
return null;
80+
}
81+
82+
}
83+
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.monitor;
18+
19+
import java.util.List;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.context.annotation.Import;
26+
27+
/**
28+
* @author Dave Syer
29+
*
30+
*/
31+
@Configuration
32+
@ConditionalOnWebApplication
33+
@Import(FileMonitorConfiguration.class)
34+
public class EnvironmentMonitorAutoConfiguration {
35+
36+
@Autowired(required=false)
37+
private List<PropertyPathNotificationExtractor> extractors;
38+
39+
@Bean
40+
public PropertyPathEndpoint propertyPathEndpoint() {
41+
return new PropertyPathEndpoint(new CompositePropertyPathNotificationExtractor(this.extractors));
42+
}
43+
}

0 commit comments

Comments
 (0)