From af4e085def96158cc0cbdea214d77c4147fc24a1 Mon Sep 17 00:00:00 2001 From: kdelay Date: Wed, 7 Oct 2026 10:33:46 +0900 Subject: [PATCH] Only apply bus env changes when the event targets the app EnvironmentChangeListener applied every EnvironmentChangeRemoteApplicationEvent it saw. Events published by the busenv endpoint are also delivered to local listeners, so POST /actuator/busenv/{destination} changed the environment of the instance that received the request even when the destination was another service. RefreshListener and ShutdownListener already check ServiceMatcher#isForSelf for the same reason (spring-cloud-bus#68). Do the same here. Signed-off-by: kdelay --- .../bus/event/EnvironmentChangeListener.java | 8 ++ ...ronmentChangeListenerIntegrationTests.java | 88 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 bus/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/EnvironmentChangeListenerIntegrationTests.java diff --git a/bus/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/EnvironmentChangeListener.java b/bus/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/EnvironmentChangeListener.java index a12b1f7af3..0a3ac4aa64 100644 --- a/bus/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/EnvironmentChangeListener.java +++ b/bus/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/EnvironmentChangeListener.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.bus.ServiceMatcher; import org.springframework.cloud.context.environment.EnvironmentManager; import org.springframework.context.ApplicationListener; @@ -35,8 +36,15 @@ public class EnvironmentChangeListener implements ApplicationListener values = event.getValues(); log.info("Received remote environment change request. Keys/values to update " + values); for (Map.Entry entry : values.entrySet()) { diff --git a/bus/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/EnvironmentChangeListenerIntegrationTests.java b/bus/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/EnvironmentChangeListenerIntegrationTests.java new file mode 100644 index 0000000000..46ccc10020 --- /dev/null +++ b/bus/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/EnvironmentChangeListenerIntegrationTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2026-present 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 + * + * https://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.bus; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = EnvironmentChangeListenerIntegrationTests.MyApp.class, + properties = { "management.endpoints.web.exposure.include=*", "spring.application.name=foobar" }) +@AutoConfigureTestRestTemplate +public class EnvironmentChangeListenerIntegrationTests { + + @Autowired + private TestRestTemplate rest; + + @Autowired + private Environment environment; + + @MockitoBean + private BusBridge busBridge; + + @Test + public void environmentIsOnlyChangedWhenEventTargetsApp() { + assertThat(rest.postForEntity("/actuator/busenv/demoapp", body("bus.test.other", "changed"), String.class) + .getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(environment.getProperty("bus.test.other")).isNull(); + + assertThat(rest.postForEntity("/actuator/busenv/foobar", body("bus.test.self", "changed"), String.class) + .getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(environment.getProperty("bus.test.self")).isEqualTo("changed"); + + verify(busBridge, times(2)).send(any()); + } + + private static Map body(String name, String value) { + Map body = new HashMap<>(); + body.put("name", name); + body.put("value", value); + return body; + } + + // no component scan: it would pick up the bus configuration classes in this + // package as regular configuration and evaluate them before auto-configuration + @SpringBootConfiguration + @EnableAutoConfiguration + @Import(TestChannelBinderConfiguration.class) + static class MyApp { + + } + +}