> result = new ArrayList<>();
+ assertThatNoException().isThrownBy(() -> {
+ PropertySource> source = locator.locate(new MockEnvironment());
+ result.add(source);
+ });
+
+ assertThat(result.get(0)).isInstanceOf(CompositePropertySource.class);
+ CompositePropertySource composite = (CompositePropertySource) result.get(0);
+ assertThat(composite.getPropertySources()).hasSize(0);
+ assertThat(output.getOut()).contains("Failed to load source:");
}
}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java
index 482e8ba32d..8e550838ba 100644
--- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java
@@ -20,6 +20,7 @@
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.config.ConfigUtils;
@@ -42,6 +43,11 @@ class Fabric8ConfigMapPropertySourceTests {
private static final ConfigUtils.Prefix DEFAULT = ConfigUtils.findPrefix("default", false, false, "irrelevant");
+ @BeforeEach
+ void beforeEach() {
+ mockClient.getConfiguration().setRequestRetryBackoffLimit(1);
+ }
+
@AfterEach
void afterEach() {
new Fabric8ConfigMapsCache().discardAll();
@@ -51,7 +57,7 @@ void afterEach() {
void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
String name = "my-config";
String namespace = "default";
- String path = String.format("/api/v1/namespaces/%s/configmaps", namespace);
+ String path = "/api/v1/namespaces/" + namespace + "/configmaps";
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").always();
NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, true, DEFAULT, true);
@@ -64,11 +70,11 @@ void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
String name = "my-config";
String namespace = "default";
- String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name);
+ String path = "/api/v1/namespaces/" + namespace + "/configmaps";
mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").always();
NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, false, false);
- Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment());
+ Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "default", new MockEnvironment());
assertThatNoException().isThrownBy(() -> new Fabric8ConfigMapPropertySource(context));
}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretErrorOnReadingSourceTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretErrorOnReadingSourceTests.java
new file mode 100644
index 0000000000..00bee4b51b
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretErrorOnReadingSourceTests.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2013-2024 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.fabric8.config;
+
+import java.util.List;
+import java.util.Map;
+
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+import io.fabric8.kubernetes.api.model.SecretListBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
+import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.mock.env.MockEnvironment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties.Source;
+
+/**
+ * @author wind57
+ */
+@EnableKubernetesMockClient
+@ExtendWith(OutputCaptureExtension.class)
+class Fabric8SecretErrorOnReadingSourceTests {
+
+ private static KubernetesMockServer mockServer;
+
+ private static KubernetesClient mockClient;
+
+ @BeforeAll
+ static void beforeAll() {
+ mockClient.getConfiguration().setRequestRetryBackoffLimit(0);
+ }
+
+ /**
+ *
+ * we try to read all secrets in a namespace and fail.
+ *
+ */
+ @Test
+ void namedSingleSecretFails(CapturedOutput output) {
+ String name = "my-secret";
+ String namespace = "spring-k8s";
+ String path = "/api/v1/namespaces/" + namespace + "/secrets";
+
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
+
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true, Map.of(), List.of(),
+ List.of(), true, name, namespace, false, true, false, RetryProperties.DEFAULT);
+
+ Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
+ secretsConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
+
+ CompositePropertySource propertySource = (CompositePropertySource) locator.locate(new MockEnvironment());
+ assertThat(propertySource.getPropertySources()).isEmpty();
+ assertThat(output.getOut()).contains("Failure in reading named sources");
+ assertThat(output.getOut()).contains("Failed to load source: { secret name : 'Optional[my-secret]'");
+
+ }
+
+ /**
+ *
+ * there are two sources and we try to read them.
+ * one fails and one passes.
+ *
+ */
+ @Test
+ void namedTwoSecretsOneFails(CapturedOutput output) {
+ String secretNameOne = "one";
+ String secretNameTwo = "two";
+ String namespace = "default";
+ String path = "/api/v1/namespaces/default/secrets";
+
+ Secret secretTwo = secret(secretNameTwo, Map.of());
+
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
+ mockServer.expect().withPath(path).andReturn(200, new SecretListBuilder().withItems(secretTwo).build()).once();
+
+ Source sourceOne = new Source(secretNameOne, namespace, Map.of(), null, null, null);
+ Source sourceTwo = new Source(secretNameTwo, namespace, Map.of(), null, null, null);
+
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true, Map.of(), List.of(),
+ List.of(sourceOne, sourceTwo), true, null, namespace, false, true, false, RetryProperties.DEFAULT);
+
+ Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
+ secretsConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
+
+ CompositePropertySource propertySource = (CompositePropertySource) locator.locate(new MockEnvironment());
+ List names = propertySource.getPropertySources().stream().map(PropertySource::getName).toList();
+
+ // one property source is present
+ assertThat(names).containsExactly("secret.two.default");
+ assertThat(output.getOut()).contains("Failure in reading named sources");
+ assertThat(output.getOut()).contains("Failed to load source: { secret name : 'Optional[one]'");
+
+ }
+
+ /**
+ *
+ * there are two sources and we try to read them.
+ * both fail.
+ *
+ */
+ @Test
+ void namedTwoSecretsBothFail(CapturedOutput output) {
+ String secretNameOne = "one";
+ String secretNameTwo = "two";
+ String namespace = "default";
+ String path = "/api/v1/namespaces/default/secrets";
+
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
+
+ Source sourceOne = new Source(secretNameOne, namespace, Map.of(), null, null, null);
+ Source sourceTwo = new Source(secretNameTwo, namespace, Map.of(), null, null, null);
+
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true, Map.of(), List.of(),
+ List.of(sourceOne, sourceTwo), true, null, namespace, false, true, false, RetryProperties.DEFAULT);
+
+ Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
+ secretsConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
+
+ CompositePropertySource propertySource = (CompositePropertySource) locator.locate(new MockEnvironment());
+
+ assertThat(propertySource.getPropertySources()).isEmpty();
+ assertThat(output.getOut()).contains("Failed to load source: { secret name : 'Optional[two]'");
+
+ }
+
+ /**
+ *
+ * we try to read all secrets in a namespace and fail,
+ * thus generate a well defined name for the source.
+ *
+ */
+ @Test
+ void labeledSingleSecretFails(CapturedOutput output) {
+ Map labels = Map.of("a", "b");
+ String namespace = "spring-k8s";
+ String path = "/api/v1/namespaces/" + namespace + "/secrets";
+
+ // one for the 'application' named secret
+ // the other for the labeled secret
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").times(2);
+
+ Source secretSource = new Source(null, namespace, labels, null, null, null);
+
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true, labels, List.of(),
+ List.of(secretSource), true, null, namespace, false, true, false, RetryProperties.DEFAULT);
+
+ Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
+ secretsConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
+
+ CompositePropertySource propertySource = (CompositePropertySource) locator.locate(new MockEnvironment());
+
+ assertThat(propertySource.getPropertySources()).isEmpty();
+ assertThat(output.getOut()).contains("Failure in reading labeled sources");
+ assertThat(output.getOut()).contains("Failure in reading named sources");
+ assertThat(output.getOut()).contains("Failed to load source: { secret labels : '{a=b}'");
+ }
+
+ /**
+ *
+ * there are two sources and we try to read them.
+ * one fails and one passes.
+ *
+ */
+ @Test
+ void labeledTwoSecretsOneFails(CapturedOutput output) {
+ String secretNameOne = "one";
+ String secretNameTwo = "two";
+
+ Map secretOneLabels = Map.of("one", "1");
+ Map secretTwoLabels = Map.of("two", "2");
+
+ String namespace = "default";
+ String path = "/api/v1/namespaces/default/secrets";
+
+ Secret secretOne = secret(secretNameOne, secretOneLabels);
+ Secret secretTwo = secret(secretNameTwo, secretTwoLabels);
+
+ // one for 'application' named secret and one for the first labeled secret
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").times(2);
+ mockServer.expect()
+ .withPath(path)
+ .andReturn(200, new SecretListBuilder().withItems(secretOne, secretTwo).build())
+ .once();
+
+ Source sourceOne = new Source(null, namespace, secretOneLabels, null, null, null);
+ Source sourceTwo = new Source(null, namespace, secretTwoLabels, null, null, null);
+
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true,
+ Map.of("one", "1", "two", "2"), List.of(), List.of(sourceOne, sourceTwo), true, null, namespace, false,
+ true, false, RetryProperties.DEFAULT);
+
+ Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
+ secretsConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
+
+ CompositePropertySource propertySource = (CompositePropertySource) locator.locate(new MockEnvironment());
+ List names = propertySource.getPropertySources().stream().map(PropertySource::getName).toList();
+
+ // one property source is present
+ assertThat(names).containsExactly("secret.two.default");
+
+ assertThat(output.getOut()).contains("Failure in reading labeled sources");
+ assertThat(output.getOut()).contains("Failure in reading named sources");
+ assertThat(output.getOut()).contains("Failed to load source: { secret labels : '{one=1}'");
+
+ }
+
+ /**
+ *
+ * there are two sources and we try to read them.
+ * both fail.
+ *
+ */
+ @Test
+ void labeledTwoConfigMapsBothFail(CapturedOutput output) {
+
+ Map secretOneLabels = Map.of("one", "1");
+ Map secretTwoLabels = Map.of("two", "2");
+
+ String namespace = "default";
+ String path = "/api/v1/namespaces/default/secrets";
+
+ // one for 'application' named configmap and two for the labeled configmaps
+ mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").times(3);
+
+ Source sourceOne = new Source(null, namespace, secretOneLabels, null, null, null);
+ Source sourceTwo = new Source(null, namespace, secretTwoLabels, null, null, null);
+
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true,
+ Map.of("one", "1", "two", "2"), List.of(), List.of(sourceOne, sourceTwo), true, null, namespace, false,
+ true, false, RetryProperties.DEFAULT);
+
+ Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
+ secretsConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
+
+ CompositePropertySource propertySource = (CompositePropertySource) locator.locate(new MockEnvironment());
+
+ assertThat(propertySource.getPropertySources()).isEmpty();
+
+ assertThat(output).contains("Failure in reading labeled sources");
+ assertThat(output).contains("Failure in reading named sources");
+ assertThat(output.getOut()).contains("Failed to load source: { secret labels : '{one=1}'");
+ assertThat(output.getOut()).contains("Failed to load source: { secret labels : '{two=2}'");
+
+ }
+
+ private Secret secret(String name, Map labels) {
+ return new SecretBuilder().withNewMetadata().withName(name).withLabels(labels).endMetadata().build();
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java
index 5223490912..e8ac439dc3 100644
--- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java
@@ -16,19 +16,27 @@
package org.springframework.cloud.kubernetes.fabric8.config;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.PropertySource;
import org.springframework.mock.env.MockEnvironment;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -36,11 +44,17 @@
* @author Isik Erhan
*/
@EnableKubernetesMockClient
+@ExtendWith(OutputCaptureExtension.class)
class Fabric8SecretsPropertySourceLocatorTests {
- KubernetesMockServer mockServer;
+ private static KubernetesMockServer mockServer;
- KubernetesClient mockClient;
+ private static KubernetesClient mockClient;
+
+ @BeforeAll
+ static void beforeAll() {
+ mockClient.getConfiguration().setRequestRetryBackoffInterval(1);
+ }
@Test
void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
@@ -61,7 +75,7 @@ void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
}
@Test
- void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
+ void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled(CapturedOutput output) {
String name = "my-secret";
String namespace = "default";
String path = "/api/v1/namespaces/default/secrets/my-secret";
@@ -74,7 +88,17 @@ void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() {
Fabric8SecretsPropertySourceLocator locator = new Fabric8SecretsPropertySourceLocator(mockClient,
configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment()));
- assertThatNoException().isThrownBy(() -> locator.locate(new MockEnvironment()));
+ List> result = new ArrayList<>();
+ assertThatNoException().isThrownBy(() -> {
+ PropertySource> source = locator.locate(new MockEnvironment());
+ result.add(source);
+ });
+
+ assertThat(result.get(0)).isInstanceOf(CompositePropertySource.class);
+ CompositePropertySource composite = (CompositePropertySource) result.get(0);
+ assertThat(composite.getPropertySources()).hasSize(0);
+ assertThat(output.getOut()).contains("Failed to load source:");
+
}
}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java
index c048667484..a0a246eded 100644
--- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java
@@ -22,6 +22,7 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource;
@@ -43,6 +44,11 @@ class Fabric8SecretsPropertySourceMockTests {
private static KubernetesClient client;
+ @BeforeAll
+ static void beforeAll() {
+ client.getConfiguration().setRequestRetryBackoffInterval(1);
+ }
+
@Test
void namedStrategyShouldThrowExceptionOnFailureWhenFailFastIsEnabled() {
final String name = "my-secret";
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/VisibleFabric8ConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/VisibleFabric8ConfigMapPropertySourceLocator.java
new file mode 100644
index 0000000000..7dfdfa566f
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/VisibleFabric8ConfigMapPropertySourceLocator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013-2024 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.fabric8.config;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
+
+/**
+ * Only needed to make Fabric8ConfigMapPropertySourceLocator visible for testing purposes
+ *
+ * @author wind57
+ */
+public class VisibleFabric8ConfigMapPropertySourceLocator extends Fabric8ConfigMapPropertySourceLocator {
+
+ public VisibleFabric8ConfigMapPropertySourceLocator(KubernetesClient client, ConfigMapConfigProperties properties,
+ KubernetesNamespaceProvider provider) {
+ super(client, properties, provider);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/VisibleFabric8SecretsPropertySourceLocator.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/VisibleFabric8SecretsPropertySourceLocator.java
new file mode 100644
index 0000000000..d0278e4d1f
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/VisibleFabric8SecretsPropertySourceLocator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013-2024 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.fabric8.config;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
+
+/**
+ * Only needed to make Fabric8SecretsPropertySourceLocator visible for testing purposes
+ *
+ * @author wind57
+ */
+public class VisibleFabric8SecretsPropertySourceLocator extends Fabric8SecretsPropertySourceLocator {
+
+ public VisibleFabric8SecretsPropertySourceLocator(KubernetesClient client, SecretsConfigProperties properties,
+ KubernetesNamespaceProvider provider) {
+ super(client, properties, provider);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java
new file mode 100644
index 0000000000..0f4c7c3db9
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2012-2024 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.fabric8.config.reload_it;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.api.model.ConfigMapList;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.dsl.MixedOperation;
+import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
+import io.fabric8.kubernetes.client.dsl.Resource;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
+import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
+import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator;
+import org.springframework.cloud.kubernetes.fabric8.config.VisibleFabric8ConfigMapPropertySourceLocator;
+import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8EventBasedConfigMapChangeDetector;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.PropertySource;
+import org.springframework.mock.env.MockEnvironment;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(
+ properties = { "spring.main.allow-bean-definition-overriding=true",
+ "logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
+ classes = { EventReloadConfigMapTest.TestConfig.class })
+@EnableKubernetesMockClient(crud = true)
+@ExtendWith(OutputCaptureExtension.class)
+public class EventReloadConfigMapTest {
+
+ private static final boolean FAIL_FAST = false;
+
+ private static final String CONFIG_MAP_NAME = "mine";
+
+ private static final String NAMESPACE = "spring-k8s";
+
+ private static KubernetesClient kubernetesClient;
+
+ private static final boolean[] strategyCalled = new boolean[] { false };
+
+ @BeforeAll
+ static void beforeAll() {
+
+ kubernetesClient = Mockito.spy(kubernetesClient);
+ kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0);
+
+ ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of());
+
+ // for the informer, when it starts
+ kubernetesClient.configMaps().inNamespace(NAMESPACE).resource(configMapOne).create();
+ }
+
+ @Test
+ @SuppressWarnings({ "unchecked" })
+ void test(CapturedOutput output) {
+
+ // we need to create this one before mocking calls
+ NonNamespaceOperation> operation = kubernetesClient.configMaps()
+ .inNamespace(NAMESPACE);
+
+ // makes sure that when 'onEvent' is triggered (because we added a config map)
+ // the call to /api/v1/namespaces/spring-k8s/configmaps will fail with an
+ // Exception
+ MixedOperation> mixedOperation = Mockito
+ .mock(MixedOperation.class);
+ NonNamespaceOperation> mockedOperation = Mockito
+ .mock(NonNamespaceOperation.class);
+ Mockito.when(kubernetesClient.configMaps()).thenReturn(mixedOperation);
+ Mockito.when(mixedOperation.inNamespace(NAMESPACE)).thenReturn(mockedOperation);
+ Mockito.when(mockedOperation.list()).thenThrow(new RuntimeException("failed in reading configmap"));
+
+ // create a different configmap that triggers even based reloading.
+ // the one we create, will trigger a call to
+ // /api/v1/namespaces/spring-k8s/configmaps
+ // that we mocked above to fail.
+ ConfigMap configMapTwo = configMap("not" + CONFIG_MAP_NAME, Map.of("a", "b"));
+ operation.resource(configMapTwo).create();
+
+ // we fail while reading 'configMapTwo'
+ Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> {
+ boolean one = output.getOut().contains("Failure in reading named sources");
+ boolean two = output.getOut().contains("Failed to load source");
+ boolean three = output.getOut()
+ .contains("Reloadable condition was not satisfied, reload will not be triggered");
+ boolean updateStrategyNotCalled = !strategyCalled[0];
+ return one && two && three && updateStrategyNotCalled;
+ });
+
+ // reset the mock and replace our configmap with some data, so that reload
+ // is triggered
+ Mockito.reset(kubernetesClient);
+ ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of("a", "b"));
+ operation.resource(configMapOne).replace();
+
+ // it passes while reading 'configMapThatWillPass'
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofSeconds(1))
+ .until(() -> strategyCalled[0]);
+ }
+
+ private static ConfigMap configMap(String name, Map data) {
+ return new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().withData(data).build();
+ }
+
+ @TestConfiguration
+ static class TestConfig {
+
+ @Bean
+ @Primary
+ Fabric8EventBasedConfigMapChangeDetector fabric8EventBasedSecretsChangeDetector(AbstractEnvironment environment,
+ ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
+ Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator,
+ KubernetesNamespaceProvider namespaceProvider) {
+ return new Fabric8EventBasedConfigMapChangeDetector(environment, configReloadProperties, kubernetesClient,
+ configurationUpdateStrategy, fabric8ConfigMapPropertySourceLocator, namespaceProvider);
+ }
+
+ @Bean
+ @Primary
+ AbstractEnvironment environment() {
+ MockEnvironment mockEnvironment = new MockEnvironment();
+ mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);
+
+ // simulate that environment already has a Fabric8ConfigMapPropertySource,
+ // otherwise we can't properly test reload functionality
+ ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties(true, List.of(),
+ List.of(), Map.of(), true, CONFIG_MAP_NAME, NAMESPACE, false, true, true, RetryProperties.DEFAULT);
+ KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(mockEnvironment);
+
+ PropertySource> propertySource = new VisibleFabric8ConfigMapPropertySourceLocator(kubernetesClient,
+ configMapConfigProperties, namespaceProvider)
+ .locate(mockEnvironment);
+
+ mockEnvironment.getPropertySources().addFirst(propertySource);
+ return mockEnvironment;
+ }
+
+ @Bean
+ @Primary
+ ConfigReloadProperties configReloadProperties() {
+ return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
+ ConfigReloadProperties.ReloadDetectionMode.EVENT, Duration.ofMillis(2000), Set.of(NAMESPACE), false,
+ Duration.ofSeconds(2));
+ }
+
+ @Bean
+ @Primary
+ ConfigMapConfigProperties configMapConfigProperties() {
+ return new ConfigMapConfigProperties(true, List.of(), List.of(), Map.of(), true, CONFIG_MAP_NAME, NAMESPACE,
+ false, true, FAIL_FAST, RetryProperties.DEFAULT);
+ }
+
+ @Bean
+ @Primary
+ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
+ return new KubernetesNamespaceProvider(environment);
+ }
+
+ @Bean
+ @Primary
+ ConfigurationUpdateStrategy configurationUpdateStrategy() {
+ return new ConfigurationUpdateStrategy("to-console", () -> {
+ strategyCalled[0] = true;
+ });
+ }
+
+ @Bean
+ @Primary
+ Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator(
+ ConfigMapConfigProperties configMapConfigProperties, KubernetesNamespaceProvider namespaceProvider) {
+ return new VisibleFabric8ConfigMapPropertySourceLocator(kubernetesClient, configMapConfigProperties,
+ namespaceProvider);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java
new file mode 100644
index 0000000000..57a699bc5c
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2012-2024 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.fabric8.config.reload_it;
+
+import java.time.Duration;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+import io.fabric8.kubernetes.api.model.SecretList;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.dsl.MixedOperation;
+import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
+import io.fabric8.kubernetes.client.dsl.Resource;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
+import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
+import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator;
+import org.springframework.cloud.kubernetes.fabric8.config.VisibleFabric8SecretsPropertySourceLocator;
+import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8EventBasedSecretsChangeDetector;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.PropertySource;
+import org.springframework.mock.env.MockEnvironment;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(
+ properties = { "spring.main.allow-bean-definition-overriding=true",
+ "logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
+ classes = { EventReloadSecretTest.TestConfig.class })
+@EnableKubernetesMockClient(crud = true)
+@ExtendWith(OutputCaptureExtension.class)
+
+class EventReloadSecretTest {
+
+ private static final boolean FAIL_FAST = false;
+
+ private static final String SECRET_NAME = "mine";
+
+ private static final String NAMESPACE = "spring-k8s";
+
+ private static KubernetesClient kubernetesClient;
+
+ private static final boolean[] strategyCalled = new boolean[] { false };
+
+ @BeforeAll
+ static void beforeAll() {
+
+ kubernetesClient = Mockito.spy(kubernetesClient);
+ kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0);
+
+ Secret secretOne = secret(SECRET_NAME, Map.of());
+
+ // for the informer, when it starts
+ kubernetesClient.secrets().inNamespace(NAMESPACE).resource(secretOne).create();
+ }
+
+ @Test
+ @SuppressWarnings({ "unchecked" })
+ void test(CapturedOutput output) {
+
+ // we need to create this one before mocking calls
+ NonNamespaceOperation> operation = kubernetesClient.secrets()
+ .inNamespace(NAMESPACE);
+
+ // makes sure that when 'onEvent' is triggered (because we added a config map)
+ // the call to /api/v1/namespaces/spring-k8s/secrets will fail with an
+ // Exception
+ MixedOperation> mixedOperation = Mockito.mock(MixedOperation.class);
+ NonNamespaceOperation> mockedOperation = Mockito
+ .mock(NonNamespaceOperation.class);
+ Mockito.when(kubernetesClient.secrets()).thenReturn(mixedOperation);
+ Mockito.when(mixedOperation.inNamespace(NAMESPACE)).thenReturn(mockedOperation);
+ Mockito.when(mockedOperation.list()).thenThrow(new RuntimeException("failed in reading secret"));
+
+ // create a different secret that triggers even based reloading.
+ // the one we create, will trigger a call to
+ // /api/v1/namespaces/spring-k8s/secrets
+ // that we mocked above to fail.
+ Secret secretTwo = secret("not" + SECRET_NAME, Map.of("a", "b"));
+ operation.resource(secretTwo).create();
+
+ // we fail while reading 'secretTwo'
+ Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> {
+ boolean one = output.getOut().contains("Failure in reading named sources");
+ boolean two = output.getOut().contains("Failed to load source");
+ boolean three = output.getOut()
+ .contains("Reloadable condition was not satisfied, reload will not be triggered");
+ boolean updateStrategyNotCalled = !strategyCalled[0];
+ return one && two && three && updateStrategyNotCalled;
+ });
+
+ // reset the mock and replace our secret with some data, so that reload
+ // is triggered
+ Mockito.reset(kubernetesClient);
+ Secret secretOne = secret(SECRET_NAME, Map.of("a", "b"));
+ operation.resource(secretOne).replace();
+
+ // it passes while reading 'secretOne'
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofSeconds(1))
+ .until(() -> strategyCalled[0]);
+ }
+
+ private static Secret secret(String name, Map data) {
+ Map encoded = data.entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ e -> new String(Base64.getEncoder().encode(e.getValue().getBytes()))));
+ return new SecretBuilder().withNewMetadata().withName(name).endMetadata().withData(encoded).build();
+ }
+
+ @TestConfiguration
+ static class TestConfig {
+
+ @Bean
+ @Primary
+ Fabric8EventBasedSecretsChangeDetector fabric8EventBasedSecretsChangeDetector(AbstractEnvironment environment,
+ ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
+ Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator,
+ KubernetesNamespaceProvider namespaceProvider) {
+ return new Fabric8EventBasedSecretsChangeDetector(environment, configReloadProperties, kubernetesClient,
+ configurationUpdateStrategy, fabric8SecretsPropertySourceLocator, namespaceProvider);
+ }
+
+ @Bean
+ @Primary
+ AbstractEnvironment environment() {
+ MockEnvironment mockEnvironment = new MockEnvironment();
+ mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);
+
+ // simulate that environment already has a
+ // Fabric8SecretsPropertySourceLocator,
+ // otherwise we can't properly test reload functionality
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true, Map.of(), List.of(),
+ List.of(), true, SECRET_NAME, NAMESPACE, false, true, true, RetryProperties.DEFAULT);
+ KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(mockEnvironment);
+
+ PropertySource> propertySource = new VisibleFabric8SecretsPropertySourceLocator(kubernetesClient,
+ secretsConfigProperties, namespaceProvider)
+ .locate(mockEnvironment);
+
+ mockEnvironment.getPropertySources().addFirst(propertySource);
+ return mockEnvironment;
+ }
+
+ @Bean
+ @Primary
+ ConfigReloadProperties configReloadProperties() {
+ return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
+ ConfigReloadProperties.ReloadDetectionMode.EVENT, Duration.ofMillis(2000), Set.of(NAMESPACE), false,
+ Duration.ofSeconds(2));
+ }
+
+ @Bean
+ @Primary
+ SecretsConfigProperties secretsConfigProperties() {
+ return new SecretsConfigProperties(true, Map.of(), List.of(), List.of(), true, SECRET_NAME, NAMESPACE,
+ false, true, FAIL_FAST, RetryProperties.DEFAULT);
+ }
+
+ @Bean
+ @Primary
+ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
+ return new KubernetesNamespaceProvider(environment);
+ }
+
+ @Bean
+ @Primary
+ ConfigurationUpdateStrategy configurationUpdateStrategy() {
+ return new ConfigurationUpdateStrategy("to-console", () -> {
+ strategyCalled[0] = true;
+ });
+ }
+
+ @Bean
+ @Primary
+ Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator(
+ SecretsConfigProperties secretsConfigProperties, KubernetesNamespaceProvider namespaceProvider) {
+ return new VisibleFabric8SecretsPropertySourceLocator(kubernetesClient, secretsConfigProperties,
+ namespaceProvider);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java
new file mode 100644
index 0000000000..0f980577f0
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2012-2024 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.fabric8.config.reload_it;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.api.model.ConfigMapListBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
+import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
+import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector;
+import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource;
+import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator;
+import org.springframework.cloud.kubernetes.fabric8.config.VisibleFabric8ConfigMapPropertySourceLocator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.PropertySource;
+import org.springframework.mock.env.MockEnvironment;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(
+ properties = { "spring.main.allow-bean-definition-overriding=true",
+ "logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
+ classes = { PollingReloadConfigMapTest.TestConfig.class })
+@EnableKubernetesMockClient
+@ExtendWith(OutputCaptureExtension.class)
+class PollingReloadConfigMapTest {
+
+ private static final boolean FAIL_FAST = false;
+
+ private static final String CONFIG_MAP_NAME = "mine";
+
+ private static final String NAMESPACE = "spring-k8s";
+
+ private static KubernetesMockServer kubernetesMockServer;
+
+ private static KubernetesClient kubernetesClient;
+
+ private static final boolean[] strategyCalled = new boolean[] { false };
+
+ @BeforeAll
+ static void beforeAll() {
+
+ kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0);
+
+ // needed so that our environment is populated with 'something'
+ // this call is done in the method that returns the AbstractEnvironment
+ ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of());
+ ConfigMap configMapTwo = configMap(CONFIG_MAP_NAME, Map.of("a", "b"));
+ String path = "/api/v1/namespaces/spring-k8s/configmaps";
+ kubernetesMockServer.expect()
+ .withPath(path)
+ .andReturn(200, new ConfigMapListBuilder().withItems(configMapOne).build())
+ .once();
+
+ kubernetesMockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
+
+ kubernetesMockServer.expect()
+ .withPath(path)
+ .andReturn(200, new ConfigMapListBuilder().withItems(configMapTwo).build())
+ .once();
+ }
+
+ /**
+ *
+ * - we have a PropertySource in the environment
+ * - first polling cycle tries to read the sources from k8s and fails
+ * - second polling cycle reads sources from k8s and finds a change
+ *
+ */
+ @Test
+ void test(CapturedOutput output) {
+ // we fail while reading 'configMapOne'
+ Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> {
+ boolean one = output.getOut().contains("Failure in reading named sources");
+ boolean two = output.getOut().contains("Failed to load source");
+ boolean three = output.getOut()
+ .contains("Reloadable condition was not satisfied, reload will not be triggered");
+ boolean updateStrategyNotCalled = !strategyCalled[0];
+ return one && two && three && updateStrategyNotCalled;
+ });
+
+ // it passes while reading 'configMapTwo'
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofSeconds(1))
+ .until(() -> strategyCalled[0]);
+ }
+
+ private static ConfigMap configMap(String name, Map data) {
+ return new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().withData(data).build();
+ }
+
+ @TestConfiguration
+ static class TestConfig {
+
+ @Bean
+ @Primary
+ PollingConfigMapChangeDetector pollingConfigMapChangeDetector(AbstractEnvironment environment,
+ ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
+ Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.initialize();
+ return new PollingConfigMapChangeDetector(environment, configReloadProperties, configurationUpdateStrategy,
+ Fabric8ConfigMapPropertySource.class, fabric8ConfigMapPropertySourceLocator, scheduler);
+ }
+
+ @Bean
+ @Primary
+ AbstractEnvironment environment() {
+ MockEnvironment mockEnvironment = new MockEnvironment();
+ mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);
+
+ // simulate that environment already has a Fabric8ConfigMapPropertySource,
+ // otherwise we can't properly test reload functionality
+ ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties(true, List.of(),
+ List.of(), Map.of(), true, CONFIG_MAP_NAME, NAMESPACE, false, true, true, RetryProperties.DEFAULT);
+ KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(mockEnvironment);
+
+ PropertySource> propertySource = new VisibleFabric8ConfigMapPropertySourceLocator(kubernetesClient,
+ configMapConfigProperties, namespaceProvider)
+ .locate(mockEnvironment);
+
+ mockEnvironment.getPropertySources().addFirst(propertySource);
+ return mockEnvironment;
+ }
+
+ @Bean
+ @Primary
+ ConfigReloadProperties configReloadProperties() {
+ return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
+ ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("non-default"),
+ false, Duration.ofSeconds(2));
+ }
+
+ @Bean
+ @Primary
+ ConfigMapConfigProperties configMapConfigProperties() {
+ return new ConfigMapConfigProperties(true, List.of(), List.of(), Map.of(), true, CONFIG_MAP_NAME, NAMESPACE,
+ false, true, FAIL_FAST, RetryProperties.DEFAULT);
+ }
+
+ @Bean
+ @Primary
+ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
+ return new KubernetesNamespaceProvider(environment);
+ }
+
+ @Bean
+ @Primary
+ ConfigurationUpdateStrategy configurationUpdateStrategy() {
+ return new ConfigurationUpdateStrategy("to-console", () -> {
+ strategyCalled[0] = true;
+ });
+ }
+
+ @Bean
+ @Primary
+ Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator(
+ ConfigMapConfigProperties configMapConfigProperties, KubernetesNamespaceProvider namespaceProvider) {
+ return new VisibleFabric8ConfigMapPropertySourceLocator(kubernetesClient, configMapConfigProperties,
+ namespaceProvider);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java
new file mode 100644
index 0000000000..4c100084ee
--- /dev/null
+++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2012-2024 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.fabric8.config.reload_it;
+
+import java.time.Duration;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+import io.fabric8.kubernetes.api.model.SecretListBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
+import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
+import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
+import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
+import org.springframework.cloud.kubernetes.commons.config.reload.PollingSecretsChangeDetector;
+import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource;
+import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator;
+import org.springframework.cloud.kubernetes.fabric8.config.VisibleFabric8SecretsPropertySourceLocator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.PropertySource;
+import org.springframework.mock.env.MockEnvironment;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(
+ properties = { "spring.main.allow-bean-definition-overriding=true",
+ "logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
+ classes = { PollingReloadSecretTest.TestConfig.class })
+@EnableKubernetesMockClient
+@ExtendWith(OutputCaptureExtension.class)
+
+public class PollingReloadSecretTest {
+
+ private static final boolean FAIL_FAST = false;
+
+ private static final String SECRET_NAME = "mine";
+
+ private static final String NAMESPACE = "spring-k8s";
+
+ private static KubernetesMockServer kubernetesMockServer;
+
+ private static KubernetesClient kubernetesClient;
+
+ private static final boolean[] strategyCalled = new boolean[] { false };
+
+ @BeforeAll
+ static void beforeAll() {
+
+ kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0);
+
+ // needed so that our environment is populated with 'something'
+ // this call is done in the method that returns the AbstractEnvironment
+ Secret secretOne = secret(SECRET_NAME, Map.of());
+ Secret secretTwo = secret(SECRET_NAME, Map.of("a", "b"));
+ String path = "/api/v1/namespaces/spring-k8s/secrets";
+ kubernetesMockServer.expect()
+ .withPath(path)
+ .andReturn(200, new SecretListBuilder().withItems(secretOne).build())
+ .once();
+
+ kubernetesMockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once();
+
+ kubernetesMockServer.expect()
+ .withPath(path)
+ .andReturn(200, new SecretListBuilder().withItems(secretTwo).build())
+ .once();
+ }
+
+ /**
+ *
+ * - we have a PropertySource in the environment
+ * - first polling cycle tries to read the sources from k8s and fails
+ * - second polling cycle reads sources from k8s and finds a change
+ *
+ */
+ @Test
+ void test(CapturedOutput output) {
+ // we fail while reading 'secretOne'
+ Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> {
+ boolean one = output.getOut().contains("Failure in reading named sources");
+ boolean two = output.getOut().contains("Failed to load source");
+ boolean three = output.getOut()
+ .contains("Reloadable condition was not satisfied, reload will not be triggered");
+ boolean updateStrategyNotCalled = !strategyCalled[0];
+ return one && two && three && updateStrategyNotCalled;
+ });
+
+ // it passes while reading 'secretTwo'
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofSeconds(1))
+ .until(() -> strategyCalled[0]);
+ }
+
+ private static Secret secret(String name, Map data) {
+ Map encoded = data.entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ e -> new String(Base64.getEncoder().encode(e.getValue().getBytes()))));
+ return new SecretBuilder().withNewMetadata().withName(name).endMetadata().withData(encoded).build();
+ }
+
+ @TestConfiguration
+ static class TestConfig {
+
+ @Bean
+ @Primary
+ PollingSecretsChangeDetector pollingSecretsChangeDetector(AbstractEnvironment environment,
+ ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
+ Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator) {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.initialize();
+ return new PollingSecretsChangeDetector(environment, configReloadProperties, configurationUpdateStrategy,
+ Fabric8SecretsPropertySource.class, fabric8SecretsPropertySourceLocator, scheduler);
+ }
+
+ @Bean
+ @Primary
+ AbstractEnvironment environment() {
+ MockEnvironment mockEnvironment = new MockEnvironment();
+ mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);
+
+ // simulate that environment already has a Fabric8SecretsPropertySource,
+ // otherwise we can't properly test reload functionality
+ SecretsConfigProperties secretsConfigProperties = new SecretsConfigProperties(true, Map.of(), List.of(),
+ List.of(), true, SECRET_NAME, NAMESPACE, false, true, true, RetryProperties.DEFAULT);
+ KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(mockEnvironment);
+
+ PropertySource> propertySource = new VisibleFabric8SecretsPropertySourceLocator(kubernetesClient,
+ secretsConfigProperties, namespaceProvider)
+ .locate(mockEnvironment);
+
+ mockEnvironment.getPropertySources().addFirst(propertySource);
+ return mockEnvironment;
+ }
+
+ @Bean
+ @Primary
+ ConfigReloadProperties configReloadProperties() {
+ return new ConfigReloadProperties(true, true, true, ConfigReloadProperties.ReloadStrategy.REFRESH,
+ ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("non-default"),
+ false, Duration.ofSeconds(2));
+ }
+
+ @Bean
+ @Primary
+ SecretsConfigProperties secretsConfigProperties() {
+ return new SecretsConfigProperties(true, Map.of(), List.of(), List.of(), true, SECRET_NAME, NAMESPACE,
+ false, true, FAIL_FAST, RetryProperties.DEFAULT);
+ }
+
+ @Bean
+ @Primary
+ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
+ return new KubernetesNamespaceProvider(environment);
+ }
+
+ @Bean
+ @Primary
+ ConfigurationUpdateStrategy configurationUpdateStrategy() {
+ return new ConfigurationUpdateStrategy("to-console", () -> {
+ strategyCalled[0] = true;
+ });
+ }
+
+ @Bean
+ @Primary
+ Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator(
+ SecretsConfigProperties secretsConfigProperties, KubernetesNamespaceProvider namespaceProvider) {
+ return new VisibleFabric8SecretsPropertySourceLocator(kubernetesClient, secretsConfigProperties,
+ namespaceProvider);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-fabric8-discovery/pom.xml b/spring-cloud-kubernetes-fabric8-discovery/pom.xml
index 73b4241991..5b527b30d9 100644
--- a/spring-cloud-kubernetes-fabric8-discovery/pom.xml
+++ b/spring-cloud-kubernetes-fabric8-discovery/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-fabric8-istio/pom.xml b/spring-cloud-kubernetes-fabric8-istio/pom.xml
index ddb29e7aca..2abde636e6 100644
--- a/spring-cloud-kubernetes-fabric8-istio/pom.xml
+++ b/spring-cloud-kubernetes-fabric8-istio/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-fabric8-leader/pom.xml b/spring-cloud-kubernetes-fabric8-leader/pom.xml
index 084f8efa4b..79e9211bc9 100644
--- a/spring-cloud-kubernetes-fabric8-leader/pom.xml
+++ b/spring-cloud-kubernetes-fabric8-leader/pom.xml
@@ -22,7 +22,7 @@
org.springframework.cloud
spring-cloud-kubernetes
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
spring-cloud-kubernetes-fabric8-leader
diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/pom.xml b/spring-cloud-kubernetes-fabric8-loadbalancer/pom.xml
index 39d7feb6d3..04cefbf442 100644
--- a/spring-cloud-kubernetes-fabric8-loadbalancer/pom.xml
+++ b/spring-cloud-kubernetes-fabric8-loadbalancer/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml
index c17152c451..48eca0ba29 100644
--- a/spring-cloud-kubernetes-integration-tests/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/pom.xml
@@ -6,7 +6,7 @@
org.springframework.cloud
spring-cloud-kubernetes
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
spring-cloud-kubernetes-integration-tests
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml
index 33be033dd6..943b54ab1d 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-integration-tests
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java
index a3062c0981..fba6fd0712 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java
@@ -25,7 +25,7 @@
*/
@SpringBootApplication
@EnableScheduling
-public class Application {
+class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java
index c4a3e86d43..38b12ad248 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java
@@ -28,7 +28,7 @@
* @author wind57
*/
@Service
-public class EndpointNameAndNamespaceService {
+class EndpointNameAndNamespaceService {
private List result;
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java
index 926c7b7f44..6ea65aae4f 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java
@@ -31,11 +31,11 @@
* @author wind57
*/
@Component
-public class HeartBeatListener implements ApplicationListener {
+class HeartBeatListener implements ApplicationListener {
private final EndpointNameAndNamespaceService service;
- public HeartBeatListener(EndpointNameAndNamespaceService service) {
+ HeartBeatListener(EndpointNameAndNamespaceService service) {
this.service = service;
}
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java
index f2889faacc..75870a17cf 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java
@@ -23,16 +23,16 @@
import org.springframework.web.bind.annotation.RestController;
@RestController
-public class HeartbeatController {
+class HeartbeatController {
private final EndpointNameAndNamespaceService service;
- public HeartbeatController(EndpointNameAndNamespaceService service) {
+ HeartbeatController(EndpointNameAndNamespaceService service) {
this.service = service;
}
@GetMapping("/result")
- public List result() {
+ List result() {
return service.result();
}
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml
index b3f1ff57e6..ed7a0a63de 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes-integration-tests
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/pom.xml
index 7cb749a2f5..7f6b6739f4 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes-integration-tests
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/pom.xml
index a2f04ddabb..0817e6dc29 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes-integration-tests
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-catalog-watcher/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-catalog-watcher/pom.xml
index 1f5896c84c..a88f0cae08 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-catalog-watcher/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-catalog-watcher/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-integration-tests
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml
index ed2ac36246..1db81335ee 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes-integration-tests
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/pom.xml
index 81fbd0b0c7..5a060e027f 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-integration-tests
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery/pom.xml
index cdf6aacb3d..1d50ce8076 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-integration-tests
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-a/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-a/pom.xml
index e689f2f971..1f4a6931d7 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-a/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-a/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
jar
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-b/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-b/pom.xml
index 4efc9f1d66..9bdc2d3fb8 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-b/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-app-b/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
../../spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-test-app/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-test-app/pom.xml
index 78df763d41..b2166c1eb4 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-test-app/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/kafka-configmap-test-app/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
jar
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/pom.xml
index 450171e4f3..0f08bbeb6b 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-integration-tests
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/pom.xml
index 47832fd7a0..33ecf23929 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-integration-tests
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-a/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-a/pom.xml
index 6e9b6299b0..30e83e17fb 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-a/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-a/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
jar
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-b/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-b/pom.xml
index c2d260596c..d059aa5b02 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-b/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-app-b/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
jar
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-test-app/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-test-app/pom.xml
index f54882a5cc..c28c9aff86 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-test-app/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps/rabbitmq-secret-test-app/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes-k8s-client-rabbitmq-secret-reload-multiple-apps
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
jar
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/pom.xml
index b8e3037c3d..582020397e 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/pom.xml
@@ -6,7 +6,7 @@
org.springframework.cloud
spring-cloud-kubernetes-integration-tests
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
spring-cloud-kubernetes-k8s-client-reload
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-secret.yaml
index fb84489801..2208fa1ad3 100644
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-secret.yaml
+++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-secret.yaml
@@ -1,6 +1,9 @@
logging:
level:
- root: DEBUG
+ org:
+ springframework:
+ cloud:
+ kubernetes: debug
spring:
application:
@@ -18,3 +21,6 @@ spring:
secrets:
enabled: true
enable-api: true
+
+ config:
+ enabled: false
diff --git a/spring-cloud-kubernetes-test-support/pom.xml b/spring-cloud-kubernetes-test-support/pom.xml
index 355b479811..00b4c0ae4f 100644
--- a/spring-cloud-kubernetes-test-support/pom.xml
+++ b/spring-cloud-kubernetes-test-support/pom.xml
@@ -5,7 +5,7 @@
org.springframework.cloud
spring-cloud-kubernetes
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java
index 9d279aee3b..c6221e855f 100644
--- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java
+++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java
@@ -276,6 +276,11 @@ private static void loadImageFromPath(String tarName, K3sContainer container) {
}
private static boolean imageAlreadyInK3s(K3sContainer container, String tarName) {
+
+ if (tarName == null) {
+ return false;
+ }
+
try {
boolean present = container.execInContainer("sh", "-c", "ctr images list | grep " + tarName)
.getStdout()
diff --git a/spring-cloud-starter-kubernetes-client-all/pom.xml b/spring-cloud-starter-kubernetes-client-all/pom.xml
index b9fed3e242..a6c0aad5f1 100644
--- a/spring-cloud-starter-kubernetes-client-all/pom.xml
+++ b/spring-cloud-starter-kubernetes-client-all/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-client-config/pom.xml b/spring-cloud-starter-kubernetes-client-config/pom.xml
index 74133557dd..db509c42f1 100644
--- a/spring-cloud-starter-kubernetes-client-config/pom.xml
+++ b/spring-cloud-starter-kubernetes-client-config/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-client-loadbalancer/pom.xml b/spring-cloud-starter-kubernetes-client-loadbalancer/pom.xml
index 706a0a45e9..5da98b7b44 100644
--- a/spring-cloud-starter-kubernetes-client-loadbalancer/pom.xml
+++ b/spring-cloud-starter-kubernetes-client-loadbalancer/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-client/pom.xml b/spring-cloud-starter-kubernetes-client/pom.xml
index 5e35220f40..3981c1d364 100644
--- a/spring-cloud-starter-kubernetes-client/pom.xml
+++ b/spring-cloud-starter-kubernetes-client/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-discoveryclient/pom.xml b/spring-cloud-starter-kubernetes-discoveryclient/pom.xml
index 1231553442..555900f83d 100644
--- a/spring-cloud-starter-kubernetes-discoveryclient/pom.xml
+++ b/spring-cloud-starter-kubernetes-discoveryclient/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-fabric8-all/pom.xml b/spring-cloud-starter-kubernetes-fabric8-all/pom.xml
index cbf16c33f9..731bc7e7b0 100644
--- a/spring-cloud-starter-kubernetes-fabric8-all/pom.xml
+++ b/spring-cloud-starter-kubernetes-fabric8-all/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-fabric8-config/pom.xml b/spring-cloud-starter-kubernetes-fabric8-config/pom.xml
index 5be3dff932..b7a44172e4 100644
--- a/spring-cloud-starter-kubernetes-fabric8-config/pom.xml
+++ b/spring-cloud-starter-kubernetes-fabric8-config/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-fabric8-loadbalancer/pom.xml b/spring-cloud-starter-kubernetes-fabric8-loadbalancer/pom.xml
index fadfd3ba86..5909797203 100644
--- a/spring-cloud-starter-kubernetes-fabric8-loadbalancer/pom.xml
+++ b/spring-cloud-starter-kubernetes-fabric8-loadbalancer/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0
diff --git a/spring-cloud-starter-kubernetes-fabric8/pom.xml b/spring-cloud-starter-kubernetes-fabric8/pom.xml
index 11c140547c..945496dc2e 100644
--- a/spring-cloud-starter-kubernetes-fabric8/pom.xml
+++ b/spring-cloud-starter-kubernetes-fabric8/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-kubernetes
org.springframework.cloud
- 3.1.4-SNAPSHOT
+ 3.1.5-SNAPSHOT
4.0.0