Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/modules/ROOT/pages/server/push-notifications-and-bus.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,43 @@ The default configuration works out of the box with Github, Gitlab, Gitea, Gitee
In addition to the JSON notifications from Github, Gitlab, Gitee, or Bitbucket, you can trigger a change notification by POSTing to `/monitor` with form-encoded body parameters in the pattern of `path=\{application}`.
Doing so broadcasts to applications matching the `\{application}` pattern (which can contain wildcards).

[[ignored-monitor-paths]]
== Ignoring Paths

You can configure the Config Server to ignore specific paths when processing webhook notifications by setting `spring.cloud.config.server.monitor.ignored-paths` to a list of regular expressions.

Ignored paths are excluded before the Config Server determines which applications should be refreshed. This can be useful when a repository contains files that should not trigger configuration refreshes.

For example:

[source,yaml]
----
spring:
cloud:
config:
server:
monitor:
ignored-paths:
- "docker-compose/.*"
- "docs/.*"
----

Each regular expression is matched against the complete webhook path. A path is ignored when it matches any configured expression.

By default, the Config Server processes at most 20 paths from a single notification. You can change this limit with `spring.cloud.config.server.monitor.max-paths`. If a notification contains more paths than the configured maximum, the remaining paths are not processed and a warning is logged.

For example:

[source,yaml]
----
spring:
cloud:
config:
server:
monitor:
max-paths: 50
----

NOTE: The `RefreshRemoteApplicationEvent` is transmitted only if the `spring-cloud-bus` is activated in both the Config Server and in the client application.

NOTE: The default configuration also detects filesystem changes in local git repositories. In that case, the webhook is not used. However, as soon as you edit a config file, a refresh is broadcast.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ protected static class BusPropertyPathConfiguration {
public PropertyPathEndpoint propertyPathEndpoint(BusProperties busProperties,
MonitorConfigurationProperties monitorProperties) {
return new PropertyPathEndpoint(new CompositePropertyPathNotificationExtractor(this.extractors),
busProperties.getId(), monitorProperties.getMaxDashes());
busProperties.getId(), monitorProperties.getMaxDashes(), monitorProperties.getMaxPaths(),
monitorProperties.getIgnoredPaths());
}

