diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/ConditionalOnKubernetesConfigMapsOrSecretsEnabled.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/ConditionalOnKubernetesConfigMapsOrSecretsEnabled.java new file mode 100644 index 00000000..ac868176 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/ConditionalOnKubernetesConfigMapsOrSecretsEnabled.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2023 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.commons; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional @Conditional} that matches when either or both of + * {@link ConditionalOnKubernetesConfigEnabled @ConditionalOnKubernetesConfigEnabled} and + * {@link ConditionalOnKubernetesSecretsEnabled @ConditionalOnKubernetesSecretsEnabled}. + * + * @author wind57 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Conditional(ConditionalOnKubernetesConfigMapsOrSecretsEnabled.OnKubernetesConfigMapsOrSecretesEnabled.class) +public @interface ConditionalOnKubernetesConfigMapsOrSecretsEnabled { + + class OnKubernetesConfigMapsOrSecretesEnabled extends AnyNestedCondition { + + OnKubernetesConfigMapsOrSecretesEnabled() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnKubernetesConfigEnabled + static class OnConfigMapPropertiesRetryEnabled { + + } + + @ConditionalOnKubernetesSecretsEnabled + static class OnSecretsPropertiesRetryEnabled { + + } + + } + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadAutoConfiguration.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadAutoConfiguration.java index a0795247..d58ea7ab 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadAutoConfiguration.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadAutoConfiguration.java @@ -23,13 +23,15 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfi import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.cloud.CloudPlatform; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration; import org.springframework.cloud.commons.util.TaskSchedulerWrapper; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.restart.RestartEndpoint; -import org.springframework.cloud.kubernetes.commons.config.ConditionalOnKubernetesAndConfigEnabled; +import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesConfigMapsOrSecretsEnabled; import org.springframework.cloud.kubernetes.commons.config.reload.condition.ConditionalOnKubernetesReloadEnabled; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; @@ -41,7 +43,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; * @author Ryan Baxter */ @Configuration(proxyBeanMethods = false) -@ConditionalOnKubernetesAndConfigEnabled +@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +@ConditionalOnKubernetesConfigMapsOrSecretsEnabled @ConditionalOnKubernetesReloadEnabled @ConditionalOnClass({ EndpointAutoConfiguration.class, RestartEndpoint.class, ContextRefresher.class }) @AutoConfigureAfter({ InfoEndpointAutoConfiguration.class, RefreshEndpointAutoConfiguration.class, diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/reload/ConfigReloadAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/reload/ConfigReloadAutoConfigurationApplicationContextTests.java new file mode 100644 index 00000000..471911b8 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/reload/ConfigReloadAutoConfigurationApplicationContextTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2013-2023 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.commons.reload; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration; +import org.springframework.cloud.commons.util.TaskSchedulerWrapper; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadAutoConfiguration; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadPropertiesAutoConfiguration; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; +import org.springframework.context.annotation.Bean; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author wind57 + */ +class ConfigReloadAutoConfigurationApplicationContextTests { + + private ApplicationContextRunner applicationContextRunner; + + /** + * no special properties provided. + */ + @Test + void testDefault() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class)); + } + + /** + * reload is disabled. + */ + @Test + void testReloadDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=false"); + applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(ConfigurationUpdateStrategy.class)); + } + + /** + * config maps support is enabled. + */ + @Test + void testConfigMapsSupportEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true", + "spring.cloud.kubernetes.config.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class)); + } + + /** + * secrets support is enabled. + */ + @Test + void testSecretsSupportEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true", + "spring.cloud.kubernetes.secrets.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class)); + } + + /** + * config maps and secrets support is enabled. + */ + @Test + void testConfigMapsAndSecretsSupportEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true", + "spring.cloud.kubernetes.config.enabled=true", "spring.cloud.kubernetes.secrets.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class)); + } + + /** + * config maps and secrets support is disabled. + */ + @Test + void testConfigMapsAndSecretsSupportDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true", + "spring.cloud.kubernetes.config.enabled=false", "spring.cloud.kubernetes.secrets.enabled=false"); + applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(ConfigurationUpdateStrategy.class)); + } + + /** + * config maps support disabled and secrets support is enabled. + */ + @Test + void testConfigMapsDisabledAndSecretsSupportEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true", + "spring.cloud.kubernetes.config.enabled=false", "spring.cloud.kubernetes.secrets.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class)); + } + + /** + * config maps support enabled and secrets support is disable. + */ + @Test + void testConfigMapsEnabledAndSecretsSupportDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true", + "spring.cloud.kubernetes.config.enabled=true", "spring.cloud.kubernetes.secrets.enabled=false"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class)); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class)); + } + + private void setup(String... properties) { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(InfoEndpointAutoConfiguration.class, + RefreshEndpointAutoConfiguration.class, ConfigReloadPropertiesAutoConfiguration.class, + RefreshAutoConfiguration.class, ConfigReloadAutoConfiguration.class)) + .withUserConfiguration(RebinderConfig.class).withPropertyValues(properties); + } + + @TestConfiguration + static class RebinderConfig { + + @Bean + ConfigurationPropertiesRebinder rebinder() { + return Mockito.mock(ConfigurationPropertiesRebinder.class); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/configmap/event/reload/ConfigMapEventReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/configmap/event/reload/ConfigMapEventReloadIT.java index 50673a1d..b2014d7e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/configmap/event/reload/ConfigMapEventReloadIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/configmap/event/reload/ConfigMapEventReloadIT.java @@ -17,8 +17,11 @@ package org.springframework.cloud.kubernetes.client.configmap.event.reload; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -28,6 +31,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1EnvVar; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.openapi.models.V1Service; @@ -94,7 +98,7 @@ class ConfigMapEventReloadIT { */ @Test void testInformFromOneNamespaceEventNotTriggered() throws Exception { - manifests("one", Phase.CREATE); + manifests("one", Phase.CREATE, false); Commons.assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", IMAGE_NAME); @@ -125,7 +129,7 @@ class ConfigMapEventReloadIT { // left configmap has not changed, no restart of app has happened Assertions.assertEquals("left-initial", result); - manifests("one", Phase.DELETE); + manifests("one", Phase.DELETE, false); } /** @@ -138,7 +142,7 @@ class ConfigMapEventReloadIT { */ @Test void testInformFromOneNamespaceEventTriggered() throws Exception { - manifests("two", Phase.CREATE); + manifests("two", Phase.CREATE, false); Commons.assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", IMAGE_NAME); @@ -166,7 +170,7 @@ class ConfigMapEventReloadIT { }); Assertions.assertEquals("right-after-change", resultAfterChange[0]); - manifests("two", Phase.DELETE); + manifests("two", Phase.DELETE, false); } /** @@ -180,7 +184,7 @@ class ConfigMapEventReloadIT { */ @Test void testInform() throws Exception { - manifests("three", Phase.CREATE); + manifests("three", Phase.CREATE, false); Commons.assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", IMAGE_NAME); @@ -236,10 +240,51 @@ class ConfigMapEventReloadIT { .block(); Assertions.assertEquals("right-after-change", rightResult); - manifests("three", Phase.DELETE); + manifests("three", Phase.DELETE, false); } - private static void manifests(String deploymentRoot, Phase phase) { + /** + *
+	 *     - there are two namespaces : left and right
+	 *     - each of the namespaces has one configmap
+	 *     - we watch the "right" namespace and make a change in the configmap in the same namespace
+	 *     - as such, event is triggered and we see the updated value
+	 * 
+ */ + @Test + void testInformFromOneNamespaceEventTriggeredSecretsDisabled() throws Exception { + manifests("two", Phase.CREATE, true); + Commons.assertReloadLogStatements("added configmap informer for namespace", + "added secret informer for namespace", IMAGE_NAME); + + // read the value from the right-configmap + WebClient webClient = builder().baseUrl("http://localhost/right").build(); + String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + Assertions.assertEquals("right-initial", result); + + // then deploy a new version of right-configmap + V1ConfigMap rightConfigMapAfterChange = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMeta().namespace("right").name("right-configmap")) + .withData(Map.of("right.value", "right-after-change")).build(); + + replaceConfigMap(rightConfigMapAfterChange, "right-configmap"); + + String[] resultAfterChange = new String[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { + WebClient innerWebClient = builder().baseUrl("http://localhost/right").build(); + String innerResult = innerWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + + resultAfterChange[0] = innerResult; + return innerResult != null; + }); + Assertions.assertEquals("right-after-change", resultAfterChange[0]); + + manifests("two", Phase.DELETE, true); + } + + private static void manifests(String deploymentRoot, Phase phase, boolean secretsDisabled) { try { @@ -251,6 +296,16 @@ class ConfigMapEventReloadIT { V1Service service = (V1Service) util.yaml("service.yaml"); V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml"); + List envVars = new ArrayList<>( + Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()) + .orElse(List.of())); + + if (secretsDisabled) { + V1EnvVar secretsDisabledEnvVar = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED").value("FALSE"); + envVars.add(secretsDisabledEnvVar); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); + } + if (phase.equals(Phase.CREATE)) { util.createAndWait("left", leftConfigMap, null); util.createAndWait("right", rightConfigMap, null); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java index 6bc94bc8..083472d8 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java @@ -17,16 +17,19 @@ package org.springframework.cloud.kubernetes.client.secrets.event.reload; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1EnvVar; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1Service; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.testcontainers.k3s.K3sContainer; @@ -75,17 +78,22 @@ class SecretsEventReloadIT { Commons.cleanUp(IMAGE_NAME, K3S); } - @AfterEach - void after() { - configK8sClientIt(Phase.DELETE); - } - @Test void testSecretReload() throws Exception { - configK8sClientIt(Phase.CREATE); + configK8sClientIt(Phase.CREATE, false); Commons.assertReloadLogStatements("added secret informer for namespace", "added configmap informer for namespace", IMAGE_NAME); testSecretEventReload(); + configK8sClientIt(Phase.DELETE, false); + } + + @Test + void testSecretReloadConfigDisabled() throws Exception { + configK8sClientIt(Phase.CREATE, true); + Commons.assertReloadLogStatements("added secret informer for namespace", + "added configmap informer for namespace", IMAGE_NAME); + testSecretEventReload(); + configK8sClientIt(Phase.DELETE, true); } void testSecretEventReload() throws Exception { @@ -108,12 +116,22 @@ class SecretsEventReloadIT { .retryWhen(retrySpec()).block().equals("after-change")); } - private void configK8sClientIt(Phase phase) { + private void configK8sClientIt(Phase phase, boolean configDisabled) { V1Deployment deployment = (V1Deployment) util.yaml("deployment.yaml"); V1Service service = (V1Service) util.yaml("service.yaml"); V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml"); V1Secret secret = (V1Secret) util.yaml("secret.yaml"); + List envVars = new ArrayList<>( + Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()) + .orElse(List.of())); + + if (configDisabled) { + V1EnvVar disableConfig = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CONFIG_ENABLED").value("FALSE"); + envVars.add(disableConfig); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); + } + if (phase.equals(Phase.CREATE)) { util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); util.createAndWait(NAMESPACE, null, secret); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/event/reload/ConfigMapEventReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/event/reload/ConfigMapEventReloadIT.java index a244ca57..d334a5fe 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/event/reload/ConfigMapEventReloadIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/event/reload/ConfigMapEventReloadIT.java @@ -99,7 +99,7 @@ class ConfigMapEventReloadIT { */ @Test void testInformFromOneNamespaceEventNotTriggered() { - manifests("one", Phase.CREATE); + manifests("one", Phase.CREATE, false); Commons.assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", IMAGE_NAME); @@ -130,7 +130,7 @@ class ConfigMapEventReloadIT { // left configmap has not changed, no restart of app has happened Assertions.assertEquals("left-initial", result); - manifests("one", Phase.DELETE); + manifests("one", Phase.DELETE, false); } /** @@ -143,7 +143,7 @@ class ConfigMapEventReloadIT { */ @Test void testInformFromOneNamespaceEventTriggered() { - manifests("two", Phase.CREATE); + manifests("two", Phase.CREATE, false); Commons.assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", IMAGE_NAME); @@ -170,7 +170,7 @@ class ConfigMapEventReloadIT { }); Assertions.assertEquals("right-after-change", resultAfterChange[0]); - manifests("two", Phase.DELETE); + manifests("two", Phase.DELETE, false); } /** @@ -184,7 +184,7 @@ class ConfigMapEventReloadIT { */ @Test void testInform() { - manifests("three", Phase.CREATE); + manifests("three", Phase.CREATE, false); Commons.assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", IMAGE_NAME); @@ -241,10 +241,51 @@ class ConfigMapEventReloadIT { .block(); Assertions.assertEquals("right-after-change", rightResult); - manifests("three", Phase.DELETE); + manifests("three", Phase.DELETE, false); } - private static void manifests(String activeProfile, Phase phase) { + /** + *
+	 *     - there are two namespaces : left and right
+	 *     - each of the namespaces has one configmap
+	 *     - secrets are disabled
+	 *     - we watch the "right" namespace and make a change in the configmap in the same namespace
+	 *     - as such, event is triggered and we see the updated value
+	 * 
+ */ + @Test + void testInformFromOneNamespaceEventTriggeredSecretsDisabled() { + manifests("two", Phase.CREATE, true); + Commons.assertReloadLogStatements("added configmap informer for namespace", + "added secret informer for namespace", IMAGE_NAME); + + // read the value from the right-configmap + WebClient webClient = builder().baseUrl("http://localhost/right").build(); + String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + Assertions.assertEquals("right-initial", result); + + // then deploy a new version of right-configmap + ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build()) + .withData(Map.of("right.value", "right-after-change")).build(); + + replaceConfigMap(rightConfigMapAfterChange); + + String[] resultAfterChange = new String[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { + WebClient innerWebClient = builder().baseUrl("http://localhost/right").build(); + String innerResult = innerWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + resultAfterChange[0] = innerResult; + return innerResult != null; + }); + Assertions.assertEquals("right-after-change", resultAfterChange[0]); + + manifests("two", Phase.DELETE, true); + } + + private static void manifests(String activeProfile, Phase phase, boolean secretsDisabled) { InputStream deploymentStream = util.inputStream("deployment.yaml"); InputStream serviceStream = util.inputStream("service.yaml"); @@ -261,6 +302,13 @@ class ConfigMapEventReloadIT { .build(); envVars.add(activeProfileProperty); + if (secretsDisabled) { + EnvVar secretsDisabledEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED") + .withValue("FALSE").build(); + envVars.add(secretsDisabledEnvVar); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); + } + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); Service service = client.services().load(serviceStream).get(); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java index 0abf0e2c..299a70f4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java @@ -18,10 +18,14 @@ package org.springframework.cloud.kubernetes.fabric8.secrets.event.reload; import java.io.InputStream; import java.time.Duration; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Map; import java.util.Objects; +import io.fabric8.kubernetes.api.model.EnvVar; +import io.fabric8.kubernetes.api.model.EnvVarBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; @@ -71,17 +75,16 @@ class SecretsEventsReloadIT { util = new Util(K3S); client = util.client(); util.setUp(NAMESPACE); - manifests(Phase.CREATE); } @AfterAll static void after() throws Exception { - manifests(Phase.DELETE); Commons.cleanUp(IMAGE_NAME, K3S); } @Test - void test() { + void testSimple() { + manifests(Phase.CREATE, false); Commons.assertReloadLogStatements("added secret informer for namespace", "added configmap informer for namespace", IMAGE_NAME); WebClient webClient = builder().baseUrl("http://localhost/key").build(); @@ -104,9 +107,39 @@ class SecretsEventsReloadIT { await().timeout(Duration.ofSeconds(120)).until(() -> webClient.method(HttpMethod.GET).retrieve() .bodyToMono(String.class).retryWhen(retrySpec()).block().equals("after-change")); + manifests(Phase.DELETE, false); } - private static void manifests(Phase phase) { + @Test + void testSimpleConfigMapsDisabled() { + manifests(Phase.CREATE, true); + Commons.assertReloadLogStatements("added secret informer for namespace", + "added configmap informer for namespace", IMAGE_NAME); + WebClient webClient = builder().baseUrl("http://localhost/key").build(); + String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + + // we first read the initial value from the secret + Assertions.assertEquals("initial", result); + + // then deploy a new version of the secret + // since we poll and have reload in place, the new property must be visible + Secret secret = new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("default").withName("event-reload").build()) + .withData(Map.of("application.properties", + Base64.getEncoder().encodeToString("from.properties.key=after-change".getBytes()))) + .build(); + + client.secrets().inNamespace("default").resource(secret).createOrReplace(); + + await().timeout(Duration.ofSeconds(120)).until(() -> webClient.method(HttpMethod.GET).retrieve() + .bodyToMono(String.class).retryWhen(retrySpec()).block().equals("after-change")); + + manifests(Phase.DELETE, true); + + } + + private static void manifests(Phase phase, boolean configMapsDisabled) { InputStream deploymentStream = util.inputStream("deployment.yaml"); InputStream serviceStream = util.inputStream("service.yaml"); @@ -118,6 +151,15 @@ class SecretsEventsReloadIT { Ingress ingress = client.network().v1().ingresses().load(ingressStream).get(); Secret secret = client.secrets().load(secretStream).get(); + if (configMapsDisabled) { + List envVars = new ArrayList<>( + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()); + EnvVar configMapsDisabledEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_CONFIG_ENABLED") + .withValue("FALSE").build(); + envVars.add(configMapsDisabledEnvVar); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); + } + if (phase.equals(Phase.CREATE)) { util.createAndWait(NAMESPACE, null, secret); util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);