Skip to content

Commit 960f865

Browse files
committed
Merge branch '3.1.x' into 3.2.x
2 parents d660bb6 + 877505e commit 960f865

File tree

3 files changed

+167
-11
lines changed

3 files changed

+167
-11
lines changed

spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadUtil.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
2828
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
2929
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
30+
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
3031
import org.springframework.core.env.CompositePropertySource;
3132
import org.springframework.core.env.ConfigurableEnvironment;
3233
import org.springframework.core.env.MapPropertySource;
@@ -110,6 +111,10 @@ else if (source instanceof MountConfigMapPropertySource mountConfigMapPropertySo
110111
// we know that the type is correct here
111112
managedSources.add((S) mountConfigMapPropertySource);
112113
}
114+
else if (source instanceof SecretsPropertySource secretsPropertySource) {
115+
// we know that the type is correct here
116+
managedSources.add((S) secretsPropertySource);
117+
}
113118
else if (source instanceof BootstrapPropertySource<?> bootstrapPropertySource) {
114119
PropertySource<?> propertySource = bootstrapPropertySource.getDelegate();
115120
LOG.debug(() -> "bootstrap delegate class : " + propertySource.getClass());
@@ -120,6 +125,10 @@ else if (propertySource instanceof MountConfigMapPropertySource mountConfigMapPr
120125
// we know that the type is correct here
121126
managedSources.add((S) mountConfigMapPropertySource);
122127
}
128+
else if (propertySource instanceof SecretsPropertySource secretsPropertySource) {
129+
// we know that the type is correct here
130+
managedSources.add((S) secretsPropertySource);
131+
}
123132
}
124133
}
125134

spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadUtilTests.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
2929
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
30+
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
31+
import org.springframework.cloud.kubernetes.commons.config.SourceData;
3032
import org.springframework.core.env.CompositePropertySource;
3133
import org.springframework.core.env.EnumerablePropertySource;
3234
import org.springframework.core.env.MapPropertySource;
@@ -127,8 +129,8 @@ void testFindPropertySources() {
127129
MockEnvironment environment = new MockEnvironment();
128130
MutablePropertySources propertySources = environment.getPropertySources();
129131
propertySources.addFirst(new OneComposite());
130-
propertySources.addFirst(new PlainPropertySource("plain"));
131-
propertySources.addFirst(new OneBootstrap(new EnumerablePropertySource<>("enumerable") {
132+
propertySources.addFirst(new PlainPropertySource<>("plain"));
133+
propertySources.addFirst(new OneBootstrap<>(new EnumerablePropertySource<>("enumerable") {
132134
@Override
133135
public String[] getPropertyNames() {
134136
return new String[0];
@@ -143,11 +145,35 @@ public Object getProperty(String name) {
143145

144146
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
145147
environment);
146-
Assertions.assertEquals(4, result.size());
148+
Assertions.assertEquals(3, result.size());
147149
Assertions.assertEquals("b", result.get(0).getProperty("a"));
148150
Assertions.assertEquals("plain", result.get(1).getProperty(""));
149-
Assertions.assertEquals("from-bootstrap", result.get(2).getProperty(""));
150-
Assertions.assertEquals("from-inner-two-composite", result.get(3).getProperty(""));
151+
Assertions.assertEquals("from-inner-two-composite", result.get(2).getProperty(""));
152+
}
153+
154+
@Test
155+
void testSecretsPropertySource() {
156+
MockEnvironment environment = new MockEnvironment();
157+
MutablePropertySources propertySources = environment.getPropertySources();
158+
propertySources.addFirst(new SecretsPropertySource(new SourceData("secret", Map.of("a", "b"))));
159+
160+
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
161+
environment);
162+
assertThat(result.size()).isEqualTo(1);
163+
assertThat(result.get(0).getProperty("a")).isEqualTo("b");
164+
}
165+
166+
@Test
167+
void testBootstrapSecretsPropertySource() {
168+
MockEnvironment environment = new MockEnvironment();
169+
MutablePropertySources propertySources = environment.getPropertySources();
170+
propertySources
171+
.addFirst(new OneBootstrap<>(new SecretsPropertySource(new SourceData("secret", Map.of("a", "b")))));
172+
173+
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
174+
environment);
175+
assertThat(result.size()).isEqualTo(1);
176+
assertThat(result.get(0).getProperty("a")).isEqualTo("b");
151177
}
152178

153179
private static final class OneComposite extends CompositePropertySource {
@@ -171,12 +197,12 @@ private TwoComposite() {
171197

172198
@Override
173199
public Collection<PropertySource<?>> getPropertySources() {
174-
return List.of(new PlainPropertySource("from-inner-two-composite"));
200+
return List.of(new PlainPropertySource<>("from-inner-two-composite"));
175201
}
176202

177203
}
178204

179-
private static final class PlainPropertySource extends PropertySource<String> {
205+
private static final class PlainPropertySource<T> extends PropertySource<T> {
180206

181207
private PlainPropertySource(String name) {
182208
super(name);
@@ -189,15 +215,18 @@ public Object getProperty(String name) {
189215

190216
}
191217

192-
private static final class OneBootstrap extends BootstrapPropertySource<String> {
218+
private static final class OneBootstrap<T> extends BootstrapPropertySource<T> {
219+
220+
private final EnumerablePropertySource<T> delegate;
193221

194-
private OneBootstrap(EnumerablePropertySource<String> delegate) {
222+
private OneBootstrap(EnumerablePropertySource<T> delegate) {
195223
super(delegate);
224+
this.delegate = delegate;
196225
}
197226

198227
@Override
199-
public PropertySource<String> getDelegate() {
200-
return new PlainPropertySource("from-bootstrap");
228+
public PropertySource<T> getDelegate() {
229+
return delegate;
201230
}
202231

203232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2013-2025 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+
* https://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.kubernetes.k8s.client.reload.it;
18+
19+
import java.nio.charset.StandardCharsets;
20+
import java.time.Duration;
21+
import java.util.Map;
22+
23+
import io.kubernetes.client.openapi.apis.CoreV1Api;
24+
import io.kubernetes.client.openapi.models.V1Secret;
25+
import org.junit.jupiter.api.AfterAll;
26+
import org.junit.jupiter.api.BeforeAll;
27+
import org.junit.jupiter.api.Test;
28+
import org.testcontainers.k3s.K3sContainer;
29+
30+
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
31+
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
32+
import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;
33+
import org.springframework.http.HttpMethod;
34+
import org.springframework.web.reactive.function.client.WebClient;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.awaitility.Awaitility.await;
38+
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.builder;
39+
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.retrySpec;
40+
41+
/**
42+
* @author wind57
43+
*/
44+
class K8sClientSecretMountBootstrapPollingIT extends K8sClientReloadBase {
45+
46+
private static final String IMAGE_NAME = "spring-cloud-kubernetes-k8s-client-reload";
47+
48+
private static final String NAMESPACE = "default";
49+
50+
private static final K3sContainer K3S = Commons.container();
51+
52+
private static Util util;
53+
54+
private static CoreV1Api coreV1Api;
55+
56+
@BeforeAll
57+
static void beforeAllLocal() throws Exception {
58+
K3S.start();
59+
Commons.validateImage(IMAGE_NAME, K3S);
60+
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
61+
62+
util = new Util(K3S);
63+
coreV1Api = new CoreV1Api();
64+
util.setUp(NAMESPACE);
65+
manifestsSecret(Phase.CREATE, util, NAMESPACE, IMAGE_NAME);
66+
}
67+
68+
@AfterAll
69+
static void afterAll() {
70+
manifestsSecret(Phase.DELETE, util, NAMESPACE, IMAGE_NAME);
71+
}
72+
73+
/**
74+
* <pre>
75+
* - we have bootstrap enabled
76+
* - we will 'locate' property sources from secrets.
77+
* - there are no explicit secrets to search for, but what we will also read,
78+
* is 'spring.cloud.kubernetes.secret.paths', which we have set to
79+
* '/tmp/application.properties'
80+
* in this test. That is populated by the volumeMounts (see mount/deployment-with-secret.yaml)
81+
* - we first assert that we are actually reading the path based source
82+
*
83+
* - we then change the secret content, wait for k8s to pick it up and replace them
84+
* - our polling will then detect that change, and trigger a reload.
85+
* </pre>
86+
*/
87+
@Test
88+
void test() throws Exception {
89+
WebClient webClient = builder().baseUrl("http://localhost:32321/secret").build();
90+
String result = webClient.method(HttpMethod.GET)
91+
.retrieve()
92+
.bodyToMono(String.class)
93+
.retryWhen(retrySpec())
94+
.block();
95+
96+
// we first read the initial value from the configmap
97+
assertThat(result).isEqualTo("initial");
98+
99+
// replace data in secret and wait for k8s to pick it up
100+
// our polling will detect that and restart the app
101+
V1Secret secret = (V1Secret) util.yaml("mount/secret.yaml");
102+
secret.setData(Map.of("from.properties.secret.key", "as-mount-changed".getBytes(StandardCharsets.UTF_8)));
103+
coreV1Api.replaceNamespacedSecret("secret-reload", NAMESPACE, secret, null, null, null, null);
104+
105+
Commons.waitForLogStatement("Detected change in config maps/secrets, reload will be triggered", K3S,
106+
IMAGE_NAME);
107+
108+
await().atMost(Duration.ofSeconds(120))
109+
.pollInterval(Duration.ofSeconds(1))
110+
.until(() -> webClient.method(HttpMethod.GET)
111+
.retrieve()
112+
.bodyToMono(String.class)
113+
.retryWhen(retrySpec())
114+
.block()
115+
.equals("as-mount-changed"));
116+
}
117+
118+
}

0 commit comments

Comments
 (0)