Initial commit

This commit is contained in:
spencergibb
2023-09-19 13:10:02 -04:00
parent 2e4d529f30
commit 8a57c69c88
1303 changed files with 8661 additions and 95772 deletions

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>3.0.4-SNAPSHOT</version>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<version>3.0.4-SNAPSHOT</version>
<packaging>pom</packaging>
<description>Spring Cloud parent pom, managing plugins and dependencies for Spring
Cloud projects</description>
<url>https://cloud.spring.io/spring-cloud-kubernetes-controllers</url>
<inceptionYear>2017</inceptionYear>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>sgibb</id>
<name>Spencer Gibb</name>
<email>sgibb at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>mgrzejszczak</id>
<name>Marcin Grzejszczak</name>
<email>mgrzejszczak at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>rbaxter</id>
<name>Ryan Baxter</name>
<email>rbaxter at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>omaciaszeksharma</id>
<name>Olga Maciaszek-Sharma</name>
<email>omaciaszeksharma at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers</developerConnection>
<url>https://github.com/spring-cloud/spring-cloud-kubernetes/spring-cloud-kubernetes-controllers</url>
</scm>
</project>

View File

@@ -1,21 +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>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<modules>
<module>spring-cloud-kubernetes-configuration-watcher</module>
<module>spring-cloud-kubernetes-discoveryserver</module>
<module>spring-cloud-kubernetes-configserver</module>
</modules>
</project>

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<version>3.0.4-SNAPSHOT</version>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-configserver</artifactId>
<version>3.0.4-SNAPSHOT</version>
<description>Spring Cloud parent pom, managing plugins and dependencies for Spring
Cloud projects</description>
<url>https://cloud.spring.io/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver</url>
<inceptionYear>2017</inceptionYear>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>sgibb</id>
<name>Spencer Gibb</name>
<email>sgibb at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>mgrzejszczak</id>
<name>Marcin Grzejszczak</name>
<email>mgrzejszczak at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>rbaxter</id>
<name>Ryan Baxter</name>
<email>rbaxter at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>omaciaszeksharma</id>
<name>Olga Maciaszek-Sharma</name>
<email>omaciaszeksharma at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver</developerConnection>
<url>https://github.com/spring-cloud/spring-cloud-kubernetes/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver</url>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>4.0.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
<version>3.0.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,138 +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>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-configserver</artifactId>
<properties>
<docker.registry.organization>springcloud</docker.registry.organization>
<plexus-archiver.version>4.1.0</plexus-archiver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- in favor of mockito-inline -->
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>${env.IMAGE}</name>
</image>
<goal>build-image</goal>
</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>
</plugins>
</build>
<profiles>
<profile>
<id>dockerpush</id>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.12</version>
<configuration>
<repository>${docker.registry.organization}/${artifactId}</repository>
<tag>${project.version}</tag>
<username>${env.DOCKER_HUB_USERNAME}</username>
<password>${env.DOCKER_HUB_PASSWORD}</password>
<build>
<noCache>true</noCache>
</build>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>
<version>${plexus-archiver.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>imagename</id>
<activation>
<property>
<name>!env.IMAGE</name>
</property>
</activation>
<properties>
<env.IMAGE>springcloud/${project.artifactId}:${project.version}</env.IMAGE>
</properties>
</profile>
</profiles>
</project>

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.config.server.ConfigServerApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* @author Ryan Baxter
*/
@SpringBootApplication
@EnableConfigServer
public class KubernetesConfigServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApplication.class).run(args);
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import java.util.ArrayList;
import java.util.List;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.config.ConfigServerAutoConfiguration;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigContext;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource;
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesConfigEnabled;
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesSecretsEnabled;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.MapPropertySource;
import static org.springframework.cloud.kubernetes.configserver.KubernetesPropertySourceSupplier.namespaceSplitter;
/**
* @author Ryan Baxter
*/
@Configuration
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class })
@AutoConfigureBefore({ ConfigServerAutoConfiguration.class })
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@EnableConfigurationProperties(KubernetesConfigServerProperties.class)
public class KubernetesConfigServerAutoConfiguration {
@Bean
@Profile("kubernetes")
public EnvironmentRepository kubernetesEnvironmentRepository(CoreV1Api coreV1Api,
List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers,
KubernetesNamespaceProvider kubernetesNamespaceProvider) {
return new KubernetesEnvironmentRepository(coreV1Api, kubernetesPropertySourceSuppliers,
kubernetesNamespaceProvider.getNamespace());
}
@Bean
@ConditionalOnKubernetesConfigEnabled
@ConditionalOnProperty(value = "spring.cloud.kubernetes.config.enableApi", matchIfMissing = true)
public KubernetesPropertySourceSupplier configMapPropertySourceSupplier(
KubernetesConfigServerProperties properties) {
return (coreApi, applicationName, namespace, springEnv) -> {
List<String> namespaces = namespaceSplitter(properties.getConfigMapNamespaces(), namespace);
List<MapPropertySource> propertySources = new ArrayList<>();
namespaces.forEach(space -> {
NamedConfigMapNormalizedSource source = new NamedConfigMapNormalizedSource(applicationName, space,
false, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space,
springEnv);
propertySources.add(new KubernetesClientConfigMapPropertySource(context));
});
return propertySources;
};
}
@Bean
@ConditionalOnKubernetesSecretsEnabled
@ConditionalOnProperty("spring.cloud.kubernetes.secrets.enableApi")
public KubernetesPropertySourceSupplier secretsPropertySourceSupplier(KubernetesConfigServerProperties properties) {
return (coreApi, applicationName, namespace, springEnv) -> {
List<String> namespaces = namespaceSplitter(properties.getSecretsNamespaces(), namespace);
List<MapPropertySource> propertySources = new ArrayList<>();
namespaces.forEach(space -> {
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false, false);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space,
springEnv);
propertySources.add(new KubernetesClientSecretsPropertySource(context));
});
return propertySources;
};
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Ryan Baxter
*/
@ConfigurationProperties("spring.cloud.kubernetes.configserver")
public class KubernetesConfigServerProperties {
private String configMapNamespaces = "";
private String secretsNamespaces = "";
public String getConfigMapNamespaces() {
return configMapNamespaces;
}
public void setConfigMapNamespaces(String configMapNamespaces) {
this.configMapNamespaces = configMapNamespaces;
}
public String getSecretsNamespaces() {
return secretsNamespaces;
}
public void setSecretsNamespaces(String secretsNamespaces) {
this.secretsNamespaces = secretsNamespaces;
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import java.util.List;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.StringUtils;
/**
* @author Ryan Baxter
*/
public class KubernetesEnvironmentRepository implements EnvironmentRepository {
private static final Log LOG = LogFactory.getLog(KubernetesEnvironmentRepository.class);
private CoreV1Api coreApi;
private List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers;
private String namespace;
public KubernetesEnvironmentRepository(CoreV1Api coreApi,
List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers, String namespace) {
this.coreApi = coreApi;
this.kubernetesPropertySourceSuppliers = kubernetesPropertySourceSuppliers;
this.namespace = namespace;
}
@Override
public Environment findOne(String application, String profile, String label) {
return findOne(application, profile, label, true);
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
LOG.info("Profiles: " + profile);
LOG.info("Application: " + application);
LOG.info("Label: " + label);
Environment environment = new Environment(application, profiles, label, null, null);
try {
StandardEnvironment springEnv = new StandardEnvironment();
springEnv.setActiveProfiles(profiles);
if (!"application".equalsIgnoreCase(application)) {
addApplicationConfiguration(environment, springEnv, application);
}
addApplicationConfiguration(environment, springEnv, "application");
return environment;
}
catch (Exception e) {
LOG.warn(e);
}
return environment;
}
private void addApplicationConfiguration(Environment environment, StandardEnvironment springEnv,
String applicationName) {
kubernetesPropertySourceSuppliers.stream().forEach(supplier -> {
List<MapPropertySource> propertySources = supplier.get(coreApi, applicationName, namespace, springEnv);
propertySources.forEach(propertySource -> {
if (propertySource.getPropertyNames().length > 0) {
LOG.debug("Adding PropertySource " + propertySource.getName());
LOG.debug("PropertySource Names: "
+ StringUtils.arrayToCommaDelimitedString(propertySource.getPropertyNames()));
environment.add(new PropertySource(propertySource.getName(), propertySource.getSource()));
}
});
});
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;
/**
* @author Ryan Baxter
*/
public interface KubernetesPropertySourceSupplier {
List<MapPropertySource> get(CoreV1Api coreV1Api, String name, String namespace, Environment environment);
static List<String> namespaceSplitter(String namespacesString, String currentNamespace) {
List<String> namespaces = Collections.singletonList(currentNamespace);
String[] namespacesArray = StringUtils.commaDelimitedListToStringArray(namespacesString);
if (namespacesArray.length > 0) {
namespaces = Arrays.asList(namespacesArray);
}
return namespaces;
}
}

View File

@@ -1 +0,0 @@
org.springframework.cloud.kubernetes.configserver.KubernetesConfigServerAutoConfiguration

View File

@@ -1,13 +0,0 @@
spring:
application:
name: kubernetesconfigserver
server:
port: 8888
---
spring:
config:
activate:
on-cloud-platform: kubernetes
import: "optional:kubernetes:"

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=default",
"spring.profiles.include=kubernetes", "spring.cloud.kubernetes.secrets.enableApi=true",
"spring.cloud.bootstrap.enabled=true" },
classes = { KubernetesConfigServerApplication.class })
public class BootstrapConfigServerIntegrationTest extends ConfigServerIntegrationTest {
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.MockedStatic;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=default",
"spring.profiles.include=kubernetes", "spring.cloud.kubernetes.secrets.enableApi=true" },
classes = { KubernetesConfigServerApplication.class })
public class ConfigDataConfigServerIntegrationTest extends ConfigServerIntegrationTest {
private static WireMockServer wireMockServer;
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void setup() {
wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
WireMock.configureFor(wireMockServer.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(wireMockServer.baseUrl()).build());
}
@AfterAll
static void teardown() {
wireMockServer.stop();
clientUtilsMock.close();
}
@AfterEach
void afterEach() {
WireMock.reset();
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "spring.profiles.include=kubernetes,kubernetesdisabled" },
classes = { KubernetesConfigServerApplication.class, MockConfig.class })
class ConfigServerAutoConfigurationKubernetesDisabled {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(0);
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class }, properties = { "spring.main.cloud-platform=KUBERNETES",
"spring.profiles.include=kubernetes", "spring.cloud.kubernetes.client.namespace=default" })
class ConfigServerAutoConfigurationKubernetesEnabledProfileIncluded {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(1);
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class },
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.profiles.include=kubernetes",
"spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.config.enableApi=false" })
class ConfigServerAutoConfigurationKubernetesEnabledProfileIncludedConfigApiDisabled {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(0);
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class }, properties = { "spring.main.cloud-platform=KUBERNETES",
"spring.profiles.include=kubernetes", "spring.cloud.kubernetes.config.enabled=false" })
class ConfigServerAutoConfigurationKubernetesEnabledProfileIncludedConfigMapDisabled {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(0);
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class }, properties = { "spring.main.cloud-platform=KUBERNETES",
"spring.profiles.include=kubernetes", "spring.cloud.kubernetes.client.namespace=default" })
class ConfigServerAutoConfigurationKubernetesEnabledProfileIncludedSecretsApiDisabled {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)[0])
.isEqualTo("configMapPropertySourceSupplier");
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class },
properties = { "spring.profiles.include=kubernetes", "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.secrets.enableApi=true" })
class ConfigServerAutoConfigurationKubernetesEnabledProfileIncludedSecretsApiEnabled {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(2);
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class, MockConfig.class },
properties = { "spring.profiles.include=kubernetes,kubernetesdisabled" })
class ConfigServerAutoConfigurationKubernetesProfileMissing {
@Autowired
private ConfigurableApplicationContext context;
@Test
void runTest() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(0);
}
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.openapi.models.V1SecretListBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.config.environment.Environment;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
abstract class ConfigServerIntegrationTest {
@Autowired
private TestRestTemplate testRestTemplate;
@BeforeEach
public void beforeEach() {
V1ConfigMapList TEST_CONFIGMAP = new V1ConfigMapList().addItemsItem(new V1ConfigMapBuilder().withMetadata(
new V1ObjectMetaBuilder().withName("test-cm").withNamespace("default").withResourceVersion("1").build())
.addToData("app.name", "test").build());
V1SecretList TEST_SECRET = new V1SecretListBuilder()
.withMetadata(new V1ListMetaBuilder().withResourceVersion("1").build())
.addToItems(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("test-cm").withResourceVersion("0")
.withNamespace("default").build())
.addToData("password", "p455w0rd".getBytes()).addToData("username", "user".getBytes()).build())
.build();
WireMock.stubFor(get(urlMatching("^/api/v1/namespaces/default/configmaps.*"))
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(TEST_CONFIGMAP))));
WireMock.stubFor(get(urlMatching("^/api/v1/namespaces/default/secrets.*"))
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(TEST_SECRET))));
}
@Test
public void enabled() {
Environment env = testRestTemplate.getForObject("/test-cm/default", Environment.class);
assertThat(env.getPropertySources().size()).isEqualTo(2);
assertThat(env.getPropertySources().get(0).getName().equals("configmap.test-cm.default")).isTrue();
assertThat(env.getPropertySources().get(0).getSource().get("app.name")).isEqualTo("test");
assertThat(env.getPropertySources().get(1).getName().equals("secret.test-cm.default")).isTrue();
assertThat(env.getPropertySources().get(1).getSource().get("password")).isEqualTo("p455w0rd");
assertThat(env.getPropertySources().get(1).getSource().get("username")).isEqualTo("user");
}
}

