Merge branch '2.1.x'

This commit is contained in:
Ryan Baxter
2023-05-11 16:23:49 -04:00
26 changed files with 987 additions and 5 deletions

View File

@@ -867,6 +867,11 @@ such a method. This, in turn, could be configured via environment properties. Fo
Failure to find a namespace from the above steps will result in an Exception being raised.
[[order_of_configMaps_and_secrets]]
=== Order of ConfigMaps and Secrets
If, for whatever reason, you enabled both configmaps and secrets, and there is a common property between them, the value from the ConfigMap will have a higher precedence. That is: it will override whatever values are found in secrets.
=== `PropertySource` Reload
WARNING: This functionality has been deprecated in the 2020.0 release. Please see

View File

@@ -22,6 +22,7 @@ import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
@@ -31,6 +32,7 @@ import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.
* @author Ryan Baxter
* @author Isik Erhan
*/
@Order(0)
public class KubernetesClientConfigMapPropertySourceLocator extends ConfigMapPropertySourceLocator {
private final CoreV1Api coreV1Api;

View File

@@ -23,6 +23,7 @@ import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.getApplicationNamespace;
@@ -31,6 +32,7 @@ import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.
* @author Ryan Baxter
* @author Isik Erhan
*/
@Order(1)
public class KubernetesClientSecretsPropertySourceLocator extends SecretsPropertySourceLocator {
private final CoreV1Api coreV1Api;

View File

@@ -22,6 +22,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.retry.annotation.Retryable;
@@ -31,8 +32,8 @@ import org.springframework.retry.annotation.Retryable;
*
* @author Ryan Baxter
*/
public class RetryableKubernetesClientConfigMapPropertySourceLocator
extends KubernetesClientConfigMapPropertySourceLocator {
@Order(0)
class RetryableKubernetesClientConfigMapPropertySourceLocator extends KubernetesClientConfigMapPropertySourceLocator {
RetryableKubernetesClientConfigMapPropertySourceLocator(CoreV1Api coreV1Api, ConfigMapConfigProperties properties,
KubernetesNamespaceProvider kubernetesNamespaceProvider) {

View File

@@ -22,6 +22,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.retry.annotation.Retryable;
@@ -31,8 +32,8 @@ import org.springframework.retry.annotation.Retryable;
*
* @author Ryan Baxter
*/
public class RetryableKubernetesClientSecretsPropertySourceLocator
extends KubernetesClientSecretsPropertySourceLocator {
@Order(1)
class RetryableKubernetesClientSecretsPropertySourceLocator extends KubernetesClientSecretsPropertySourceLocator {
RetryableKubernetesClientSecretsPropertySourceLocator(CoreV1Api coreV1Api,
KubernetesNamespaceProvider kubernetesNamespaceProvider, SecretsConfigProperties secretsConfigProperties) {

View File

@@ -0,0 +1,79 @@
/*
* 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.client.config;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.config.applications.sources_order.SourcesOrderApp;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in :
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SourcesOrderConfigurationStub}
*
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SourcesOrderApp.class,
properties = { "spring.cloud.bootstrap.name=sources-order", "sources.order.stub=true" })
@AutoConfigureWebTestClient
class KubernetesClientSourcesOrderTests {
@Autowired
private WebTestClient webClient;
@AfterEach
void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
* 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
*
* We invoke three endpoints: /one, /two, /key.
* The first two prove that both the secret and configmap have been read, the last one proves that
* config maps have a higher precedence.
* </pre>
*/
@Test
void test() {
this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
this.webClient.get().uri("/key").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("from-configmap"));
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.client.config;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.config.applications.sources_order.SourcesOrderApp;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* The stub data for this test is in :
* {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SourcesOrderConfigurationStub}
*
* @author wind57
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SourcesOrderApp.class,
properties = { "spring.cloud.bootstrap.name=retryable-sources-order", "sources.order.stub=true" })
@AutoConfigureWebTestClient
class RetryableKubernetesClientSourcesOrderTests {
@Autowired
private WebTestClient webClient;
@AfterEach
void afterEach() {
WireMock.reset();
}
@AfterAll
static void afterAll() {
WireMock.shutdownServer();
}
/**
* <pre>
* 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
* 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
*
* We invoke three endpoints: /one, /two, /key.
* The first two prove that both the secret and configmap have been read, the last one proves that
* config maps have a higher precedence.
* </pre>
*/
@Test
void test() {
this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
this.webClient.get().uri("/key").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("from-configmap"));
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.client.config.applications.sources_order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.client.config.applications.sources_order.properties.Properties;
/**
* @author wind57
*/
@SpringBootApplication
@EnableConfigurationProperties(Properties.class)
public class SourcesOrderApp {
public static void main(String[] args) {
SpringApplication.run(SourcesOrderApp.class, args);
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.client.config.applications.sources_order.controller;
import org.springframework.cloud.kubernetes.client.config.applications.sources_order.properties.Properties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wind57
*/
@RestController
public class SourcesOrderController {
private final Properties properties;
SourcesOrderController(Properties properties) {
this.properties = properties;
}
@GetMapping("/key")
String key() {
return properties.getKey();
}
@GetMapping("/one")
String one() {
return properties.getOne();
}
@GetMapping("/two")
String two() {
return properties.getTwo();
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.client.config.applications.sources_order.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author wind57
*/
@ConfigurationProperties("my")
public class Properties {
private String key;
private String one;
private String two;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.client.config.boostrap.stubs;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
@Order(0)
@Configuration
@ConditionalOnProperty("sources.order.stub")
public class SourcesOrderConfigurationStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubConfigMapData();
stubSecretsData();
return apiClient;
}
private void stubConfigMapData() {
Map<String, String> configMapData = new HashMap<>();
configMapData.put("my.key", "from-configmap");
configMapData.put("my.two", "two");
V1ConfigMap myConfigMap = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder()
.withName("my-configmap").withNamespace("spring-k8s").withResourceVersion("1").build())
.addToData(configMapData).build();
V1ConfigMapList allConfigMaps = new V1ConfigMapList();
allConfigMaps.setItems(Collections.singletonList(myConfigMap));
// the actual stub for CoreV1Api calls
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allConfigMaps))));
}
private void stubSecretsData() {
Map<String, byte[]> secretData = new HashMap<>();
secretData.put("my.key", "from-secret".getBytes(StandardCharsets.UTF_8));
secretData.put("my.one", "one".getBytes(StandardCharsets.UTF_8));
V1Secret mySecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withName("my-secret")
.withNamespace("spring-k8s").withResourceVersion("1").build()).addToData(secretData).build();
V1SecretList allConfigMaps = new V1SecretList();
allConfigMaps.setItems(Collections.singletonList(mySecret));
// the actual stub for CoreV1Api calls
WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allConfigMaps))));
}
}

View File

@@ -8,4 +8,7 @@ org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretW
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SingleSourceMultipleFilesConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.ConfigMapNameAsPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SourcesOrderConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.EnableRetryBootstrapConfiguration

View File

@@ -0,0 +1,17 @@
spring:
application:
name: sources-order
cloud:
kubernetes:
secrets:
fail-fast: true
enabled: true
enableApi: true
namespace: spring-k8s
sources:
- name: my-secret
config:
fail-fast: true
namespace: spring-k8s
sources:
- name: my-configmap

View File

@@ -0,0 +1,15 @@
spring:
application:
name: sources-order
cloud:
kubernetes:
secrets:
enabled: true
enableApi: true
namespace: spring-k8s
sources:
- name: my-secret
config:
namespace: spring-k8s
sources:
- name: my-configmap

View File

@@ -32,7 +32,7 @@ import org.springframework.retry.annotation.Retryable;
*
* @author Ryan Baxter
*/
@Order(1)
@Order(0)
class RetryableFabric8ConfigMapPropertySourceLocator extends Fabric8ConfigMapPropertySourceLocator {
RetryableFabric8ConfigMapPropertySourceLocator(KubernetesClient client, ConfigMapConfigProperties properties,

View File

@@ -22,6 +22,7 @@ import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.retry.annotation.Retryable;
@@ -31,6 +32,7 @@ import org.springframework.retry.annotation.Retryable;
*
* @author Ryan Baxter
*/
@Order(1)
class RetryableFabric8SecretsPropertySourceLocator extends Fabric8SecretsPropertySourceLocator {
RetryableFabric8SecretsPropertySourceLocator(KubernetesClient client, SecretsConfigProperties properties,

View File

@@ -0,0 +1,57 @@
/*
* 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.fabric8.config.retryable_sources_order;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author wind57
*/
@ConfigurationProperties("my")
class Properties {
private String key;
private String one;
private String two;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.fabric8.config.retryable_sources_order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author wind57
*/
@SpringBootApplication
@EnableConfigurationProperties(Properties.class)
class RetryableSourcesOrderApp {
static void main(String[] args) {
SpringApplication.run(RetryableSourcesOrderApp.class, args);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.fabric8.config.retryable_sources_order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wind57
*/
@RestController
class RetryableSourcesOrderController {
private final Properties properties;
RetryableSourcesOrderController(Properties properties) {
this.properties = properties;
}
@GetMapping("/retryable-key")
String key() {
return properties.getKey();
}
@GetMapping("/retryable-one")
String one() {
return properties.getOne();
}
@GetMapping("/retryable-two")
String two() {
return properties.getTwo();
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.fabric8.config.retryable_sources_order;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RetryableSourcesOrderApp.class,
properties = { "spring.cloud.bootstrap.name=retryable-sources-order" })
@EnableKubernetesMockClient(crud = true, https = false)
class RetryableSourcesOrderTests {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@BeforeAll
static void setUpBeforeClass() {
// Configure the kubernetes master url to point to the mock server
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
Map<String, String> secretData = new HashMap<>();
secretData.put("my.key", Base64.getEncoder().encodeToString("from-secret".getBytes(StandardCharsets.UTF_8)));
secretData.put("my.one", Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8)));
createSecret("my-secret", secretData);
Map<String, String> configMapData = new HashMap<>();
configMapData.put("my.key", "from-configmap");
configMapData.put("my.two", "two");
createConfigmap("my-configmap", configMapData);
}
@AfterAll
static void afterAll() {
System.clearProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY);
}
private static void createSecret(String name, Map<String, String> data) {
mockClient.secrets().inNamespace("spring-k8s")
.create(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build());
}
private static void createConfigmap(String name, Map<String, String> data) {
mockClient.configMaps().inNamespace("spring-k8s")
.create(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build());
}
/**
* <pre>
* 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
* 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
*
* We invoke three endpoints: /one, /two, /key.
* The first two prove that both the secret and configmap have been read, the last one proves that
* config maps have a higher precedence.
* </pre>
*/
@Test
void test() {
this.webClient.get().uri("/retryable-one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
this.webClient.get().uri("/retryable-two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
this.webClient.get().uri("/retryable-key").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("from-configmap"));
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.fabric8.config.sources_order;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author wind57
*/
@ConfigurationProperties("my")
class Properties {
private String key;
private String one;
private String two;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.fabric8.config.sources_order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author wind57
*/
@SpringBootApplication
@EnableConfigurationProperties(Properties.class)
class SourcesOrderApp {
static void main(String[] args) {
SpringApplication.run(SourcesOrderApp.class, args);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.fabric8.config.sources_order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wind57
*/
@RestController
class SourcesOrderController {
private final Properties properties;
SourcesOrderController(Properties properties) {
this.properties = properties;
}
@GetMapping("/key")
String key() {
return properties.getKey();
}
@GetMapping("/one")
String one() {
return properties.getOne();
}
@GetMapping("/two")
String two() {
return properties.getTwo();
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.fabric8.config.sources_order;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SourcesOrderApp.class,
properties = { "spring.cloud.bootstrap.name=sources-order" })
@EnableKubernetesMockClient(crud = true, https = false)
class SourcesOrderTests {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@BeforeAll
static void setUpBeforeClass() {
// Configure the kubernetes master url to point to the mock server
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
Map<String, String> secretData = new HashMap<>();
secretData.put("my.key", Base64.getEncoder().encodeToString("from-secret".getBytes(StandardCharsets.UTF_8)));
secretData.put("my.one", Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8)));
createSecret("my-secret", secretData);
Map<String, String> configMapData = new HashMap<>();
configMapData.put("my.key", "from-configmap");
configMapData.put("my.two", "two");
createConfigmap("my-configmap", configMapData);
}
@AfterAll
static void afterAll() {
System.clearProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY);
}
private static void createSecret(String name, Map<String, String> data) {
mockClient.secrets().inNamespace("spring-k8s")
.create(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build());
}
private static void createConfigmap(String name, Map<String, String> data) {
mockClient.configMaps().inNamespace("spring-k8s")
.create(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build());
}
/**
* <pre>
* 1. There is one secret deployed: my-secret. It has two properties: {my.one=one, my.key=from-secret}
* 2. There is one configmap deployed: my-configmap. It has two properties: {my.two=two, my.key=from-configmap}
*
* We invoke three endpoints: /one, /two, /key.
* The first two prove that both the secret and configmap have been read, the last one proves that
* config maps have a higher precedence.
* </pre>
*/
@Test
void test() {
this.webClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("one"));
this.webClient.get().uri("/two").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("two"));
this.webClient.get().uri("/key").exchange().expectStatus().isOk().expectBody(String.class)
.value(Matchers.equalTo("from-configmap"));
}
}

View File

@@ -0,0 +1,17 @@
spring:
application:
name: sources-order
cloud:
kubernetes:
secrets:
fail-fast: true
enabled: true
enableApi: true
namespace: spring-k8s
sources:
- name: my-secret
config:
fail-fast: true
namespace: spring-k8s
sources:
- name: my-configmap

View File

@@ -0,0 +1,15 @@
spring:
application:
name: sources-order
cloud:
kubernetes:
secrets:
enabled: true
enableApi: true
namespace: spring-k8s
sources:
- name: my-secret
config:
namespace: spring-k8s
sources:
- name: my-configmap