Merge branch '3.2.x'
This commit is contained in:
@@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
@@ -110,6 +111,10 @@ public final class ConfigReloadUtil {
|
||||
// we know that the type is correct here
|
||||
managedSources.add((S) mountConfigMapPropertySource);
|
||||
}
|
||||
else if (source instanceof SecretsPropertySource secretsPropertySource) {
|
||||
// we know that the type is correct here
|
||||
managedSources.add((S) secretsPropertySource);
|
||||
}
|
||||
else if (source instanceof BootstrapPropertySource<?> bootstrapPropertySource) {
|
||||
PropertySource<?> propertySource = bootstrapPropertySource.getDelegate();
|
||||
LOG.debug(() -> "bootstrap delegate class : " + propertySource.getClass());
|
||||
@@ -120,6 +125,10 @@ public final class ConfigReloadUtil {
|
||||
// we know that the type is correct here
|
||||
managedSources.add((S) mountConfigMapPropertySource);
|
||||
}
|
||||
else if (propertySource instanceof SecretsPropertySource secretsPropertySource) {
|
||||
// we know that the type is correct here
|
||||
managedSources.add((S) secretsPropertySource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.SourceData;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
@@ -127,8 +129,8 @@ class ConfigReloadUtilTests {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
propertySources.addFirst(new OneComposite());
|
||||
propertySources.addFirst(new PlainPropertySource("plain"));
|
||||
propertySources.addFirst(new OneBootstrap(new EnumerablePropertySource<>("enumerable") {
|
||||
propertySources.addFirst(new PlainPropertySource<>("plain"));
|
||||
propertySources.addFirst(new OneBootstrap<>(new EnumerablePropertySource<>("enumerable") {
|
||||
@Override
|
||||
public String[] getPropertyNames() {
|
||||
return new String[0];
|
||||
@@ -143,11 +145,35 @@ class ConfigReloadUtilTests {
|
||||
|
||||
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
|
||||
environment);
|
||||
Assertions.assertEquals(4, result.size());
|
||||
Assertions.assertEquals(3, result.size());
|
||||
Assertions.assertEquals("b", result.get(0).getProperty("a"));
|
||||
Assertions.assertEquals("plain", result.get(1).getProperty(""));
|
||||
Assertions.assertEquals("from-bootstrap", result.get(2).getProperty(""));
|
||||
Assertions.assertEquals("from-inner-two-composite", result.get(3).getProperty(""));
|
||||
Assertions.assertEquals("from-inner-two-composite", result.get(2).getProperty(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSecretsPropertySource() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
propertySources.addFirst(new SecretsPropertySource(new SourceData("secret", Map.of("a", "b"))));
|
||||
|
||||
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
|
||||
environment);
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
assertThat(result.get(0).getProperty("a")).isEqualTo("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBootstrapSecretsPropertySource() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
propertySources
|
||||
.addFirst(new OneBootstrap<>(new SecretsPropertySource(new SourceData("secret", Map.of("a", "b")))));
|
||||
|
||||
List<? extends PropertySource> result = ConfigReloadUtil.findPropertySources(PlainPropertySource.class,
|
||||
environment);
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
assertThat(result.get(0).getProperty("a")).isEqualTo("b");
|
||||
}
|
||||
|
||||
private static final class OneComposite extends CompositePropertySource {
|
||||
@@ -171,12 +197,12 @@ class ConfigReloadUtilTests {
|
||||
|
||||
@Override
|
||||
public Collection<PropertySource<?>> getPropertySources() {
|
||||
return List.of(new PlainPropertySource("from-inner-two-composite"));
|
||||
return List.of(new PlainPropertySource<>("from-inner-two-composite"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class PlainPropertySource extends PropertySource<String> {
|
||||
private static final class PlainPropertySource<T> extends PropertySource<T> {
|
||||
|
||||
private PlainPropertySource(String name) {
|
||||
super(name);
|
||||
@@ -189,15 +215,18 @@ class ConfigReloadUtilTests {
|
||||
|
||||
}
|
||||
|
||||
private static final class OneBootstrap extends BootstrapPropertySource<String> {
|
||||
private static final class OneBootstrap<T> extends BootstrapPropertySource<T> {
|
||||
|
||||
private OneBootstrap(EnumerablePropertySource<String> delegate) {
|
||||
private final EnumerablePropertySource<T> delegate;
|
||||
|
||||
private OneBootstrap(EnumerablePropertySource<T> delegate) {
|
||||
super(delegate);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertySource<String> getDelegate() {
|
||||
return new PlainPropertySource("from-bootstrap");
|
||||
public PropertySource<T> getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.kubernetes.k8s.client.reload.it;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Secret;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.builder;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.retrySpec;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class K8sClientSecretMountBootstrapPollingIT extends K8sClientReloadBase {
|
||||
|
||||
private static final String IMAGE_NAME = "spring-cloud-kubernetes-k8s-client-reload";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
private static Util util;
|
||||
|
||||
private static CoreV1Api coreV1Api;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAllLocal() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(IMAGE_NAME, K3S);
|
||||
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
|
||||
|
||||
util = new Util(K3S);
|
||||
coreV1Api = new CoreV1Api();
|
||||
util.setUp(NAMESPACE);
|
||||
manifestsSecret(Phase.CREATE, util, NAMESPACE, IMAGE_NAME);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
manifestsSecret(Phase.DELETE, util, NAMESPACE, IMAGE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - we have bootstrap enabled
|
||||
* - we will 'locate' property sources from secrets.
|
||||
* - there are no explicit secrets to search for, but what we will also read,
|
||||
* is 'spring.cloud.kubernetes.secret.paths', which we have set to
|
||||
* '/tmp/application.properties'
|
||||
* in this test. That is populated by the volumeMounts (see mount/deployment-with-secret.yaml)
|
||||
* - we first assert that we are actually reading the path based source
|
||||
*
|
||||
* - we then change the secret content, wait for k8s to pick it up and replace them
|
||||
* - our polling will then detect that change, and trigger a reload.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
WebClient webClient = builder().baseUrl("http://localhost:32321/secret").build();
|
||||
String result = webClient.method(HttpMethod.GET)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
// we first read the initial value from the configmap
|
||||
assertThat(result).isEqualTo("initial");
|
||||
|
||||
// replace data in secret and wait for k8s to pick it up
|
||||
// our polling will detect that and restart the app
|
||||
V1Secret secret = (V1Secret) util.yaml("mount/secret.yaml");
|
||||
secret.setData(Map.of("from.properties.secret.key", "as-mount-changed".getBytes(StandardCharsets.UTF_8)));
|
||||
coreV1Api.replaceNamespacedSecret("secret-reload", NAMESPACE, secret, null, null, null, null);
|
||||
|
||||
Commons.waitForLogStatement("Detected change in config maps/secrets, reload will be triggered", K3S,
|
||||
IMAGE_NAME);
|
||||
|
||||
await().atMost(Duration.ofSeconds(120))
|
||||
.pollInterval(Duration.ofSeconds(1))
|
||||
.until(() -> webClient.method(HttpMethod.GET)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.retryWhen(retrySpec())
|
||||
.block()
|
||||
.equals("as-mount-changed"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user