View File

@@ -1,295 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import java.util.ArrayList;
import java.util.List;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
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.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.openapi.models.V1SecretListBuilder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigContext;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource;
import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource;
import org.springframework.cloud.kubernetes.commons.config.NormalizedSource;
import org.springframework.core.env.MapPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
*/
class KubernetesEnvironmentRepositoryTests {
private static final List<KubernetesPropertySourceSupplier> KUBERNETES_PROPERTY_SOURCE_SUPPLIER = new ArrayList<>();
private static final String VALUE = "dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n";
private static final String DEFAULT_NAMESPACE = "default";
private static final V1ConfigMapList CONFIGMAP_DEFAULT_LIST = new V1ConfigMapList()
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName("application").withNamespace(DEFAULT_NAMESPACE).build())
.addToData("application.yaml", VALUE).build())
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("stores").withNamespace(DEFAULT_NAMESPACE).build())
.addToData("application.yaml", VALUE).build())
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(
new V1ObjectMetaBuilder().withName("stores-dev").withNamespace(DEFAULT_NAMESPACE).build())
.addToData("application.yaml",
"dummy:\n property:\n string1: \"a\"\n string2: \"b\"\n int2: 2\n bool2: false\n")
.build());
private static final V1ConfigMapList CONFIGMAP_DEV_LIST = new V1ConfigMapList()
.addItemsItem(new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("stores").withNamespace("dev")
.withResourceVersion("1").build())
.addToData("application.yaml",
"dummy:\n property:\n string2: \"dev\"\n int2: 1\n bool2: true\n")
.build());
private static final V1SecretList SECRET_LIST = new V1SecretListBuilder()
.addToItems(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("application").withResourceVersion("0")
.withNamespace("default").build())
.addToData("password", "p455w0rd".getBytes()).addToData("username", "user".getBytes()).build())
.addToItems(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("stores").withResourceVersion("0")
.withNamespace("default").build())
.addToData("password", "password-from-stores".getBytes()).addToData("username", "stores".getBytes())
.build())
.addToItems(new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("stores-dev").withResourceVersion("0")
.withNamespace("default").build())
.addToData("password", "password-from-stores-dev".getBytes())
.addToData("username", "stores-dev".getBytes()).build())
.build();
@BeforeAll
public static void before() {
KUBERNETES_PROPERTY_SOURCE_SUPPLIER.add((coreApi, applicationName, namespace, springEnv) -> {
List<MapPropertySource> propertySources = new ArrayList<>();
NormalizedSource defaultSource = new NamedConfigMapNormalizedSource(applicationName, "default", false,
true);
KubernetesClientConfigContext defaultContext = new KubernetesClientConfigContext(coreApi, defaultSource,
"default", springEnv);
NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, true);
KubernetesClientConfigContext devContext = new KubernetesClientConfigContext(coreApi, devSource, "dev",
springEnv);
propertySources.add(new KubernetesClientConfigMapPropertySource(defaultContext));
propertySources.add(new KubernetesClientConfigMapPropertySource(devContext));
return propertySources;
});
KUBERNETES_PROPERTY_SOURCE_SUPPLIER.add((coreApi, applicationName, namespace, springEnv) -> {
List<MapPropertySource> propertySources = new ArrayList<>();
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, "default", false, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, "default",
springEnv);
propertySources.add(new KubernetesClientSecretsPropertySource(context));
return propertySources;
});
}
@Test
public void testApplicationCase() throws ApiException {
CoreV1Api coreApi = mock(CoreV1Api.class);
when(coreApi.listNamespacedConfigMap(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEFAULT_LIST);
when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_LIST);
when(coreApi.listNamespacedConfigMap(eq("dev"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEV_LIST);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default");
Environment environment = environmentRepository.findOne("application", "", "");
assertThat(environment.getPropertySources().size()).isEqualTo(2);
environment.getPropertySources().forEach(propertySource -> {
assertThat(propertySource.getName().equals("configmap.application.default")
|| propertySource.getName().equals("secret.application.default")).isTrue();
if (propertySource.getName().equals("configmap.application.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(3);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a");
}
if (propertySource.getName().equals("secrets.application.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(2);
assertThat(propertySource.getSource().get("username")).isEqualTo("user");
assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd");
}
});
}
@Test
public void testStoresCase() throws ApiException {
CoreV1Api coreApi = mock(CoreV1Api.class);
when(coreApi.listNamespacedConfigMap(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEFAULT_LIST);
when(coreApi.listNamespacedConfigMap(eq("dev"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEV_LIST);
when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_LIST);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default");
Environment environment = environmentRepository.findOne("stores", "", "");
assertThat(environment.getPropertySources().size()).isEqualTo(5);
environment.getPropertySources().forEach(propertySource -> {
assertThat(propertySource.getName().equals("configmap.application.default")
|| propertySource.getName().equals("secret.application.default")
|| propertySource.getName().equals("configmap.stores.default")
|| propertySource.getName().equals("configmap.stores.dev")
|| propertySource.getName().equals("secret.stores.default")).isTrue();
if (propertySource.getName().equals("configmap.application.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(3);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a");
}
if (propertySource.getName().equals("secret.application.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(2);
assertThat(propertySource.getSource().get("username")).isEqualTo("user");
assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd");
}
if (propertySource.getName().equals("configmap.stores.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(3);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a");
}
if (propertySource.getName().equals("configmap.stores.dev")) {
assertThat(propertySource.getSource().size()).isEqualTo(3);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("dev");
}
if (propertySource.getName().equals("secret.stores.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(2);
assertThat(propertySource.getSource().get("username")).isEqualTo("stores");
assertThat(propertySource.getSource().get("password")).isEqualTo("password-from-stores");
}
});
}
@Test
public void testStoresProfileCase() throws ApiException {
CoreV1Api coreApi = mock(CoreV1Api.class);
when(coreApi.listNamespacedConfigMap(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEFAULT_LIST);
when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_LIST);
when(coreApi.listNamespacedConfigMap(eq("dev"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEV_LIST);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default");
Environment environment = environmentRepository.findOne("stores", "dev", "");
assertThat(environment.getPropertySources().size()).isEqualTo(5);
environment.getPropertySources().forEach(propertySource -> {
assertThat(propertySource.getName().equals("configmap.application.default")
|| propertySource.getName().equals("secret.application.default")
|| propertySource.getName().equals("configmap.stores.stores-dev.default")
|| propertySource.getName().equals("configmap.stores.dev")
|| propertySource.getName().equals("secret.stores.stores-dev.default")).isTrue();
if (propertySource.getName().equals("configmap.application.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(3);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a");
}
else if (propertySource.getName().equals("secret.application.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(2);
assertThat(propertySource.getSource().get("username")).isEqualTo("user");
assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd");
}
else if (propertySource.getName().equals("configmap.stores.stores-dev.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(4);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(2);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(false);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("b");
assertThat(propertySource.getSource().get("dummy.property.string1")).isEqualTo("a");
}
else if (propertySource.getName().equals("configmap.stores.dev")) {
assertThat(propertySource.getSource().size()).isEqualTo(3);
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("dev");
}
else if (propertySource.getName().equals("secret.stores.stores-dev.default")) {
assertThat(propertySource.getSource().size()).isEqualTo(2);
assertThat(propertySource.getSource().get("username")).isEqualTo("stores-dev");
assertThat(propertySource.getSource().get("password")).isEqualTo("password-from-stores-dev");
}
else {
Assertions.fail("no match in property source names");
}
});
}
@Test
public void testApplicationPropertiesAnSecretsOverride() throws ApiException {
CoreV1Api coreApi = mock(CoreV1Api.class);
when(coreApi.listNamespacedConfigMap(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEFAULT_LIST);
when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_LIST);
when(coreApi.listNamespacedConfigMap(eq("dev"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEV_LIST);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default");
Environment environment = environmentRepository.findOne("stores-dev", "", "");
environment.getPropertySources().stream()
.filter(propertySource -> propertySource.getName().startsWith("configmap"))
.reduce((first, second) -> second).ifPresent(propertySource -> {
assertThat(propertySource.getName()).isEqualTo("configmap.application.default");
});
environment.getPropertySources().stream()
.filter(propertySource -> propertySource.getName().startsWith("configmap")).findFirst()
.ifPresent(propertySource -> {
assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(2);
assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(false);
assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("b");
});
environment.getPropertySources().stream()
.filter(propertySource -> propertySource.getName().startsWith("secrets")).findFirst()
.ifPresent(propertySource -> {
assertThat(propertySource.getSource().get("username")).isEqualTo("stores-dev");
assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd");
});
}
}

View File

@@ -1,184 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import java.util.Collections;
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.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.openapi.models.V1SecretListBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.config.environment.Environment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests for verifying the behavior of the {@link KubernetesPropertySourceSupplier} beans
* for ConfigMaps and Secrets created by the auto-configuration.
*
* @author Thomas Vitale
*/
class KubernetesPropertySourceSupplierTests {
private static final CoreV1Api coreApi = mock(CoreV1Api.class);
private static final V1ConfigMapList CONFIGMAP_DEFAULT_LIST = new V1ConfigMapList()
.addItemsItem(buildConfigMap("gateway", "tdefault"));
private static final V1ConfigMapList CONFIGMAP_TEAM_A_LIST = new V1ConfigMapList()
.addItemsItem(buildConfigMap("stores", "team-a"));
private static final V1ConfigMapList CONFIGMAP_TEAM_B_LIST = new V1ConfigMapList()
.addItemsItem(buildConfigMap("orders", "team-b"));
private static final V1SecretList SECRET_DEFAULT_LIST = new V1SecretListBuilder()
.addToItems(buildSecret("gateway", "default")).build();
private static final V1SecretList SECRET_TEAM_A_LIST = new V1SecretListBuilder()
.addToItems(buildSecret("stores", "team-a")).build();
private static final V1SecretList SECRET_TEAM_B_LIST = new V1SecretListBuilder()
.addToItems(buildSecret("orders", "team-b")).build();
@BeforeAll
public static void before() throws ApiException {
when(coreApi.listNamespacedConfigMap(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEFAULT_LIST);
when(coreApi.listNamespacedConfigMap(eq("team-a"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_TEAM_A_LIST);
when(coreApi.listNamespacedConfigMap(eq("team-b"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_TEAM_B_LIST);
when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_DEFAULT_LIST);
when(coreApi.listNamespacedSecret(eq("team-a"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_TEAM_A_LIST);
when(coreApi.listNamespacedSecret(eq("team-b"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null),
eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_TEAM_B_LIST);
}
@Test
void whenCurrentAndExtraNamespacesAddedThenAllConfigMapsAreIncluded() {
KubernetesConfigServerProperties kubernetesConfigServerProperties = new KubernetesConfigServerProperties();
kubernetesConfigServerProperties.setConfigMapNamespaces("default,team-a,team-b");
KubernetesPropertySourceSupplier kubernetesPropertySourceSupplier = new KubernetesConfigServerAutoConfiguration()
.configMapPropertySourceSupplier(kubernetesConfigServerProperties);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
Collections.singletonList(kubernetesPropertySourceSupplier), "default");
Environment environmentGateway = environmentRepository.findOne("gateway", "", "");
assertThat(environmentGateway.getPropertySources().size()).isEqualTo(1);
Environment environmentStores = environmentRepository.findOne("stores", "", "");
assertThat(environmentStores.getPropertySources().size()).isEqualTo(1);
Environment environmentOrders = environmentRepository.findOne("orders", "", "");
assertThat(environmentOrders.getPropertySources().size()).isEqualTo(1);
}
@Test
void whenExtraNamespacesAddedThenConfigMapsInCurrentNamespaceAreNotIncluded() {
KubernetesConfigServerProperties kubernetesConfigServerProperties = new KubernetesConfigServerProperties();
kubernetesConfigServerProperties.setConfigMapNamespaces("team-a,team-b");
KubernetesPropertySourceSupplier kubernetesPropertySourceSupplier = new KubernetesConfigServerAutoConfiguration()
.configMapPropertySourceSupplier(kubernetesConfigServerProperties);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
Collections.singletonList(kubernetesPropertySourceSupplier), "default");
Environment environmentGateway = environmentRepository.findOne("gateway", "", "");
assertThat(environmentGateway.getPropertySources().size()).isEqualTo(0);
Environment environmentStores = environmentRepository.findOne("stores", "", "");
assertThat(environmentStores.getPropertySources().size()).isEqualTo(1);
Environment environmentOrders = environmentRepository.findOne("orders", "", "");
assertThat(environmentOrders.getPropertySources().size()).isEqualTo(1);
}
@Test
void whenCurrentAndExtraNamespacesAddedThenAllSecretsAreIncluded() {
KubernetesConfigServerProperties kubernetesConfigServerProperties = new KubernetesConfigServerProperties();
kubernetesConfigServerProperties.setSecretsNamespaces("default,team-a,team-b");
KubernetesPropertySourceSupplier kubernetesPropertySourceSupplier = new KubernetesConfigServerAutoConfiguration()
.secretsPropertySourceSupplier(kubernetesConfigServerProperties);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
Collections.singletonList(kubernetesPropertySourceSupplier), "default");
Environment environmentGateway = environmentRepository.findOne("gateway", "", "");
assertThat(environmentGateway.getPropertySources().size()).isEqualTo(1);
Environment environmentStores = environmentRepository.findOne("stores", "", "");
assertThat(environmentStores.getPropertySources().size()).isEqualTo(1);
Environment environmentOrders = environmentRepository.findOne("orders", "", "");
assertThat(environmentOrders.getPropertySources().size()).isEqualTo(1);
}
@Test
void whenExtraNamespacesAddedThenSecretsInCurrentNamespaceAreNotIncluded() {
KubernetesConfigServerProperties kubernetesConfigServerProperties = new KubernetesConfigServerProperties();
kubernetesConfigServerProperties.setSecretsNamespaces("team-a,team-b");
KubernetesPropertySourceSupplier kubernetesPropertySourceSupplier = new KubernetesConfigServerAutoConfiguration()
.secretsPropertySourceSupplier(kubernetesConfigServerProperties);
KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi,
Collections.singletonList(kubernetesPropertySourceSupplier), "default");
Environment environmentGateway = environmentRepository.findOne("gateway", "", "");
assertThat(environmentGateway.getPropertySources().size()).isEqualTo(0);
Environment environmentStores = environmentRepository.findOne("stores", "", "");
assertThat(environmentStores.getPropertySources().size()).isEqualTo(1);
Environment environmentOrders = environmentRepository.findOne("orders", "", "");
assertThat(environmentOrders.getPropertySources().size()).isEqualTo(1);
}
private static V1ConfigMap buildConfigMap(String name, String namespace) {
return new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(name).withNamespace(namespace).withResourceVersion("1")
.build())
.addToData("application.yaml", "dummy:\n property:\n string: \"" + name + "\"\n").build();
}
private static V1Secret buildSecret(String name, String namespace) {
return new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName(name).withResourceVersion("0").withNamespace(namespace)
.build())
.addToData("password", "p455w0rd".getBytes()).addToData("username", "user".getBytes()).build();
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2013-2022 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.configserver;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import static org.mockito.Mockito.mock;
@Configuration
class MockConfig {
@Bean
@Profile("kubernetesdisabled")
public EnvironmentRepository environmentRepository() {
return mock(EnvironmentRepository.class);
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2013-2021 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.configserver;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.context.annotation.Bean;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* @author Ryan Baxter
*/
public class TestBootstrapConfig {
@Bean
WireMockServer wireMockServer() {
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
WireMock.configureFor(wireMockServer.port());
return wireMockServer;
}
@Bean
ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath(wireMockServer.baseUrl()).build();
apiClient.setDebugging(true);
return apiClient;
}
@Bean
CoreV1Api coreApi(ApiClient apiClient) {
return new CoreV1Api(apiClient);
}
}

View File

@@ -1,2 +0,0 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.kubernetes.configserver.TestBootstrapConfig

View File

@@ -1 +0,0 @@
org.springframework.cloud.kubernetes.configserver.KubernetesConfigServerAutoConfiguration

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<version>3.0.4-SNAPSHOT</version>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-configuration-watcher</artifactId>
<version>3.0.4-SNAPSHOT</version>
<description>Spring Cloud parent pom, managing plugins and dependencies for Spring
Cloud projects</description>
<url>https://cloud.spring.io/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher</url>
<inceptionYear>2017</inceptionYear>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>sgibb</id>
<name>Spencer Gibb</name>
<email>sgibb at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>mgrzejszczak</id>
<name>Marcin Grzejszczak</name>
<email>mgrzejszczak at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>rbaxter</id>
<name>Ryan Baxter</name>
<email>rbaxter at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>omaciaszeksharma</id>
<name>Olga Maciaszek-Sharma</name>
<email>omaciaszeksharma at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher</developerConnection>
<url>https://github.com/spring-cloud/spring-cloud-kubernetes/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher</url>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-all</artifactId>
<version>3.0.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
<version>4.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
<version>4.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,158 +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-controllers</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-configuration-watcher</artifactId>
<properties>
<docker.registry.organization>springcloud</docker.registry.organization>
<plexus-archiver.version>4.1.0</plexus-archiver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-test</artifactId>
<scope>test</scope>
<!-- In favor of mockito-inline -->
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>${env.IMAGE}</name>
</image>
<goal>build-image</goal>
</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>
</plugins>
</build>
<profiles>
<profile>
<id>dockerpush</id>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.12</version>
<configuration>
<repository>${docker.registry.organization}/${artifactId}</repository>
<tag>${project.version}</tag>
<username>${env.DOCKER_HUB_USERNAME}</username>
<password>${env.DOCKER_HUB_PASSWORD}</password>
<build>
<noCache>true</noCache>
</build>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>
<version>${plexus-archiver.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>imagename</id>
<activation>
<property>
<name>!env.IMAGE</name>
</property>
</activation>
<properties>
<env.IMAGE>springcloud/${project.artifactId}:${project.version}</env.IMAGE>
</properties>
</profile>
</profiles>
</project>

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2013-2022 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.configuration.watcher;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import reactor.core.publisher.Mono;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
final class BusEventBasedConfigMapWatcherChangeDetector extends ConfigMapWatcherChangeDetector {
private final BusRefreshTrigger busRefreshTrigger;
BusEventBasedConfigMapWatcherChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
KubernetesClientConfigMapPropertySourceLocator propertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadPoolTaskExecutor, BusRefreshTrigger busRefreshTrigger) {
super(coreV1Api, environment, properties, strategy, propertySourceLocator, kubernetesNamespaceProvider,
k8SConfigurationProperties, threadPoolTaskExecutor);
this.busRefreshTrigger = busRefreshTrigger;
}
@Override
public Mono<Void> triggerRefresh(KubernetesObject configMap, String appName) {
return busRefreshTrigger.triggerRefresh(configMap, appName);
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2013-2022 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.configuration.watcher;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import reactor.core.publisher.Mono;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
final class BusEventBasedSecretsWatcherChangeDetector extends SecretsWatcherChangeDetector {
private final BusRefreshTrigger busRefreshTrigger;
BusEventBasedSecretsWatcherChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
KubernetesClientSecretsPropertySourceLocator propertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadPoolTaskExecutor, BusRefreshTrigger busRefreshTrigger) {
super(coreV1Api, environment, properties, strategy, propertySourceLocator, kubernetesNamespaceProvider,
k8SConfigurationProperties, threadPoolTaskExecutor);
this.busRefreshTrigger = busRefreshTrigger;
}
@Override
public Mono<Void> triggerRefresh(KubernetesObject secret, String appName) {
return busRefreshTrigger.triggerRefresh(secret, appName);
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2013-2022 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.configuration.watcher;
import io.kubernetes.client.common.KubernetesObject;
import reactor.core.publisher.Mono;
import org.springframework.cloud.bus.event.PathDestinationFactory;
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
/**
* An event publisher for an 'event bus' type of application.
*
* @author wind57
*/
final class BusRefreshTrigger implements RefreshTrigger {
private final ApplicationEventPublisher applicationEventPublisher;
private final String busId;
BusRefreshTrigger(ApplicationEventPublisher applicationEventPublisher, String busId) {
this.applicationEventPublisher = applicationEventPublisher;
this.busId = busId;
}
@Override
public Mono<Void> triggerRefresh(KubernetesObject configMap, String appName) {
applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(configMap, busId,
new PathDestinationFactory().getDestination(appName)));
return Mono.empty();
}
}

View File

@@ -1,67 +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.configuration.watcher;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.client.config.reload.KubernetesClientEventBasedConfigMapChangeDetector;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.CONFIG_MAP_APPS_ANNOTATION;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.CONFIG_MAP_LABEL;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
abstract sealed class ConfigMapWatcherChangeDetector extends KubernetesClientEventBasedConfigMapChangeDetector
implements
RefreshTrigger permits BusEventBasedConfigMapWatcherChangeDetector, HttpBasedConfigMapWatchChangeDetector {
private final ScheduledExecutorService executorService;
private final long refreshDelay;
ConfigMapWatcherChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
KubernetesClientConfigMapPropertySourceLocator propertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadPoolTaskExecutor) {
super(coreV1Api, environment, properties, strategy, propertySourceLocator, kubernetesNamespaceProvider);
this.executorService = Executors.newScheduledThreadPool(k8SConfigurationProperties.getThreadPoolSize(),
threadPoolTaskExecutor);
this.refreshDelay = k8SConfigurationProperties.getRefreshDelay().toMillis();
}
@Override
protected final void onEvent(KubernetesObject configMap) {
// this::refreshTrigger is coming from BusEventBasedConfigMapWatcherChangeDetector
WatcherUtil.onEvent(configMap, CONFIG_MAP_LABEL, CONFIG_MAP_APPS_ANNOTATION, refreshDelay, executorService,
"config-map", this::triggerRefresh);
}
}

View File

@@ -1,45 +0,0 @@
/*
* 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.configuration.watcher;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
/**
* @author wind57
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.reload.enabled", havingValue = "false", matchIfMissing = true)
class ConfigUpdateStrategyAutoConfiguration {
private static final LogAccessor LOG = new LogAccessor(
LogFactory.getLog(ConfigUpdateStrategyAutoConfiguration.class));
@Bean
@ConditionalOnMissingBean
ConfigurationUpdateStrategy noopConfigurationUpdateStrategy() {
LOG.debug(() -> "creating NOOP strategy because reload is disabled");
return ConfigurationUpdateStrategy.NOOP;
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2013-2019 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.configuration.watcher;
import org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
import org.springframework.context.annotation.Configuration;
/**
* @author Ryan Baxter
*/
@Configuration(proxyBeanMethods = false)
@SpringBootApplication(
exclude = { ContextFunctionCatalogAutoConfiguration.class, RabbitHealthContributorAutoConfiguration.class })
public class ConfigurationWatcherApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigurationWatcherApplication.class).run(args);
}
}

View File

@@ -1,181 +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.configuration.watcher;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ConfigurationWatcherConfigurationProperties.class })
@Import({ ConfigurationWatcherAutoConfiguration.RefreshTriggerConfiguration.class })
public class ConfigurationWatcherAutoConfiguration {
private static final String AMQP = "bus-amqp";
private static final String KAFKA = "bus-kafka";
@Bean
@ConditionalOnMissingBean
public WebClient webClient(WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}
@Bean
@ConditionalOnMissingBean(ConfigMapWatcherChangeDetector.class)
@ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class)
public ConfigMapWatcherChangeDetector httpBasedConfigMapWatchChangeDetector(AbstractEnvironment environment,
CoreV1Api coreV1Api, KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
KubernetesNamespaceProvider namespaceProvider, ThreadPoolTaskExecutor threadFactory,
HttpRefreshTrigger httpRefreshTrigger) {
return new HttpBasedConfigMapWatchChangeDetector(coreV1Api, environment, properties, strategy,
configMapPropertySourceLocator, namespaceProvider, k8SConfigurationProperties, threadFactory,
httpRefreshTrigger);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class)
public SecretsWatcherChangeDetector httpBasedSecretsWatchChangeDetector(AbstractEnvironment environment,
CoreV1Api coreV1Api, KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator,
KubernetesNamespaceProvider namespaceProvider, ConfigReloadProperties properties,
ConfigurationUpdateStrategy strategy,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadFactory, HttpRefreshTrigger httpRefreshTrigger) {
return new HttpBasedSecretsWatchChangeDetector(coreV1Api, environment, properties, strategy,
secretsPropertySourceLocator, namespaceProvider, k8SConfigurationProperties, threadFactory,
httpRefreshTrigger);
}
@Configuration
@Profile(AMQP)
@Import({ ContextFunctionCatalogAutoConfiguration.class, RabbitHealthContributorAutoConfiguration.class,
RefreshTriggerConfiguration.class })
static class BusRabbitConfiguration {
@Bean
@ConditionalOnMissingBean(ConfigMapWatcherChangeDetector.class)
@ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class)
public ConfigMapWatcherChangeDetector busConfigMapChangeWatcher(AbstractEnvironment environment,
CoreV1Api coreV1Api, KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider, ConfigReloadProperties properties,
ConfigurationUpdateStrategy strategy,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadFactory, BusRefreshTrigger busRefreshTrigger) {
return new BusEventBasedConfigMapWatcherChangeDetector(coreV1Api, environment, properties, strategy,
configMapPropertySourceLocator, kubernetesNamespaceProvider, k8SConfigurationProperties,
threadFactory, busRefreshTrigger);
}
@Bean
@ConditionalOnMissingBean(SecretsWatcherChangeDetector.class)
@ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class)
public SecretsWatcherChangeDetector busSecretsChangeWatcher(AbstractEnvironment environment,
CoreV1Api coreV1Api, KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator,
ConfigReloadProperties properties, KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationUpdateStrategy strategy,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadFactory, BusRefreshTrigger busRefreshTrigger) {
return new BusEventBasedSecretsWatcherChangeDetector(coreV1Api, environment, properties, strategy,
secretsPropertySourceLocator, kubernetesNamespaceProvider, k8SConfigurationProperties,
threadFactory, busRefreshTrigger);
}
}
@Configuration
@Profile(KAFKA)
@Import({ ContextFunctionCatalogAutoConfiguration.class, RefreshTriggerConfiguration.class })
static class BusKafkaConfiguration {
@Bean
@ConditionalOnMissingBean(ConfigMapWatcherChangeDetector.class)
@ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class)
public ConfigMapWatcherChangeDetector busConfigMapChangeWatcher(AbstractEnvironment environment,
CoreV1Api coreV1Api, KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator,
ConfigReloadProperties properties, KubernetesNamespaceProvider namespaceProvider,
ConfigurationUpdateStrategy strategy,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadFactory, BusRefreshTrigger busRefreshTrigger) {
return new BusEventBasedConfigMapWatcherChangeDetector(coreV1Api, environment, properties, strategy,
configMapPropertySourceLocator, namespaceProvider, k8SConfigurationProperties, threadFactory,
busRefreshTrigger);
}
@Bean
@ConditionalOnMissingBean(SecretsWatcherChangeDetector.class)
@ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class)
public SecretsWatcherChangeDetector busSecretsChangeWatcher(AbstractEnvironment environment,
CoreV1Api coreV1Api, KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadFactory, KubernetesNamespaceProvider namespaceProvider,
BusRefreshTrigger busRefreshTrigger) {
return new BusEventBasedSecretsWatcherChangeDetector(coreV1Api, environment, properties, strategy,
secretsPropertySourceLocator, namespaceProvider, k8SConfigurationProperties, threadFactory,
busRefreshTrigger);
}
}
@AutoConfiguration
static class RefreshTriggerConfiguration {
@Bean
@ConditionalOnMissingBean
@Profile({ AMQP, KAFKA })
public BusRefreshTrigger busRefreshTrigger(ApplicationEventPublisher applicationEventPublisher,
BusProperties busProperties) {
return new BusRefreshTrigger(applicationEventPublisher, busProperties.getId());
}
@Bean
@ConditionalOnMissingBean
public HttpRefreshTrigger httpRefreshTrigger(KubernetesInformerReactiveDiscoveryClient client,
ConfigurationWatcherConfigurationProperties properties, WebClient webClient) {
return new HttpRefreshTrigger(client, properties, webClient);
}
}
}

View File

@@ -1,108 +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.configuration.watcher;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
/**
* @author Ryan Baxter
*/
@ConfigurationProperties("spring.cloud.kubernetes.configuration.watcher")
public class ConfigurationWatcherConfigurationProperties {
/**
* label to enable refresh/restart when using configmaps.
*/
public static final String CONFIG_MAP_LABEL = "spring.cloud.kubernetes.config";
/**
* label to enable refresh/restart when using secrets.
*/
public static final String SECRET_LABEL = "spring.cloud.kubernetes.secret";
/**
* annotation name to enable refresh/restart for specific apps when using configmaps.
*/
public static final String CONFIG_MAP_APPS_ANNOTATION = "spring.cloud.kubernetes.configmap.apps";
/**
* annotation name to enable refresh/restart for specific apps when using secrets.
*/
public static final String SECRET_APPS_ANNOTATION = "spring.cloud.kubernetes.secret.apps";
/**
* Annotation key for actuator port and path.
*/
public static final String ANNOTATION_KEY = "boot.spring.io/actuator";
/**
* Amount of time to delay the posting of the event to allow the app volume to update
* data.
*/
@DurationUnit(ChronoUnit.MILLIS)
private Duration refreshDelay = Duration.ofMillis(120000);
private int threadPoolSize = 1;
private String actuatorPath = "/actuator";
private Integer actuatorPort = -1;
public String getActuatorPath() {
return actuatorPath;
}
public void setActuatorPath(String actuatorPath) {
String normalizedPath = actuatorPath;
if (!normalizedPath.startsWith("/")) {
normalizedPath = "/" + normalizedPath;
}
if (normalizedPath.endsWith("/")) {
normalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);
}
this.actuatorPath = normalizedPath;
}
public Integer getActuatorPort() {
return actuatorPort;
}
public void setActuatorPort(Integer actuatorPort) {
this.actuatorPort = actuatorPort;
}
public Duration getRefreshDelay() {
return refreshDelay;
}
public void setRefreshDelay(Duration refreshDelay) {
this.refreshDelay = refreshDelay;
}
public int getThreadPoolSize() {
return threadPoolSize;
}
public void setThreadPoolSize(int threadPoolSize) {
this.threadPoolSize = threadPoolSize;
}
}

View File

@@ -1,55 +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.configuration.watcher;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import reactor.core.publisher.Mono;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
final class HttpBasedConfigMapWatchChangeDetector extends ConfigMapWatcherChangeDetector {
private final HttpRefreshTrigger httpRefreshTrigger;
HttpBasedConfigMapWatchChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
KubernetesClientConfigMapPropertySourceLocator propertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadPoolTaskExecutor, HttpRefreshTrigger httpRefreshTrigger) {
super(coreV1Api, environment, properties, strategy, propertySourceLocator, kubernetesNamespaceProvider,
k8SConfigurationProperties, threadPoolTaskExecutor);
this.httpRefreshTrigger = httpRefreshTrigger;
}
@Override
public Mono<Void> triggerRefresh(KubernetesObject configMap, String appName) {
return httpRefreshTrigger.triggerRefresh(configMap, appName);
}
}

View File

@@ -1,55 +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.configuration.watcher;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import reactor.core.publisher.Mono;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
final class HttpBasedSecretsWatchChangeDetector extends SecretsWatcherChangeDetector {
private final HttpRefreshTrigger httpRefreshTrigger;
HttpBasedSecretsWatchChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
KubernetesClientSecretsPropertySourceLocator propertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadPoolTaskExecutor, HttpRefreshTrigger httpRefreshTrigger) {
super(coreV1Api, environment, properties, strategy, propertySourceLocator, kubernetesNamespaceProvider,
k8SConfigurationProperties, threadPoolTaskExecutor);
this.httpRefreshTrigger = httpRefreshTrigger;
}
@Override
public Mono<Void> triggerRefresh(KubernetesObject secret, String appName) {
return httpRefreshTrigger.triggerRefresh(secret, appName);
}
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright 2013-2022 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.configuration.watcher;
import java.net.URI;
import java.util.function.Consumer;
import io.kubernetes.client.common.KubernetesObject;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.client.config.reload.KubernetesClientEventBasedSecretsChangeDetector;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.core.log.LogAccessor;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @author wind57
*/
final class HttpRefreshTrigger implements RefreshTrigger {
private static final LogAccessor LOG = new LogAccessor(
LogFactory.getLog(KubernetesClientEventBasedSecretsChangeDetector.class));
private final KubernetesInformerReactiveDiscoveryClient kubernetesReactiveDiscoveryClient;
private final ConfigurationWatcherConfigurationProperties k8SConfigurationProperties;
private final WebClient webClient;
HttpRefreshTrigger(KubernetesInformerReactiveDiscoveryClient kubernetesReactiveDiscoveryClient,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties, WebClient webClient) {
this.kubernetesReactiveDiscoveryClient = kubernetesReactiveDiscoveryClient;
this.k8SConfigurationProperties = k8SConfigurationProperties;
this.webClient = webClient;
}
@Override
public Mono<Void> triggerRefresh(KubernetesObject kubernetesObject, String appName) {
return kubernetesReactiveDiscoveryClient.getInstances(appName).flatMap(si -> {
URI actuatorUri = getActuatorUri(si, k8SConfigurationProperties.getActuatorPath(),
k8SConfigurationProperties.getActuatorPort());
LOG.debug(() -> "Sending refresh request for " + appName + " to URI " + actuatorUri);
return webClient.post().uri(actuatorUri).retrieve().toBodilessEntity()
.doOnSuccess(onSuccess(appName, actuatorUri)).doOnError(onError(appName));
}).then();
}
private Consumer<ResponseEntity<Void>> onSuccess(String name, URI actuatorUri) {
return re -> LOG.debug(() -> "Refresh sent to " + name + " at URI address " + actuatorUri + " returned a "
+ re.getStatusCode());
}
private Consumer<Throwable> onError(String name) {
return t -> LOG.warn(t, () -> "Refresh sent to " + name + " failed");
}
private URI getActuatorUri(ServiceInstance si, String actuatorPath, int actuatorPort) {
String metadataUri = si.getMetadata().getOrDefault(ConfigurationWatcherConfigurationProperties.ANNOTATION_KEY,
"");
LOG.debug(() -> "Metadata actuator uri is: " + metadataUri);
UriComponentsBuilder actuatorUriBuilder = UriComponentsBuilder.newInstance().scheme(si.getScheme())
.host(si.getHost());
if (StringUtils.hasText(metadataUri)) {
LOG.debug(() -> "Found actuator URI in service instance metadata");
setActuatorUriFromAnnotation(actuatorUriBuilder, metadataUri);
}
else {
int port = actuatorPort < 0 ? si.getPort() : actuatorPort;
actuatorUriBuilder = actuatorUriBuilder.path(actuatorPath + "/refresh").port(port);
}
return actuatorUriBuilder.build().toUri();
}
private void setActuatorUriFromAnnotation(UriComponentsBuilder actuatorUriBuilder, String metadataUri) {
URI annotationUri = URI.create(metadataUri);
actuatorUriBuilder.path(annotationUri.getPath() + "/refresh");
// The URI may not contain a host so if that is the case the port in the URI will
// be -1. The authority of the URI will be :<port> for example :9090, we just need
// the 9090 in this case
if (annotationUri.getPort() < 0) {
if (annotationUri.getAuthority() != null) {
actuatorUriBuilder.port(annotationUri.getAuthority().replaceFirst(":", ""));
}
}
else {
actuatorUriBuilder.port(annotationUri.getPort());
}
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2013-2022 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.configuration.watcher;
import io.kubernetes.client.common.KubernetesObject;
import reactor.core.publisher.Mono;
/**
* Defines the refresh trigger contract.
*
* @author wind57
*/
sealed interface RefreshTrigger permits BusRefreshTrigger, ConfigMapWatcherChangeDetector, HttpRefreshTrigger, SecretsWatcherChangeDetector {
/**
* @param kubernetesObject either a config-map or secret at the moment.
* @param appName which is not necessarily equal to
* kubernetesObject.getMetadata().getName()
*/
Mono<Void> triggerRefresh(KubernetesObject kubernetesObject, String appName);
}

View File

@@ -1,66 +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.configuration.watcher;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
import org.springframework.cloud.kubernetes.client.config.reload.KubernetesClientEventBasedSecretsChangeDetector;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_APPS_ANNOTATION;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_LABEL;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
abstract sealed class SecretsWatcherChangeDetector extends KubernetesClientEventBasedSecretsChangeDetector implements
RefreshTrigger permits BusEventBasedSecretsWatcherChangeDetector, HttpBasedSecretsWatchChangeDetector {
private final ScheduledExecutorService executorService;
private final long refreshDelay;
SecretsWatcherChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment,
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
KubernetesClientSecretsPropertySourceLocator propertySourceLocator,
KubernetesNamespaceProvider kubernetesNamespaceProvider,
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
ThreadPoolTaskExecutor threadPoolTaskExecutor) {
super(coreV1Api, environment, properties, strategy, propertySourceLocator, kubernetesNamespaceProvider);
this.executorService = Executors.newScheduledThreadPool(k8SConfigurationProperties.getThreadPoolSize(),
threadPoolTaskExecutor);
this.refreshDelay = k8SConfigurationProperties.getRefreshDelay().toMillis();
}
@Override
protected final void onEvent(KubernetesObject secret) {
// this::refreshTrigger is coming from BusEventBasedSecretsWatcherChangeDetector
WatcherUtil.onEvent(secret, SECRET_LABEL, SECRET_APPS_ANNOTATION, refreshDelay, executorService, "secret",
this::triggerRefresh);
}
}

View File

@@ -1,129 +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.configuration.watcher;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogAccessor;
/**
* A common place where 'onEvent' code delegates to.
*
* @author wind57
*/
final class WatcherUtil {
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(WatcherUtil.class));
private WatcherUtil() {
}
static void onEvent(KubernetesObject kubernetesObject, String label, String annotationName, long refreshDelay,
ScheduledExecutorService executorService, String type,
BiFunction<KubernetesObject, String, Mono<Void>> triggerRefresh) {
String name = kubernetesObject.getMetadata().getName();
boolean isSpringCloudKubernetes = isSpringCloudKubernetes(kubernetesObject, label);
if (isSpringCloudKubernetes) {
Set<String> apps = apps(kubernetesObject, annotationName);
if (apps.isEmpty()) {
apps.add(name);
}
LOG.info(() -> "will schedule remote refresh based on apps : " + apps);
apps.forEach(appName -> schedule(type, appName, refreshDelay, executorService, triggerRefresh,
kubernetesObject));
}
else {
LOG.debug(() -> "Not publishing event." + type + ": " + name + " does not contain the label " + label);
}
}
static boolean isSpringCloudKubernetes(KubernetesObject kubernetesObject, String label) {
if (kubernetesObject.getMetadata() == null) {
return false;
}
return Boolean.parseBoolean(labels(kubernetesObject).getOrDefault(label, "false"));
}
static Set<String> apps(KubernetesObject kubernetesObject, String annotationName) {
// mutable on purpose
Set<String> apps = new HashSet<>(1);
Map<String, String> annotations = annotations(kubernetesObject);
if (annotations.isEmpty()) {
LOG.debug(() -> annotationName + " not present (empty data)");
return apps;
}
String appsValue = annotations.get(annotationName);
if (appsValue == null) {
LOG.debug(() -> annotationName + " not present (missing in annotations)");
return apps;
}
if (appsValue.isBlank()) {
LOG.debug(() -> appsValue + " not present (blanks only)");
return apps;
}
return Arrays.stream(appsValue.split(",")).map(String::trim).collect(Collectors.toSet());
}
static Map<String, String> labels(KubernetesObject kubernetesObject) {
return Optional.ofNullable(kubernetesObject.getMetadata()).map(V1ObjectMeta::getLabels).orElse(Map.of());
}
static Map<String, String> annotations(KubernetesObject kubernetesObject) {
return Optional.ofNullable(kubernetesObject.getMetadata()).map(V1ObjectMeta::getAnnotations).orElse(Map.of());
}
private static void schedule(String type, String appName, long refreshDelay,
ScheduledExecutorService executorService, BiFunction<KubernetesObject, String, Mono<Void>> triggerRefresh,
KubernetesObject kubernetesObject) {
LOG.debug(() -> "Scheduling remote refresh event to be published for " + type + ": with appName : " + appName
+ " to be published in " + refreshDelay + " milliseconds");
executorService.schedule(() -> {
try {
triggerRefresh.apply(kubernetesObject, appName).subscribe();
}
catch (Throwable t) {
LOG.warn(t, "Error when refreshing appName " + appName);
}
}, refreshDelay, TimeUnit.MILLISECONDS);
}
}

View File

@@ -1,2 +0,0 @@
org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherAutoConfiguration
org.springframework.cloud.kubernetes.configuration.watcher.ConfigUpdateStrategyAutoConfiguration

View File

@@ -1,11 +0,0 @@
spring:
cloud:
bus:
enabled: true
stream:
default-binder: rabbit
management:
health:
rabbit:
enabled: true

View File

@@ -1,6 +0,0 @@
spring:
cloud:
bus:
enabled: true
stream:
default-binder: kafka

View File

@@ -1,23 +0,0 @@
spring:
config:
import: "kubernetes:"
application:
name: spring-cloud-kubernetes-configuration-watcher
cloud:
kubernetes:
reload:
enabled: true
monitoring-secrets: true
strategy: shutdown
bus:
enabled: false
management:
health:
rabbit:
enabled: false
endpoint:
health:
probes:
enabled: true
server:
port: 8888

View File

@@ -1,98 +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.configuration.watcher;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider.NAMESPACE_PROPERTY;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
@ExtendWith(MockitoExtension.class)
class BusEventBasedConfigMapWatcherChangeDetectorTests {
private static final ConfigurationUpdateStrategy UPDATE_STRATEGY = new ConfigurationUpdateStrategy("strategy",
() -> {
});
@Mock
private CoreV1Api coreV1Api;
@Mock
private KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator;
@Mock
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Mock
private ApplicationEventPublisher applicationEventPublisher;
private BusEventBasedConfigMapWatcherChangeDetector changeDetector;
private BusProperties busProperties;
@BeforeEach
void setup() {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(NAMESPACE_PROPERTY, "default");
ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
busProperties = new BusProperties();
changeDetector = new BusEventBasedConfigMapWatcherChangeDetector(coreV1Api, mockEnvironment,
ConfigReloadProperties.DEFAULT, UPDATE_STRATEGY, configMapPropertySourceLocator,
new KubernetesNamespaceProvider(mockEnvironment), configurationWatcherConfigurationProperties,
threadPoolTaskExecutor, new BusRefreshTrigger(applicationEventPublisher, busProperties.getId()));
}
@Test
void triggerRefreshWithConfigMap() {
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
V1ConfigMap configMap = new V1ConfigMap();
configMap.setMetadata(objectMeta);
changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName());
ArgumentCaptor<RefreshRemoteApplicationEvent> argumentCaptor = ArgumentCaptor
.forClass(RefreshRemoteApplicationEvent.class);
verify(applicationEventPublisher).publishEvent(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getSource()).isEqualTo(configMap);
assertThat(argumentCaptor.getValue().getOriginService()).isEqualTo(busProperties.getId());
assertThat(argumentCaptor.getValue().getDestinationService()).isEqualTo("foo:**");
}
}

View File

@@ -1,98 +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.configuration.watcher;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Secret;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider.NAMESPACE_PROPERTY;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
@ExtendWith(MockitoExtension.class)
class BusEventBasedSecretsWatcherChangeDetectorTests {
private static final ConfigurationUpdateStrategy UPDATE_STRATEGY = new ConfigurationUpdateStrategy("strategy",
() -> {
});
@Mock
private CoreV1Api coreV1Api;
@Mock
private KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator;
@Mock
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Mock
private ApplicationEventPublisher applicationEventPublisher;
private BusEventBasedSecretsWatcherChangeDetector changeDetector;
private BusProperties busProperties;
@BeforeEach
void setup() {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(NAMESPACE_PROPERTY, "default");
ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
busProperties = new BusProperties();
changeDetector = new BusEventBasedSecretsWatcherChangeDetector(coreV1Api, mockEnvironment,
ConfigReloadProperties.DEFAULT, UPDATE_STRATEGY, secretsPropertySourceLocator,
new KubernetesNamespaceProvider(mockEnvironment), configurationWatcherConfigurationProperties,
threadPoolTaskExecutor, new BusRefreshTrigger(applicationEventPublisher, busProperties.getId()));
}
@Test
void triggerRefreshWithSecret() {
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
V1Secret secret = new V1Secret();
secret.setMetadata(objectMeta);
changeDetector.triggerRefresh(secret, secret.getMetadata().getName());
ArgumentCaptor<RefreshRemoteApplicationEvent> argumentCaptor = ArgumentCaptor
.forClass(RefreshRemoteApplicationEvent.class);
verify(applicationEventPublisher).publishEvent(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getSource()).isEqualTo(secret);
assertThat(argumentCaptor.getValue().getOriginService()).isEqualTo(busProperties.getId());
assertThat(argumentCaptor.getValue().getDestinationService()).isEqualTo("foo:**");
}
}

View File

@@ -1,37 +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.configuration.watcher;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
class ConfigurationWatcherConfigurationPropertiesTests {
@Test
void setActuatorPath() {
ConfigurationWatcherConfigurationProperties properties = new ConfigurationWatcherConfigurationProperties();
properties.setActuatorPath("foo");
assertThat(properties.getActuatorPath()).isEqualTo("/foo");
properties.setActuatorPath("/foo/bar/");
assertThat(properties.getActuatorPath()).isEqualTo("/foo/bar");
}
}

View File

@@ -1,200 +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.configuration.watcher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.reactive.function.client.WebClient;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider.NAMESPACE_PROPERTY;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
@ExtendWith(MockitoExtension.class)
class HttpBasedConfigMapWatchChangeDetectorTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
private static final WireMockServer WIRE_MOCK_SERVER = new WireMockServer(
WireMockConfiguration.options().dynamicPort());
@Mock
private CoreV1Api coreV1Api;
@Mock
private KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator;
@Mock
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Mock
private KubernetesInformerReactiveDiscoveryClient reactiveDiscoveryClient;
private HttpBasedConfigMapWatchChangeDetector changeDetector;
private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties;
@BeforeAll
static void beforeAll() {
WIRE_MOCK_SERVER.start();
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::createApiClientForInformerClient)
.thenReturn(new ClientBuilder().setBasePath(WIRE_MOCK_SERVER.baseUrl()).build());
clientUtilsMock
.when(() -> KubernetesClientUtils.getApplicationNamespace(Mockito.any(), Mockito.any(), Mockito.any()))
.thenReturn("default");
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
@BeforeEach
void setup() {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(NAMESPACE_PROPERTY, "default");
configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
WebClient webClient = WebClient.builder().build();
ConfigurationUpdateStrategy strategy = new ConfigurationUpdateStrategy("refresh", () -> {
});
changeDetector = new HttpBasedConfigMapWatchChangeDetector(coreV1Api, mockEnvironment,
ConfigReloadProperties.DEFAULT, strategy, configMapPropertySourceLocator,
new KubernetesNamespaceProvider(mockEnvironment), configurationWatcherConfigurationProperties,
threadPoolTaskExecutor, new HttpRefreshTrigger(reactiveDiscoveryClient,
configurationWatcherConfigurationProperties, webClient));
}
@Test
void triggerConfigMapRefresh() {
stubReactiveCall();
V1ConfigMap configMap = new V1ConfigMap();
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
configMap.setMetadata(objectMeta);
WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port());
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh"))
.willReturn(WireMock.aResponse().withStatus(200)));
StepVerifier.create(changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName()))
.verifyComplete();
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh")));
}
@Test
void triggerConfigMapRefreshWithPropertiesBasedActuatorPath() {
stubReactiveCall();
configurationWatcherConfigurationProperties.setActuatorPath("/my/custom/actuator");
V1ConfigMap configMap = new V1ConfigMap();
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
configMap.setMetadata(objectMeta);
WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port());
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
.willReturn(WireMock.aResponse().withStatus(200)));
StepVerifier.create(changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName()))
.verifyComplete();
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh")));
}
@Test
void triggerConfigMapRefreshWithAnnotationActuatorPath() {
int port = WIRE_MOCK_SERVER.port();
WireMock.configureFor("localhost", port);
Map<String, String> metadata = new HashMap<>();
metadata.put(ConfigurationWatcherConfigurationProperties.ANNOTATION_KEY,
"http://:" + port + "/my/custom/actuator");
V1EndpointAddress fooEndpointAddress = new V1EndpointAddress();
fooEndpointAddress.setIp("127.0.0.1");
fooEndpointAddress.setHostname("localhost");
CoreV1EndpointPort fooEndpointPort = new CoreV1EndpointPort();
fooEndpointPort.setPort(port);
List<ServiceInstance> instances = new ArrayList<>();
DefaultKubernetesServiceInstance fooServiceInstance = new DefaultKubernetesServiceInstance("foo", "foo",
fooEndpointAddress.getIp(), fooEndpointPort.getPort(), metadata, false);
instances.add(fooServiceInstance);
when(reactiveDiscoveryClient.getInstances(eq("foo"))).thenReturn(Flux.fromIterable(instances));
V1ConfigMap configMap = new V1ConfigMap();
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
configMap.setMetadata(objectMeta);
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
.willReturn(WireMock.aResponse().withStatus(200)));
StepVerifier.create(changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName()))
.verifyComplete();
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh")));
}
private void stubReactiveCall() {
V1EndpointAddress fooEndpointAddress = new V1EndpointAddress();
fooEndpointAddress.setIp("127.0.0.1");
fooEndpointAddress.setHostname("localhost");
CoreV1EndpointPort fooEndpointPort = new CoreV1EndpointPort();
fooEndpointPort.setPort(WIRE_MOCK_SERVER.port());
List<ServiceInstance> instances = new ArrayList<>();
DefaultKubernetesServiceInstance fooServiceInstance = new DefaultKubernetesServiceInstance("foo", "foo",
fooEndpointAddress.getIp(), fooEndpointPort.getPort(), new HashMap<>(), false);
instances.add(fooServiceInstance);
when(reactiveDiscoveryClient.getInstances(eq("foo"))).thenReturn(Flux.fromIterable(instances));
}
}

