Skip to content

Commit 1f42676

Browse files
committed
Merge branch '3.2.x' into 3.3.x
2 parents 2f5c9d2 + 0986d3b commit 1f42676

File tree

7 files changed

+268
-17
lines changed

7 files changed

+268
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2013-present 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.commons.config;
18+
19+
/**
20+
* @author wind57
21+
*/
22+
public final class MountSecretPropertySource extends SecretsPropertySource {
23+
24+
public MountSecretPropertySource(SourceData sourceData) {
25+
super(sourceData);
26+
}
27+
28+
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,33 +152,33 @@ protected void putPathConfig(CompositePropertySource composite) {
152152
* @author wind57
153153
*/
154154
private static class SecretsPropertySourceCollector
155-
implements Collector<Path, List<SecretsPropertySource>, List<SecretsPropertySource>> {
155+
implements Collector<Path, List<MountSecretPropertySource>, List<MountSecretPropertySource>> {
156156

157157
@Override
158-
public Supplier<List<SecretsPropertySource>> supplier() {
158+
public Supplier<List<MountSecretPropertySource>> supplier() {
159159
return ArrayList::new;
160160
}
161161

162162
@Override
163-
public BiConsumer<List<SecretsPropertySource>, Path> accumulator() {
163+
public BiConsumer<List<MountSecretPropertySource>, Path> accumulator() {
164164
return (list, filePath) -> {
165-
SecretsPropertySource source = property(filePath);
165+
MountSecretPropertySource source = property(filePath);
166166
if (source != null) {
167167
list.add(source);
168168
}
169169
};
170170
}
171171

172172
@Override
173-
public BinaryOperator<List<SecretsPropertySource>> combiner() {
173+
public BinaryOperator<List<MountSecretPropertySource>> combiner() {
174174
return (left, right) -> {
175175
left.addAll(right);
176176
return left;
177177
};
178178
}
179179

180180
@Override
181-
public Function<List<SecretsPropertySource>, List<SecretsPropertySource>> finisher() {
181+
public Function<List<MountSecretPropertySource>, List<MountSecretPropertySource>> finisher() {
182182
return Function.identity();
183183
}
184184

@@ -187,15 +187,15 @@ public Set<Characteristics> characteristics() {
187187
return EnumSet.of(Characteristics.UNORDERED, Characteristics.IDENTITY_FINISH);
188188
}
189189

190-
private SecretsPropertySource property(Path filePath) {
190+
private MountSecretPropertySource property(Path filePath) {
191191

192192
String fileName = filePath.getFileName().toString();
193193

194194
try {
195195
String content = new String(Files.readAllBytes(filePath)).trim();
196196
String sourceName = fileName.toLowerCase(Locale.ROOT);
197197
SourceData sourceData = new SourceData(sourceName, Map.of(fileName, content));
198-
return new SecretsPropertySource(sourceData);
198+
return new MountSecretPropertySource(sourceData);
199199
}
200200
catch (IOException e) {
201201
LOG.warn("Error reading properties file", e);

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +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;
30+
import org.springframework.cloud.kubernetes.commons.config.MountSecretPropertySource;
3131
import org.springframework.core.env.CompositePropertySource;
3232
import org.springframework.core.env.ConfigurableEnvironment;
3333
import org.springframework.core.env.MapPropertySource;
@@ -89,6 +89,7 @@ public static boolean reload(PropertySourceLocator locator, ConfigurableEnvironm
8989
* @deprecated this method will not be public in the next major release.
9090
*/
9191
@Deprecated(forRemoval = false)
92+
@SuppressWarnings("unchecked")
9293
public static <S extends PropertySource<?>> List<S> findPropertySources(Class<S> sourceClass,
9394
ConfigurableEnvironment environment) {
9495
List<S> managedSources = new ArrayList<>();
@@ -111,9 +112,9 @@ else if (source instanceof MountConfigMapPropertySource mountConfigMapPropertySo
111112
// we know that the type is correct here
112113
managedSources.add((S) mountConfigMapPropertySource);
113114
}
114-
else if (source instanceof SecretsPropertySource secretsPropertySource) {
115+
else if (source instanceof MountSecretPropertySource mountSecretPropertySource) {
115116
// we know that the type is correct here
116-
managedSources.add((S) secretsPropertySource);
117+
managedSources.add((S) mountSecretPropertySource);
117118
}
118119
else if (source instanceof BootstrapPropertySource<?> bootstrapPropertySource) {
119120
PropertySource<?> propertySource = bootstrapPropertySource.getDelegate();
@@ -125,9 +126,9 @@ else if (propertySource instanceof MountConfigMapPropertySource mountConfigMapPr
125126
// we know that the type is correct here
126127
managedSources.add((S) mountConfigMapPropertySource);
127128
}
128-
else if (propertySource instanceof SecretsPropertySource secretsPropertySource) {
129+
else if (propertySource instanceof MountSecretPropertySource mountSecretPropertySource) {
129130
// we know that the type is correct here
130-
managedSources.add((S) secretsPropertySource);
131+
managedSources.add((S) mountSecretPropertySource);
131132
}
132133
}
133134
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
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;
30+
import org.springframework.cloud.kubernetes.commons.config.MountSecretPropertySource;
3131
import org.springframework.cloud.kubernetes.commons.config.SourceData;
3232
import org.springframework.core.env.CompositePropertySource;
3333
import org.springframework.core.env.EnumerablePropertySource;
@@ -157,7 +157,7 @@ public Object getProperty(String name) {
157157
void testSecretsPropertySource() {
158158
MockEnvironment environment = new MockEnvironment();
159159
MutablePropertySources propertySources = environment.getPropertySources();
160-
propertySources.addFirst(new SecretsPropertySource(new SourceData("secret", Map.of("a", "b"))));
160+
propertySources.addFirst(new MountSecretPropertySource(new SourceData("secret", Map.of("a", "b"))));
161161

162162
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
163163
environment);
@@ -170,7 +170,7 @@ void testBootstrapSecretsPropertySource() {
170170
MockEnvironment environment = new MockEnvironment();
171171
MutablePropertySources propertySources = environment.getPropertySources();
172172
propertySources
173-
.addFirst(new OneBootstrap<>(new SecretsPropertySource(new SourceData("secret", Map.of("a", "b")))));
173+
.addFirst(new OneBootstrap<>(new MountSecretPropertySource(new SourceData("secret", Map.of("a", "b")))));
174174

175175
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
176176
environment);

spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/example/App.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626
@EnableConfigurationProperties(GreetingProperties.class)
2727
@SpringBootApplication
28-
class App {
28+
public class App {
2929

3030
public static void main(String[] args) {
3131
SpringApplication.run(App.class, args);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Copyright 2012-present 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.fabric8.config.reload_it;
18+
19+
import java.nio.charset.StandardCharsets;
20+
import java.time.Duration;
21+
import java.util.Base64;
22+
import java.util.Collections;
23+
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
import java.util.stream.Collectors;
27+
28+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
29+
import io.fabric8.kubernetes.api.model.SecretBuilder;
30+
import io.fabric8.kubernetes.client.Config;
31+
import io.fabric8.kubernetes.client.KubernetesClient;
32+
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
33+
import org.awaitility.Awaitility;
34+
import org.junit.jupiter.api.BeforeAll;
35+
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.api.extension.ExtendWith;
37+
38+
import org.springframework.beans.factory.annotation.Autowired;
39+
import org.springframework.boot.test.context.SpringBootTest;
40+
import org.springframework.boot.test.context.TestConfiguration;
41+
import org.springframework.boot.test.system.CapturedOutput;
42+
import org.springframework.boot.test.system.OutputCaptureExtension;
43+
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
44+
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
45+
import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector;
46+
import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource;
47+
import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator;
48+
import org.springframework.cloud.kubernetes.fabric8.config.example.App;
49+
import org.springframework.context.annotation.Bean;
50+
import org.springframework.context.annotation.Primary;
51+
import org.springframework.core.env.AbstractEnvironment;
52+
import org.springframework.core.env.ConfigurableEnvironment;
53+
import org.springframework.core.env.PropertySource;
54+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
55+
56+
import static org.assertj.core.api.Assertions.assertThat;
57+
58+
/**
59+
* Proves that
60+
* <a href="https://github.com/spring-cloud/spring-cloud-kubernetes/issues/2008">this</a>
61+
* issue is fixed.
62+
*
63+
* @author wind57
64+
*/
65+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
66+
properties = { "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.config.enabled=true",
67+
"spring.cloud.bootstrap.name=polling-reload-configmap-and-secret",
68+
"spring.main.cloud-platform=KUBERNETES", "spring.application.name=polling-reload-configmap-and-secret",
69+
"spring.main.allow-bean-definition-overriding=true",
70+
"spring.cloud.kubernetes.client.namespace=spring-k8s",
71+
"logging.level.org.springframework.cloud.kubernetes.commons.config.reload=debug" },
72+
classes = { PollingReloadConfigMapAndSecretTest.TestConfig.class, App.class })
73+
@EnableKubernetesMockClient(crud = true, https = false)
74+
@ExtendWith(OutputCaptureExtension.class)
75+
class PollingReloadConfigMapAndSecretTest {
76+
77+
private static final String NAMESPACE = "spring-k8s";
78+
79+
private static final AtomicBoolean STRATEGY_FOR_SECRET_CALLED = new AtomicBoolean(false);
80+
81+
private static KubernetesClient mockClient;
82+
83+
@Autowired
84+
private ConfigurableEnvironment environment;
85+
86+
@BeforeAll
87+
static void beforeAll() {
88+
// Configure the kubernetes master url to point to the mock server
89+
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
90+
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
91+
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
92+
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
93+
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
94+
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
95+
96+
// namespace: spring-k8s, name: secret-a
97+
Map<String, String> secretA = Collections.singletonMap("one",
98+
Base64.getEncoder().encodeToString("a".getBytes(StandardCharsets.UTF_8)));
99+
createSecret("secret-a", secretA);
100+
101+
// namespace: spring-k8s, name: secret-b
102+
Map<String, String> secretB = Collections.singletonMap("two",
103+
Base64.getEncoder().encodeToString("b".getBytes(StandardCharsets.UTF_8)));
104+
createSecret("secret-b", secretB);
105+
106+
// namespace: spring-k8s, name: configmap-a
107+
Map<String, String> configMapA = Collections.singletonMap("one", "a");
108+
createConfigMap("configmap-a", configMapA);
109+
110+
// namespace: spring-k8s, name: configmap-b
111+
Map<String, String> configMapB = Collections.singletonMap("two", "b");
112+
createConfigMap("configmap-b", configMapB);
113+
114+
}
115+
116+
@Test
117+
void test(CapturedOutput output) {
118+
119+
Set<String> sources = environment.getPropertySources()
120+
.stream()
121+
.map(PropertySource::getName)
122+
.collect(Collectors.toSet());
123+
assertThat(sources).contains("bootstrapProperties-configmap.configmap-b.spring-k8s",
124+
"bootstrapProperties-configmap.configmap-a.spring-k8s",
125+
"bootstrapProperties-secret.secret-b.spring-k8s", "bootstrapProperties-secret.secret-a.spring-k8s");
126+
127+
// 1. first, wait for a cycle where we see the configmaps as being the same
128+
Awaitility.await()
129+
.atMost(Duration.ofSeconds(10))
130+
.pollInterval(Duration.ofSeconds(1))
131+
.until(() -> output.getOut()
132+
.contains("Reloadable condition was not satisfied, reload will not be triggered"));
133+
134+
// 2. then change a configmap, so the cycle seems them as different and triggers a
135+
// reload
136+
Map<String, String> configMapA = Collections.singletonMap("one", "aa");
137+
replaceConfigMap("configmap-a", configMapA);
138+
139+
// 3. reload is triggered
140+
Awaitility.await()
141+
.atMost(Duration.ofSeconds(10))
142+
.pollInterval(Duration.ofSeconds(1))
143+
.until(STRATEGY_FOR_SECRET_CALLED::get);
144+
145+
}
146+
147+
private static void createSecret(String name, Map<String, String> data) {
148+
mockClient.secrets()
149+
.inNamespace(NAMESPACE)
150+
.resource(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build())
151+
.create();
152+
}
153+
154+
private static void createConfigMap(String name, Map<String, String> data) {
155+
mockClient.configMaps()
156+
.inNamespace(NAMESPACE)
157+
.resource(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build())
158+
.create();
159+
}
160+
161+
private static void replaceConfigMap(String name, Map<String, String> data) {
162+
mockClient.configMaps()
163+
.inNamespace(NAMESPACE)
164+
.resource(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build())
165+
.createOrReplace();
166+
}
167+
168+
@TestConfiguration
169+
static class TestConfig {
170+
171+
@Bean
172+
@Primary
173+
PollingConfigMapChangeDetector pollingConfigMapChangeDetector(AbstractEnvironment environment,
174+
ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
175+
Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) {
176+
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
177+
scheduler.initialize();
178+
return new PollingConfigMapChangeDetector(environment, configReloadProperties, configurationUpdateStrategy,
179+
Fabric8ConfigMapPropertySource.class, fabric8ConfigMapPropertySourceLocator, scheduler);
180+
}
181+
182+
@Bean
183+
@Primary
184+
ConfigReloadProperties configReloadProperties() {
185+
return new ConfigReloadProperties(true, true, true, ConfigReloadProperties.ReloadStrategy.REFRESH,
186+
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(200), Set.of(NAMESPACE),
187+
false, Duration.ofSeconds(2));
188+
}
189+
190+
@Bean
191+
@Primary
192+
ConfigurationUpdateStrategy secretConfigurationUpdateStrategy() {
193+
return new ConfigurationUpdateStrategy("to-console", () -> STRATEGY_FOR_SECRET_CALLED.set(true));
194+
}
195+
196+
}
197+
198+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
spring:
2+
application:
3+
name: polling-reload-configmap-and-secret
4+
cloud:
5+
kubernetes:
6+
reload:
7+
enabled: true
8+
monitoring-config-maps: true
9+
monitoring-secrets: true
10+
mode: polling
11+
config:
12+
namespace: spring-k8s
13+
sources:
14+
- name: configmap-a
15+
- name: configmap-b
16+
enable-api: true
17+
include-profile-specific-sources: false
18+
secrets:
19+
namespace: spring-k8s
20+
sources:
21+
- name: secret-a
22+
- name: secret-b
23+
enable-api: true
24+
include-profile-specific-sources: false

0 commit comments

Comments
 (0)