// TODO: With the current implementation bus can't be disabled
Expand All @@ -85,7 +86,8 @@ public PropertyPathEndpoint noBusBeanPropertyPathEndpoint(
@Value("${spring.cloud.bus.id:application}") String id,
MonitorConfigurationProperties monitorProperties) {
return new PropertyPathEndpoint(new CompositePropertyPathNotificationExtractor(this.extractors), id,
monitorProperties.getMaxDashes());
monitorProperties.getMaxDashes(), monitorProperties.getMaxPaths(),
monitorProperties.getIgnoredPaths());
}

}
Expand All @@ -99,7 +101,8 @@ public PropertyPathEndpoint noBusPropertyPathEndpoint(@Value("${spring.cloud.bus
@Autowired(required = false) List<PropertyPathNotificationExtractor> extractors,
MonitorConfigurationProperties monitorProperties) {
return new PropertyPathEndpoint(new CompositePropertyPathNotificationExtractor(extractors), id,
monitorProperties.getMaxDashes());
monitorProperties.getMaxDashes(), monitorProperties.getMaxPaths(),
monitorProperties.getIgnoredPaths());
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package org.springframework.cloud.config.monitor;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("spring.cloud.config.server.monitor")
Expand All @@ -27,6 +30,11 @@ public class MonitorConfigurationProperties {
*/
public static final int DEFAULT_MAX_DASHES = 20;

/**
* Default maximum number of paths processed from a notification.
*/
public static final int DEFAULT_MAX_PATHS = 20;

private Endpoint endpoint = new Endpoint();

/**
Expand All @@ -36,6 +44,10 @@ public class MonitorConfigurationProperties {
*/
private int maxDashes = DEFAULT_MAX_DASHES;

private int maxPaths = DEFAULT_MAX_PATHS;

private List<String> ignoredPaths = new ArrayList<>();

private GitHub github = new GitHub();

private Gogs gogs = new Gogs();
Expand Down Expand Up @@ -112,6 +124,22 @@ public void setMaxDashes(int maxDashes) {
this.maxDashes = maxDashes;
}

public int getMaxPaths() {
return maxPaths;
}

public void setMaxPaths(int maxPaths) {
this.maxPaths = maxPaths;
}

public List<String> getIgnoredPaths() {
return ignoredPaths;
}

public void setIgnoredPaths(List<String> ignoredPaths) {
this.ignoredPaths = ignoredPaths;
}

public static class Endpoint {

private String path = "/monitor";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -66,14 +67,32 @@ public class PropertyPathEndpoint implements ApplicationEventPublisherAware {
*/
private final int maxDashes;

/**
* Upper bound on the number of paths processed from a notification. Prevents an
* externally supplied notification from causing an unbounded amount of path
* processing. Configurable via {@code spring.cloud.config.server.monitor.max-paths}.
*/
private final int maxPaths;

private final List<Pattern> ignoredPaths;

public PropertyPathEndpoint(PropertyPathNotificationExtractor extractor, String busId) {
this(extractor, busId, MonitorConfigurationProperties.DEFAULT_MAX_DASHES);
this(extractor, busId, MonitorConfigurationProperties.DEFAULT_MAX_DASHES,
MonitorConfigurationProperties.DEFAULT_MAX_PATHS, Collections.emptyList());
}

public PropertyPathEndpoint(PropertyPathNotificationExtractor extractor, String busId, int maxDashes) {
this(extractor, busId, maxDashes, MonitorConfigurationProperties.DEFAULT_MAX_PATHS, Collections.emptyList());
}

public PropertyPathEndpoint(PropertyPathNotificationExtractor extractor, String busId, int maxDashes, int maxPaths,
List<String> ignoredPaths) {
this.extractor = extractor;
this.busId = busId;
this.maxDashes = maxDashes;
this.maxPaths = maxPaths;
this.ignoredPaths = ignoredPaths == null ? Collections.emptyList()
: ignoredPaths.stream().map(Pattern::compile).toList();
}

/* for testing */ String getBusId() {
Expand All @@ -92,8 +111,19 @@ public Set<String> notifyByPath(@RequestHeader HttpHeaders headers, @RequestBody

Set<String> services = new LinkedHashSet<>();

for (String path : notification.getPaths()) {
services.addAll(guessServiceName(path));
String[] paths = notification.getPaths();
if (paths != null) {
int pathCount = Math.min(paths.length, this.maxPaths);
for (int i = 0; i < pathCount; i++) {
String path = paths[i];
if (!isIgnored(path)) {
services.addAll(guessServiceName(path));
}
}
if (paths.length > this.maxPaths) {
log.warn("Number of paths in notification exceeds the configured maximum of " + this.maxPaths
+ " (spring.cloud.config.server.monitor.max-paths); stopping path processing early");
}
}
if (this.applicationEventPublisher != null) {
for (String service : services) {
Expand Down Expand Up @@ -144,4 +174,8 @@ private Set<String> guessServiceName(String path) {
return services;
}

private boolean isIgnored(String path) {
return path != null && this.ignoredPaths.stream().anyMatch(pattern -> pattern.matcher(path).matches());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ public void testCanAddCustomPropertyPathNotificationExtractor() {
context.close();
}

@Test
public void testMonitorPathProperties() {
ConfigurableApplicationContext context = new SpringApplicationBuilder(BusConfig.class,
EnvironmentMonitorAutoConfiguration.class, TomcatServletWebServerAutoConfiguration.class,
ServerProperties.class, PropertyPlaceholderAutoConfiguration.class)
.properties("server.port=-1", "spring.cloud.config.server.monitor.max-paths=1",
"spring.cloud.config.server.monitor.ignored-paths=docker-compose/.*")
.run();

PropertyPathEndpoint endpoint = context.getBean(PropertyPathEndpoint.class);

assertThat(ReflectionTestUtils.getField(endpoint, "maxPaths")).isEqualTo(1);
assertThat((Collection<?>) ReflectionTestUtils.getField(endpoint, "ignoredPaths")).hasSize(1);

context.close();
}

@Configuration(proxyBeanMethods = false)
static class BusConfig {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,37 @@ public void testNotifyLimitsDashes() {
.hasSize(2);
}

@Test
public void testNotifyIgnoresConfiguredPaths() {
PropertyPathEndpoint ignoredEndpoint = new PropertyPathEndpoint(
new CompositePropertyPathNotificationExtractor(Collections.emptyList()), "abc1", 20, 20,
Collections.singletonList("docker-compose/.*"));
StaticApplicationContext publisher = new StaticApplicationContext();
ignoredEndpoint.setApplicationEventPublisher(publisher);
publisher.refresh();

List<String> request = new ArrayList<>();
request.add("docker-compose/docker-compose.yml");
request.add("foo.yml");

assertThat(ignoredEndpoint.notifyByForm(new HttpHeaders(), request).toString()).isEqualTo("[foo]");
}

@Test
public void testNotifyLimitsPaths() {
PropertyPathEndpoint limitedEndpoint = new PropertyPathEndpoint(
new CompositePropertyPathNotificationExtractor(Collections.emptyList()), "abc1", 20, 2,
Collections.emptyList());
StaticApplicationContext publisher = new StaticApplicationContext();
limitedEndpoint.setApplicationEventPublisher(publisher);
publisher.refresh();

List<String> request = new ArrayList<>();
request.add("foo.yml");
request.add("bar.yml");
request.add("baz.yml");

assertThat(limitedEndpoint.notifyByForm(new HttpHeaders(), request).toString()).isEqualTo("[foo, bar]");
}

}
Loading