View File

@@ -1,190 +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.configuration.watcher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
import io.kubernetes.client.openapi.models.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.reactive.function.client.WebClient;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider.NAMESPACE_PROPERTY;
/**
* @author Ryan Baxter
* @author Kris Iyer
*/
@ExtendWith(MockitoExtension.class)
class HttpBasedSecretsWatchChangeDetectorTests {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
private static final WireMockServer WIRE_MOCK_SERVER = new WireMockServer(
WireMockConfiguration.options().dynamicPort());
@Mock
private CoreV1Api coreV1Api;
@Mock
private ConfigurationUpdateStrategy updateStrategy;
@Mock
private KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator;
@Mock
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Mock
private KubernetesInformerReactiveDiscoveryClient reactiveDiscoveryClient;
private HttpBasedSecretsWatchChangeDetector changeDetector;
private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties;
@BeforeEach
void setup() {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(NAMESPACE_PROPERTY, "default");
configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
WebClient webClient = WebClient.builder().build();
changeDetector = new HttpBasedSecretsWatchChangeDetector(coreV1Api, mockEnvironment,
ConfigReloadProperties.DEFAULT, updateStrategy, secretsPropertySourceLocator,
new KubernetesNamespaceProvider(mockEnvironment), configurationWatcherConfigurationProperties,
threadPoolTaskExecutor, new HttpRefreshTrigger(reactiveDiscoveryClient,
configurationWatcherConfigurationProperties, webClient));
}
@BeforeAll
static void beforeAll() {
WIRE_MOCK_SERVER.start();
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::createApiClientForInformerClient)
.thenReturn(new ClientBuilder().setBasePath(WIRE_MOCK_SERVER.baseUrl()).build());
clientUtilsMock
.when(() -> KubernetesClientUtils.getApplicationNamespace(Mockito.any(), Mockito.any(), Mockito.any()))
.thenReturn("default");
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
@Test
void triggerSecretRefresh() {
stubReactiveCall();
V1Secret secret = new V1Secret();
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
secret.setMetadata(objectMeta);
WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port());
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh"))
.willReturn(WireMock.aResponse().withStatus(200)));
StepVerifier.create(changeDetector.triggerRefresh(secret, secret.getMetadata().getName())).verifyComplete();
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh")));
}
@Test
void triggerSecretRefreshWithPropertiesBasedActuatorPath() {
stubReactiveCall();
configurationWatcherConfigurationProperties.setActuatorPath("/my/custom/actuator");
V1Secret secret = new V1Secret();
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
secret.setMetadata(objectMeta);
WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port());
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
.willReturn(WireMock.aResponse().withStatus(200)));
StepVerifier.create(changeDetector.triggerRefresh(secret, secret.getMetadata().getName())).verifyComplete();
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh")));
}
@Test
void triggerSecretRefreshWithAnnotationActuatorPath() {
WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port());
Map<String, String> metadata = new HashMap<>();
metadata.put(ConfigurationWatcherConfigurationProperties.ANNOTATION_KEY,
"http://:" + WIRE_MOCK_SERVER.port() + "/my/custom/actuator");
V1EndpointAddress fooEndpointAddress = new V1EndpointAddress();
fooEndpointAddress.setIp("127.0.0.1");
fooEndpointAddress.setHostname("localhost");
CoreV1EndpointPort fooEndpointPort = new CoreV1EndpointPort();
fooEndpointPort.setPort(WIRE_MOCK_SERVER.port());
List<ServiceInstance> instances = new ArrayList<>();
DefaultKubernetesServiceInstance fooServiceInstance = new DefaultKubernetesServiceInstance("foo", "foo",
fooEndpointAddress.getIp(), fooEndpointPort.getPort(), metadata, false);
instances.add(fooServiceInstance);
when(reactiveDiscoveryClient.getInstances(eq("foo"))).thenReturn(Flux.fromIterable(instances));
V1Secret secret = new V1Secret();
V1ObjectMeta objectMeta = new V1ObjectMeta();
objectMeta.setName("foo");
secret.setMetadata(objectMeta);
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
.willReturn(WireMock.aResponse().withStatus(200)));
StepVerifier.create(changeDetector.triggerRefresh(secret, secret.getMetadata().getName())).verifyComplete();
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh")));
}
private void stubReactiveCall() {
V1EndpointAddress fooEndpointAddress = new V1EndpointAddress();
fooEndpointAddress.setIp("127.0.0.1");
fooEndpointAddress.setHostname("localhost");
CoreV1EndpointPort fooEndpointPort = new CoreV1EndpointPort();
fooEndpointPort.setPort(WIRE_MOCK_SERVER.port());
List<ServiceInstance> instances = new ArrayList<>();
DefaultKubernetesServiceInstance fooServiceInstance = new DefaultKubernetesServiceInstance("foo", "foo",
fooEndpointAddress.getIp(), fooEndpointPort.getPort(), new HashMap<>(), false);
instances.add(fooServiceInstance);
when(reactiveDiscoveryClient.getInstances(eq("foo"))).thenReturn(Flux.fromIterable(instances));
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2013-2022 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.configuration.watcher;
import java.util.Map;
import java.util.Set;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.CONFIG_MAP_LABEL;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_APPS_ANNOTATION;
import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_LABEL;
class WatcherUtilTests {
@Test
void isSpringCloudKubernetesConfigFalse() {
V1ConfigMap configMap = new V1ConfigMapBuilder().withMetadata(new V1ObjectMeta().labels(Map.of())).build();
boolean present = WatcherUtil.isSpringCloudKubernetes(configMap, CONFIG_MAP_LABEL);
Assertions.assertFalse(present);
}
@Test
void isSpringCloudKubernetesConfigTrue() {
V1ConfigMap configMap = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMeta().labels(Map.of(CONFIG_MAP_LABEL, "true"))).build();
boolean present = WatcherUtil.isSpringCloudKubernetes(configMap, CONFIG_MAP_LABEL);
Assertions.assertTrue(present);
}
@Test
void isSpringCloudKubernetesSecretFalse() {
V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().labels(Map.of())).build();
boolean present = WatcherUtil.isSpringCloudKubernetes(secret, SECRET_LABEL);
Assertions.assertFalse(present);
}
@Test
void isSpringCloudKubernetesSecretTrue() {
V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().labels(Map.of(SECRET_LABEL, "true")))
.build();
boolean present = WatcherUtil.isSpringCloudKubernetes(secret, SECRET_LABEL);
Assertions.assertTrue(present);
}
@Test
void labelsMissing() {
V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta()).build();
Map<String, String> res = WatcherUtil.labels(secret);
Assertions.assertEquals(res.size(), 0);
}
@Test
void labelsPresent() {
V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().labels(Map.of("a", "b"))).build();
Map<String, String> res = WatcherUtil.labels(secret);
Assertions.assertEquals(res.size(), 1);
}
@Test
void appsNoMetadata() {
V1Secret secret = new V1SecretBuilder().build();
Set<String> apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION);
Assertions.assertEquals(apps.size(), 0);
}
@Test
void appsNoAnnotations() {
V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().annotations(Map.of())).build();
Set<String> apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION);
Assertions.assertEquals(apps.size(), 0);
}
@Test
void appsAnnotationNotFound() {
V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().annotations(Map.of("a", "b"))).build();
Set<String> apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION);
Assertions.assertEquals(apps.size(), 0);
}
@Test
void appsSingleResult() {
V1Secret secret = new V1SecretBuilder()
.withMetadata(new V1ObjectMeta().annotations(Map.of(SECRET_APPS_ANNOTATION, "one-app"))).build();
Set<String> apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION);
Assertions.assertEquals(apps.size(), 1);
Assertions.assertEquals(apps.iterator().next(), "one-app");
}
@Test
void appsMultipleResults() {
V1Secret secret = new V1SecretBuilder()
.withMetadata(new V1ObjectMeta().annotations(Map.of(SECRET_APPS_ANNOTATION, "one, two, three ")))
.build();
Set<String> apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION);
Assertions.assertEquals(apps.size(), 3);
Assertions.assertTrue(apps.contains("one"));
Assertions.assertTrue(apps.contains("two"));
Assertions.assertTrue(apps.contains("three"));
}
}

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<version>3.0.4-SNAPSHOT</version>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discoveryserver</artifactId>
<version>3.0.4-SNAPSHOT</version>
<description>Spring Cloud parent pom, managing plugins and dependencies for Spring
Cloud projects</description>
<url>https://cloud.spring.io/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver</url>
<inceptionYear>2017</inceptionYear>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>sgibb</id>
<name>Spencer Gibb</name>
<email>sgibb at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>mgrzejszczak</id>
<name>Marcin Grzejszczak</name>
<email>mgrzejszczak at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>rbaxter</id>
<name>Ryan Baxter</name>
<email>rbaxter at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>omaciaszeksharma</id>
<name>Olga Maciaszek-Sharma</name>
<email>omaciaszeksharma at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver</developerConnection>
<url>https://github.com/spring-cloud/spring-cloud-kubernetes/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver</url>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client</artifactId>
<version>3.0.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,130 +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>
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-discoveryserver</artifactId>
<properties>
<docker.registry.organization>springcloud</docker.registry.organization>
<plexus-archiver.version>4.1.0</plexus-archiver.version>
</properties>
<dependencies>
<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-starter-kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>${env.IMAGE}</name>
</image>
<goal>build-image</goal>
</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>
</plugins>
</build>
<profiles>
<profile>
<id>dockerpush</id>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.12</version>
<configuration>
<repository>${docker.registry.organization}/${artifactId}</repository>
<tag>${project.version}</tag>
<username>${env.DOCKER_HUB_USERNAME}</username>
<password>${env.DOCKER_HUB_PASSWORD}</password>
<build>
<noCache>true</noCache>
</build>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>
<version>${plexus-archiver.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>imagename</id>
<activation>
<property>
<name>!env.IMAGE</name>
</property>
</activation>
<properties>
<env.IMAGE>springcloud/${project.artifactId}:${project.version}</env.IMAGE>
</properties>
</profile>
</profiles>
</project>

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2013-2021 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.springframewok.cloud.kubernetes.discoveryserver;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
/**
* @author Ryan Baxter
*/
@SpringBootApplication
public class DiscoveryServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DiscoveryServerApplication.class).run(args);
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2013-2021 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.springframewok.cloud.kubernetes.discoveryserver;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Ryan Baxter
*/
@RestController
public class DiscoveryServerController {
private final KubernetesInformerReactiveDiscoveryClient reactiveDiscoveryClient;
public DiscoveryServerController(KubernetesInformerReactiveDiscoveryClient reactiveDiscoveryClient) {
this.reactiveDiscoveryClient = reactiveDiscoveryClient;
}
@GetMapping("/apps")
public Flux<Service> apps() {
return reactiveDiscoveryClient.getServices().flatMap(service -> reactiveDiscoveryClient.getInstances(service)
.collectList().flatMap(serviceInstances -> Mono.just(new Service(service, serviceInstances))));
}
@GetMapping("/apps/{name}")
public Flux<ServiceInstance> appInstances(@PathVariable String name) {
return reactiveDiscoveryClient.getInstances(name);
}
@GetMapping("/app/{name}/{instanceId}")
public Mono<ServiceInstance> appInstance(@PathVariable String name, @PathVariable String instanceId) {
return reactiveDiscoveryClient.getInstances(name)
.filter(serviceInstance -> serviceInstance.getInstanceId().equals(instanceId)).singleOrEmpty();
}
private record Service(String name, List<ServiceInstance> serviceInstances) {
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright 2013-2021 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.springframewok.cloud.kubernetes.discoveryserver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesServiceInstance;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
*/
class DiscoveryServerControllerTests {
private static final KubernetesServiceInstance serviceAInstance1 = new KubernetesServiceInstance("serviceAInstance1",
"serviceAInstance1", "2.2.2.2", 8080, new HashMap<>(), false, "namespace1", null);
private static final KubernetesServiceInstance serviceAInstance2 = new KubernetesServiceInstance("serviceAInstance2",
"serviceAInstance2", "2.2.2.2", 8080, new HashMap<>(), false, "namespace1", null);
private static final KubernetesServiceInstance serviceAInstance3 = new KubernetesServiceInstance("serviceAInstance3",
"serviceAInstance3", "2.2.2.2", 8080, new HashMap<>(), false, "namespace2", null);
private static final KubernetesServiceInstance serviceBInstance1 = new KubernetesServiceInstance("serviceBInstance1",
"serviceBInstance1", "2.2.2.2", 8080, new HashMap<>(), false, "namespace1", null);
private static final KubernetesServiceInstance serviceCInstance1 = new KubernetesServiceInstance("serviceCInstance1",
"serviceCInstance1", "2.2.2.2", 8080, new HashMap<>(), false, "namespace2", null);
private static DiscoveryServerController.Service serviceA = new DiscoveryServerController.Service();
private static DiscoveryServerController.Service serviceB = new DiscoveryServerController.Service();
private static DiscoveryServerController.Service serviceC = new DiscoveryServerController.Service();
private static KubernetesInformerReactiveDiscoveryClient discoveryClient;
@BeforeAll
static void beforeAll() {
Flux<String> services = Flux.just("serviceA", "serviceB", "serviceC");
List<ServiceInstance> serviceAInstanceList = new ArrayList<>();
serviceAInstanceList.add(serviceAInstance1);
serviceAInstanceList.add(serviceAInstance2);
serviceAInstanceList.add(serviceAInstance3);
Flux<ServiceInstance> serviceAInstances = Flux.fromIterable(serviceAInstanceList);
List<ServiceInstance> serviceBInstanceList = Collections.singletonList(serviceBInstance1);
Flux<ServiceInstance> serviceBInstances = Flux.fromIterable(serviceBInstanceList);
List<ServiceInstance> serviceCInstanceList = Collections.singletonList(serviceCInstance1);
Flux<ServiceInstance> serviceCInstances = Flux.fromIterable(serviceCInstanceList);
discoveryClient = mock(KubernetesInformerReactiveDiscoveryClient.class);
when(discoveryClient.getServices()).thenReturn(services);
when(discoveryClient.getInstances(eq("serviceA"))).thenReturn(serviceAInstances);
when(discoveryClient.getInstances(eq("serviceB"))).thenReturn(serviceBInstances);
when(discoveryClient.getInstances(eq("serviceC"))).thenReturn(serviceCInstances);
when(discoveryClient.getInstances(eq("serviceD"))).thenReturn(Flux.empty());
serviceA.setName("serviceA");
serviceA.setServiceInstances(serviceAInstanceList);
serviceB.setName("serviceB");
serviceB.setServiceInstances(serviceBInstanceList);
serviceC.setName("serviceC");
serviceC.setServiceInstances(serviceCInstanceList);
}
@Test
void apps() {
DiscoveryServerController controller = new DiscoveryServerController(discoveryClient);
StepVerifier.create(controller.apps()).expectNext(serviceA, serviceB, serviceC).verifyComplete();
}
@Test
void appInstances() {
DiscoveryServerController controller = new DiscoveryServerController(discoveryClient);
StepVerifier.create(controller.appInstances("serviceA")).expectNext(serviceAInstance1, serviceAInstance2, serviceAInstance3).verifyComplete();
StepVerifier.create(controller.appInstances("serviceB")).expectNext(serviceBInstance1).verifyComplete();
StepVerifier.create(controller.appInstances("serviceC")).expectNext(serviceCInstance1).verifyComplete();
StepVerifier.create(controller.appInstances("serviceD")).expectNextCount(0).verifyComplete();
}
@Test
void appInstance() {
DiscoveryServerController controller = new DiscoveryServerController(discoveryClient);
StepVerifier.create(controller.appInstance("serviceA", "serviceAInstance2")).expectNext(serviceAInstance2).verifyComplete();
StepVerifier.create(controller.appInstance("serviceB", "doesnotexist")).expectNextCount(0).verifyComplete();
}
}

View File

@@ -1,208 +0,0 @@
/*
* Copyright 2013-2021 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.springframewok.cloud.kubernetes.discoveryserver;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1EndpointPort;
import io.kubernetes.client.openapi.models.V1EndpointSubset;
import io.kubernetes.client.openapi.models.V1Endpoints;
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1ObjectReferenceBuilder;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
import io.kubernetes.client.openapi.models.V1ServiceSpec;
import io.kubernetes.client.openapi.models.V1ServiceStatus;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesServiceInstance;
import org.springframework.context.annotation.Bean;
import org.springframework.test.web.reactive.server.WebTestClient;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = DiscoveryServerIntegrationTests.TestConfig.class,
properties = {"debug=true"})
public class DiscoveryServerIntegrationTests {
private static final V1Service testService1 = new V1Service()
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
.spec(new V1ServiceSpec().loadBalancerIP("1.1.1.1")).status(new V1ServiceStatus());
private static final V1Endpoints testEndpoints1 = new V1Endpoints()
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080).name("http"))
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2").targetRef(new V1ObjectReferenceBuilder().withUid("uid1").build())));
private static final V1Service testService2 = new V1Service()
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace2"))
.spec(new V1ServiceSpec().loadBalancerIP("1.1.1.1")).status(new V1ServiceStatus());
private static final V1Service testService3 = new V1Service()
.metadata(new V1ObjectMeta().name("test-svc-3").namespace("namespace1").putLabelsItem("spring", "true")
.putLabelsItem("k8s", "true"))
.spec(new V1ServiceSpec().loadBalancerIP("1.1.1.1")).status(new V1ServiceStatus());
private static final V1Endpoints testEndpoints3 = new V1Endpoints()
.metadata(new V1ObjectMeta().name("test-svc-3").namespace("namespace1"))
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080).name("http"))
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2").targetRef(new V1ObjectReferenceBuilder().withUid("uid2").build())));
private static WireMockServer wireMockServer;
@Autowired
WebTestClient webTestClient;
@Test
void apps(){
Map<String, String> kubernetesServiceInstance1Metadata = new HashMap<>();
kubernetesServiceInstance1Metadata.put(testEndpoints1.getSubsets().get(0).getPorts().get(0).getName(), testEndpoints1.getSubsets().get(0).getPorts().get(0).getPort().toString());
Map<String, String> kubernetesServiceInstance2Metadata = new HashMap<>();
kubernetesServiceInstance2Metadata.put(testEndpoints3.getSubsets().get(0).getPorts().get(0).getName(), testEndpoints3.getSubsets().get(0).getPorts().get(0).getPort().toString());
kubernetesServiceInstance2Metadata.putAll(testService3.getMetadata().getLabels());
KubernetesServiceInstance kubernetesServiceInstance1 = new KubernetesServiceInstance(testEndpoints1.getSubsets().get(0).getAddresses().get(0).getTargetRef().getUid(), testService1.getMetadata().getName(), testEndpoints1.getSubsets().get(0).getAddresses().get(0).getIp(), testEndpoints1.getSubsets().get(0).getPorts().get(0).getPort(), kubernetesServiceInstance1Metadata, false, testService1.getMetadata().getNamespace(), null);
KubernetesServiceInstance kubernetesServiceInstance3 = new KubernetesServiceInstance(testEndpoints3.getSubsets().get(0).getAddresses().get(0).getTargetRef().getUid(), testService3.getMetadata().getName(), testEndpoints3.getSubsets().get(0).getAddresses().get(0).getIp(), testEndpoints3.getSubsets().get(0).getPorts().get(0).getPort(), kubernetesServiceInstance2Metadata, false, testService3.getMetadata().getNamespace(), null);
webTestClient.get().uri("/apps").exchange().expectBodyList(KubernetesService.class).hasSize(2).contains(new KubernetesService(testService1.getMetadata().getName(),
Collections.singletonList(kubernetesServiceInstance1)), new KubernetesService(testService3.getMetadata().getName(), Collections.singletonList(kubernetesServiceInstance3)));
}
@Test
void appsName() {
Map<String, String> kubernetesServiceInstance2Metadata = new HashMap<>();
kubernetesServiceInstance2Metadata.put(testEndpoints3.getSubsets().get(0).getPorts().get(0).getName(), testEndpoints3.getSubsets().get(0).getPorts().get(0).getPort().toString());
kubernetesServiceInstance2Metadata.putAll(testService3.getMetadata().getLabels());
KubernetesServiceInstance kubernetesServiceInstance3 = new KubernetesServiceInstance(testEndpoints3.getSubsets().get(0).getAddresses().get(0).getTargetRef().getUid(), testService3.getMetadata().getName(), testEndpoints3.getSubsets().get(0).getAddresses().get(0).getIp(), testEndpoints3.getSubsets().get(0).getPorts().get(0).getPort(), kubernetesServiceInstance2Metadata, false, testService3.getMetadata().getNamespace(), null);
webTestClient.get().uri("/apps/test-svc-3").exchange().expectBodyList(KubernetesServiceInstance.class).hasSize(1).contains(kubernetesServiceInstance3);
}
@Test
void instance() {
Map<String, String> kubernetesServiceInstance2Metadata = new HashMap<>();
kubernetesServiceInstance2Metadata.put(testEndpoints3.getSubsets().get(0).getPorts().get(0).getName(), testEndpoints3.getSubsets().get(0).getPorts().get(0).getPort().toString());
kubernetesServiceInstance2Metadata.putAll(testService3.getMetadata().getLabels());
KubernetesServiceInstance kubernetesServiceInstance3 = new KubernetesServiceInstance(testEndpoints3.getSubsets().get(0).getAddresses().get(0).getTargetRef().getUid(), testService3.getMetadata().getName(), testEndpoints3.getSubsets().get(0).getAddresses().get(0).getIp(), testEndpoints3.getSubsets().get(0).getPorts().get(0).getPort(), kubernetesServiceInstance2Metadata, false, testService3.getMetadata().getNamespace(), null);
webTestClient.get().uri("/app/test-svc-3/uid2").exchange().expectBody(KubernetesServiceInstance.class).isEqualTo(kubernetesServiceInstance3);
}
@SpringBootApplication
protected static class TestConfig {
@Bean
public KubernetesNamespaceProvider kubernetesNamespaceProvider() {
KubernetesNamespaceProvider provider = mock(KubernetesNamespaceProvider.class);
when(provider.getNamespace()).thenReturn("namespace1");
return provider;
}
@Bean
public ApiClient apiClient() {
wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
WireMock.configureFor(wireMockServer.port());
stubFor(get("/api/v1/namespaces/namespace1/endpoints?resourceVersion=0&watch=false")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(new V1EndpointsListBuilder()
.withMetadata(new V1ListMetaBuilder().withNewResourceVersion("0").build()).addToItems(testEndpoints1, testEndpoints3).build()))));
stubFor(get("/api/v1/namespaces/namespace1/services?resourceVersion=0&watch=false")
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(new V1ServiceListBuilder()
.withMetadata(new V1ListMetaBuilder().withNewResourceVersion("0").build()).addToItems(testService1, testService2, testService3).build()))));
stubFor(get("/api/v1/namespaces/namespace1/endpoints?watch=true")
.willReturn(aResponse().withStatus(200)));
stubFor(get("/api/v1/namespaces/namespace1/services?watch=true")
.willReturn(aResponse().withStatus(200)));
ApiClient apiClient = new ClientBuilder().setBasePath(wireMockServer.baseUrl()).build();
return apiClient;
}
}
public static class KubernetesService {
private String name;
private List<KubernetesServiceInstance> serviceInstances;
public KubernetesService() { }
public KubernetesService(String name, List<KubernetesServiceInstance> serviceInstances) {
this.name = name;
this.serviceInstances = serviceInstances;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<KubernetesServiceInstance> getServiceInstances() {
return serviceInstances;
}
public void setServiceInstances(List<KubernetesServiceInstance> serviceInstances) {
this.serviceInstances = serviceInstances;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
KubernetesService service = (KubernetesService) o;
return Objects.equals(getName(), service.getName()) && Objects.equals(getServiceInstances(), service.getServiceInstances());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getServiceInstances());
}
@Override
public String toString() {
return "KubernetesService{" +
"name='" + name + '\'' +
", serviceInstances=" + serviceInstances +
'}';
}
}
}