Merge branch '3.0.x'

This commit is contained in:
Ryan Baxter
2023-09-18 11:25:37 -04:00
14 changed files with 0 additions and 548 deletions

View File

@@ -56,7 +56,6 @@
<module>spring-cloud-kubernetes-fabric8-client-configmap-event-reload</module>
<module>spring-cloud-kubernetes-discoveryclient-it</module>
<module>spring-cloud-kubernetes-client-config-it</module>
<module>spring-cloud-kubernetes-client-loadbalancer-it</module>
<module>spring-cloud-kubernetes-client-reactive-discoveryclient-it</module>
<module>spring-cloud-kubernetes-configuration-watcher-it</module>

View File

@@ -1,111 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-client-config-it</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>../src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<!-- build image in the 'package' phase, and ignore plain tests -->
<!-- via maven-surefire-plugin::skipTests -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
</configuration>
<executions>
<execution>
<id>build-image</id>
<configuration>
<skip>${skip.build.image}</skip>
</configuration>
<phase>package</phase>
<goals>
<goal>build-image</goal>
</goals>
</execution>
<execution>
<id>repackage</id>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
</goals>
<configuration>
<includes>
<include>${testsToRun}</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2013-2020 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.it;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Ryan Baxter
*/
@SpringBootApplication
@RestController
public class KubernetesConfigClientApplicationIt {
private final MyConfigurationProperties configurationProperties;
public KubernetesConfigClientApplicationIt(MyConfigurationProperties configurationProperties) {
this.configurationProperties = configurationProperties;
}
@GetMapping("/myProperty")
public String myProperty() {
return configurationProperties.getMyProperty();
}
@GetMapping("/mySecret")
public String mySecret() {
return configurationProperties.getMySecret();
}
public static void main(String[] args) {
SpringApplication.run(KubernetesConfigClientApplicationIt.class, args);
}
@Configuration
@EnableConfigurationProperties(MyConfigurationProperties.class)
static class MyConfig {
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2013-2020 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.it;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Ryan Baxter
*/
@ConfigurationProperties("my.config")
public class MyConfigurationProperties {
private String myProperty = "default-value";
private String mySecret = "default-secret-value";
public MyConfigurationProperties() {
}
public String getMyProperty() {
return myProperty;
}
public void setMyProperty(String myProperty) {
this.myProperty = myProperty;
}
public String getMySecret() {
return mySecret;
}
public void setMySecret(String mySecret) {
this.mySecret = mySecret;
}
}

View File

@@ -1,5 +0,0 @@
management:
endpoints:
web:
exposure:
include: "*"

View File

@@ -1,14 +0,0 @@
spring:
cloud:
kubernetes:
secrets:
enable-api: true
reload:
enabled: true
monitoring-secrets: true
logging:
level:
org:
springframework:
cloud:
kubernetes: DEBUG

View File

@@ -1,4 +0,0 @@
spring:
application:
name: spring-cloud-kubernetes-client-config-it

View File

@@ -1,213 +0,0 @@
/*
* Copyright 2013-2020 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.it;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1Deployment;
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.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import static org.awaitility.Awaitility.await;
import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithReplace;
/**
* @author Ryan Baxter
*/
class ConfigMapAndSecretIT {
private static final Map<String, String> POD_LABELS = Map.of("app", "spring-cloud-kubernetes-client-config-it");
private static final String BODY = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-client-config-it-deployment",
"image": "image_name_here",
"env": [
{
"name": "SPRING_CLOUD_KUBERNETES_RELOAD_MODE",
"value": "POLLING"
}
]
}]
}
}
}
}
""";
private static final String PROPERTY_URL = "http://localhost:80/myProperty";
private static final String SECRET_URL = "http://localhost:80/mySecret";
private static final String K8S_CONFIG_CLIENT_IT_SERVICE_NAME = "spring-cloud-kubernetes-client-config-it";
private static final String NAMESPACE = "default";
private static final String APP_NAME = "spring-cloud-kubernetes-client-config-it";
private static final String DOCKER_IMAGE = "docker.io/springcloud/" + APP_NAME + ":" + Commons.pomVersion();
private static final K3sContainer K3S = Commons.container();
private static Util util;
private static CoreV1Api coreV1Api;
@BeforeAll
static void setup() throws Exception {
K3S.start();
Commons.validateImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
util = new Util(K3S);
coreV1Api = new CoreV1Api();
util.setUp(NAMESPACE);
}
@AfterAll
static void afterAll() throws Exception {
configK8sClientIt(Phase.DELETE);
Commons.cleanUp(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
Commons.systemPrune();
}
@Test
void testConfigMapAndSecretWatchRefresh() throws Exception {
configK8sClientIt(Phase.CREATE);
testConfigMapAndSecretRefresh();
testConfigMapAndSecretPollingRefresh();
}
void testConfigMapAndSecretPollingRefresh() throws Exception {
recreateConfigMapAndSecret();
patchForPollingReload();
testConfigMapAndSecretRefresh();
}
/**
* <pre>
* - read configmap/secrets the way we initially build them and assert their values
* - replace the above and assert we get the new values.
* </pre>
*/
void testConfigMapAndSecretRefresh() throws Exception {
WebClient.Builder builder = builder();
WebClient propertyClient = builder.baseUrl(PROPERTY_URL).build();
await().timeout(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2))
.ignoreException(WebClientResponseException.BadGateway.class).until(() -> propertyClient
.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block().equals("from-config-map"));
WebClient secretClient = builder.baseUrl(SECRET_URL).build();
String secret = secretClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
.block();
Assertions.assertEquals(secret, "p455w0rd");
V1ConfigMap configMap = (V1ConfigMap) util.yaml("spring-cloud-kubernetes-client-config-it-configmap.yaml");
Map<String, String> data = configMap.getData();
data.replace("application.yaml", data.get("application.yaml").replace("from-config-map", "from-unit-test"));
configMap.data(data);
coreV1Api.replaceNamespacedConfigMap(APP_NAME, NAMESPACE, configMap, null, null, null, null);
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2))
.ignoreException(WebClientResponseException.BadGateway.class).until(() -> propertyClient
.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block().equals("from-unit-test"));
V1Secret v1Secret = (V1Secret) util.yaml("spring-cloud-kubernetes-client-config-it-secret.yaml");
Map<String, byte[]> secretData = v1Secret.getData();
secretData.replace("my.config.mySecret", "p455w1rd".getBytes());
v1Secret.setData(secretData);
coreV1Api.replaceNamespacedSecret(APP_NAME, NAMESPACE, v1Secret, null, null, null, null);
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2))
.ignoreException(WebClientResponseException.BadGateway.class).until(() -> secretClient
.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block().equals("p455w1rd"));
}
private void recreateConfigMapAndSecret() {
try {
coreV1Api.deleteNamespacedConfigMap("spring-cloud-kubernetes-client-config-it", NAMESPACE, null, null, null,
null, null, null);
coreV1Api.deleteNamespacedSecret("spring-cloud-kubernetes-client-config-it", NAMESPACE, null, null, null,
null, null, null);
V1ConfigMap configMap = (V1ConfigMap) util.yaml("spring-cloud-kubernetes-client-config-it-configmap.yaml");
V1Secret secret = (V1Secret) util.yaml("spring-cloud-kubernetes-client-config-it-secret.yaml");
util.createAndWait(NAMESPACE, configMap, secret);
}
catch (ApiException e) {
throw new RuntimeException(e);
}
}
private static void configK8sClientIt(Phase phase) {
V1Deployment deployment = (V1Deployment) util.yaml("spring-cloud-kubernetes-client-config-it-deployment.yaml");
V1Service service = (V1Service) util.yaml("spring-cloud-kubernetes-client-config-it-service.yaml");
V1Ingress ingress = (V1Ingress) util.yaml("spring-cloud-kubernetes-client-config-it-ingress.yaml");
V1ConfigMap configMap = (V1ConfigMap) util.yaml("spring-cloud-kubernetes-client-config-it-configmap.yaml");
V1Secret secret = (V1Secret) util.yaml("spring-cloud-kubernetes-client-config-it-secret.yaml");
if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
util.createAndWait(NAMESPACE, configMap, secret);
}
else if (phase.equals(Phase.DELETE)) {
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
util.deleteAndWait(NAMESPACE, configMap, secret);
}
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
}
private static void patchForPollingReload() {
patchWithReplace(ConfigMapAndSecretIT.DOCKER_IMAGE, ConfigMapAndSecretIT.APP_NAME + "-deployment",
ConfigMapAndSecretIT.NAMESPACE, BODY, POD_LABELS);
}
}

View File

@@ -1,14 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.testcontainers" level="INFO"/>
<logger name="com.github.dockerjava" level="WARN"/>
</configuration>

View File

@@ -1,9 +0,0 @@
apiVersion: v1
data:
application.yaml: |-
my:
config:
myProperty: from-config-map
kind: ConfigMap
metadata:
name: spring-cloud-kubernetes-client-config-it

View File

@@ -1,31 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-kubernetes-client-config-it-deployment
spec:
selector:
matchLabels:
app: spring-cloud-kubernetes-client-config-it
template:
metadata:
labels:
app: spring-cloud-kubernetes-client-config-it
spec:
serviceAccountName: spring-cloud-kubernetes-serviceaccount
containers:
- name: spring-cloud-kubernetes-client-config-it
image: docker.io/springcloud/spring-cloud-kubernetes-client-config-it
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 8080
path: /actuator/health/readiness
livenessProbe:
httpGet:
port: 8080
path: /actuator/health/liveness
env:
- name: SPRING_PROFILES_ACTIVE
value: kubernetes
ports:
- containerPort: 8080

View File

@@ -1,16 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: it-ingress
namespace: default
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spring-cloud-kubernetes-client-config-it
port:
number: 8080

View File

@@ -1,7 +0,0 @@
apiVersion: v1
data:
my.config.mySecret: cDQ1NXcwcmQ=
kind: Secret
metadata:
name: spring-cloud-kubernetes-client-config-it
type: Opaque

View File

@@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: spring-cloud-kubernetes-client-config-it
name: spring-cloud-kubernetes-client-config-it
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: spring-cloud-kubernetes-client-config-it
type: ClusterIP