Add Kubernetes Config Server (#881)
* Initial commit
* Added profile activation and support for secrets
* Cherry pick c48fdfd121
* Refactoring classnames that make more sense
* Refining configserver for milestone
* Remove unused code
This commit is contained in:
122
docs/src/main/asciidoc/spring-cloud-kubernetes-configserver.adoc
Normal file
122
docs/src/main/asciidoc/spring-cloud-kubernetes-configserver.adoc
Normal file
@@ -0,0 +1,122 @@
|
||||
[#spring-cloud-kubernetes-configserver]
|
||||
## Spring Cloud Kubernetes Config Server
|
||||
|
||||
The Spring Cloud Kubernetes Config Server, is based on https://spring.io/projects/spring-cloud-config[Spring Cloud Config Server] and adds an https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_environment_repository[environment repository] for Kubernetes
|
||||
https://kubernetes.io/docs/concepts/configuration/configmap/[Config Maps] and https://kubernetes.io/docs/concepts/configuration/secret/[Secrets].
|
||||
|
||||
This is component is completely optional. However, it allows you to continue to leverage configuration
|
||||
you may have stored in existing environment repositories (Git, SVN, Vault, etc) with applications that you are running on Kubernetes.
|
||||
|
||||
A default image is located on Docker Hub which will allow you to easily get a Config Server deployed on Kubernetes without building
|
||||
the code and image yourself. However, if you need to customize the config server behavior you can easily build your own
|
||||
image from the source code on GitHub and use that.
|
||||
|
||||
### Configuration
|
||||
|
||||
#### Enabling The Kubernetes Environment Repository
|
||||
To enable the Kubernetes environment repository the `kubernetes` profile must be included in the list of active profiles.
|
||||
You may activate other profiles as well to use other environment repository implementations.
|
||||
|
||||
#### Config Map and Secret PropertySources
|
||||
By default, only Config Map data will be fetched. To enable Secrets as well you will need to set `spring.cloud.kubernetes.secrets.enableApi=true`.
|
||||
You can disable the Config Map `PropertySource` by setting `spring.cloud.kubernetes.config.enableApi=false`.
|
||||
|
||||
#### Fetching Config Map and Secret Data From Additional Namespaces
|
||||
By default, the Kubernetes environment repository will only fetch Config Map and Secrets from the namespace in which it is deployed.
|
||||
If you want to include data from other namespaces you can set `spring.cloud.kubernetes.configserver.config-map-namespaces` and/or `spring.cloud.kubernetes.configserver.secrets-namespaces` to a comma separated
|
||||
list of namespace values.
|
||||
|
||||
NOTE: If you set `spring.cloud.kubernetes.configserver.config-map-namespaces` and/or `spring.cloud.kubernetes.configserver.secrets-namespaces`
|
||||
you will need to include the namespace in which the Config Server is deployed in order to continue to fetch Config Map and Secret data from that namespace.
|
||||
|
||||
#### Kubernetes Access Controls
|
||||
The Kubernetes Config Server uses the Kubernetes API server to fetch Config Map and Secret data. In order for it to do that
|
||||
it needs ability to `get` and `list` Config Map and Secrets (depending on what you enable/disable).
|
||||
|
||||
### Deployment Yaml
|
||||
|
||||
Below is a sample deployment, service and permissions configuration you can use to deploy a basic Config Server to Kubernetes.
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8888
|
||||
targetPort: 8888
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
type: ClusterIP
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
name: spring-cloud-kubernetes-configserver:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: namespace-reader
|
||||
rules:
|
||||
- apiGroups: ["", "extensions", "apps"]
|
||||
resources: ["configmaps", "secrets"]
|
||||
verbs: ["get", "list"]
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configserver-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
spec:
|
||||
serviceAccount: spring-cloud-kubernetes-configserver
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-configserver
|
||||
image: springcloud/spring-cloud-kubernetes-configserver
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: SPRING_PROFILES_INCLUDE
|
||||
value: "kubernetes"
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
|
||||
----
|
||||
====
|
||||
@@ -31,6 +31,8 @@ include::service-registry.adoc[]
|
||||
|
||||
include::spring-cloud-kubernetes-configuration-watcher.adoc[]
|
||||
|
||||
include::spring-cloud-kubernetes-configserver.adoc[]
|
||||
|
||||
include::examples.adoc[]
|
||||
|
||||
include::other-resources.adoc[]
|
||||
|
||||
@@ -3,3 +3,4 @@ set -e
|
||||
|
||||
./mvnw deploy -DskipTests -B -Pfast,deploy ${@}
|
||||
./mvnw dockerfile:push -pl :spring-cloud-kubernetes-configuration-watcher -Pdockerpush ${@}
|
||||
./mvnw dockerfile:push -pl :spring-cloud-kubernetes-configserver -Pdockerpush ${@}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-kubernetes-configuration-watcher</module>
|
||||
<module>spring-cloud-kubernetes-configserver</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8888
|
||||
targetPort: 8888
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
type: LoadBalancer
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
name: spring-cloud-kubernetes-configserver:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: namespace-reader
|
||||
rules:
|
||||
- apiGroups: ["", "extensions", "apps"]
|
||||
resources: ["configmaps", "secrets"]
|
||||
verbs: ["get", "list"]
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configserver-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configserver
|
||||
spec:
|
||||
serviceAccount: spring-cloud-kubernetes-configserver
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-configserver
|
||||
image: springcloud/spring-cloud-kubernetes-configserver:2.0.4-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: SPRING_PROFILES_INCLUDE
|
||||
value: "kubernetes"
|
||||
- name: SPRING_CLOUD_KUBERNETES_SECRETS_ENABLEAPI
|
||||
value: "true"
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
@@ -0,0 +1,145 @@
|
||||
<?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>2.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-configserver</artifactId>
|
||||
|
||||
<properties>
|
||||
<jib.version>1.8.0</jib.version>
|
||||
<base.image>openjdk:8u222-slim</base.image>
|
||||
<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>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</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>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</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>
|
||||
<profile>
|
||||
<id>jib</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<version>${jib.version}</version>
|
||||
<configuration>
|
||||
<from>
|
||||
<image>${base.image}</image>
|
||||
</from>
|
||||
<to>
|
||||
<image>spring-cloud/${project.artifactId}</image>
|
||||
</to>
|
||||
<container>
|
||||
<user>nobody:nogroup</user>
|
||||
<environment>
|
||||
</environment>
|
||||
</container>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>dockerBuild</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: skaffold/v2alpha3
|
||||
kind: Config
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configserver
|
||||
build:
|
||||
artifacts:
|
||||
- image: springcloud/spring-cloud-kubernetes-configserver
|
||||
custom:
|
||||
buildCommand: "../../mvnw clean install"
|
||||
dependencies:
|
||||
paths:
|
||||
- src
|
||||
- pom.xml
|
||||
# jib: {
|
||||
# args: ["-Pjib"]
|
||||
# }
|
||||
deploy:
|
||||
kubectl:
|
||||
manifests:
|
||||
- k8s/deployment.yaml
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.HashMap;
|
||||
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.ConditionalOnProperty;
|
||||
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.KubernetesClientConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesConfigEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesSecretsEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
|
||||
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 })
|
||||
@ConditionalOnKubernetesEnabled
|
||||
@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.getSecretsNamespaces(), namespace);
|
||||
List<MapPropertySource> propertySources = new ArrayList<>();
|
||||
namespaces.forEach(space -> propertySources
|
||||
.add(new KubernetesClientConfigMapPropertySource(coreApi, applicationName, space, springEnv, "")));
|
||||
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 -> propertySources.add(new KubernetesClientSecretsPropertySource(coreApi,
|
||||
applicationName, space, springEnv, new HashMap<>())));
|
||||
return propertySources;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 conigMapNamespaces = "";
|
||||
|
||||
private String secretsNamespaces = "";
|
||||
|
||||
public String getConigMapNamespaces() {
|
||||
return conigMapNamespaces;
|
||||
}
|
||||
|
||||
public void setConigMapNamespaces(String conigMapNamespaces) {
|
||||
this.conigMapNamespaces = conigMapNamespaces;
|
||||
}
|
||||
|
||||
public String getSecretsNamespaces() {
|
||||
return secretsNamespaces;
|
||||
}
|
||||
|
||||
public void setSecretsNamespaces(String secretsNamespaces) {
|
||||
this.secretsNamespaces = secretsNamespaces;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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);
|
||||
addApplicationConfiguration(environment, springEnv, "application");
|
||||
if (!"application".equalsIgnoreCase(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()));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.kubernetes.configserver.KubernetesConfigServerAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
spring:
|
||||
application:
|
||||
name: kubernetesconfigserver
|
||||
|
||||
server:
|
||||
port: 8888
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.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.context.SpringBootTest;
|
||||
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
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.profiles.include=kubernetes",
|
||||
"spring.cloud.kubernetes.secrets.enableApi=true", "debug=true" },
|
||||
classes = { KubernetesConfigServerApplication.class })
|
||||
public class ConfigServerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate testRestTemplate;
|
||||
|
||||
public static WireMockServer wireMockServer;
|
||||
|
||||
@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() throws Exception {
|
||||
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("secrets.test-cm.default")).isTrue();
|
||||
assertThat(env.getPropertySources().get(1).getSource().get("password")).isEqualTo("p455w0rd");
|
||||
assertThat(env.getPropertySources().get(1).getSource().get("username")).isEqualTo("user");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class KubernetesConfigServerAutoConfigurationTests {
|
||||
|
||||
@Configuration
|
||||
static class MockConfig {
|
||||
|
||||
@Bean
|
||||
@Profile("kubernetesdisabled")
|
||||
public EnvironmentRepository environmentRepository() {
|
||||
return mock(EnvironmentRepository.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "spring.profiles.include=kubernetes,kubernetesdisabled",
|
||||
"spring.cloud.kubernetes.enabled=false", "debug=true" },
|
||||
classes = { KubernetesConfigServerApplication.class, MockConfig.class })
|
||||
public class KubernetesDisabled {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void runTest() {
|
||||
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { KubernetesConfigServerApplication.class, MockConfig.class },
|
||||
properties = { "spring.cloud.kubernetes.enabled=false",
|
||||
"spring.profiles.include=kubernetes,kubernetesdisabled", "debug=true" })
|
||||
@Nested
|
||||
class KubernetesProfileMissing {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void runTest() {
|
||||
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { KubernetesConfigServerApplication.class }, properties = { "spring.profiles.include=kubernetes",
|
||||
"debug=true", "spring.cloud.kubernetes.client.namespace=default" })
|
||||
@Nested
|
||||
class KubernetesEnabledProfileIncluded {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void runTest() {
|
||||
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
|
||||
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { KubernetesConfigServerApplication.class }, properties = { "spring.profiles.include=kubernetes",
|
||||
"debug=true", "spring.cloud.kubernetes.config.enabled=false" })
|
||||
@Nested
|
||||
class KubernetesEnabledProfileIncludedConfigMapDisabled {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void runTest() {
|
||||
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
|
||||
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { KubernetesConfigServerApplication.class }, properties = { "spring.profiles.include=kubernetes",
|
||||
"debug=true", "spring.cloud.kubernetes.client.namespace=default" })
|
||||
@Nested
|
||||
class KubernetesEnabledProfileIncludedSecretsApiDisabled {
|
||||
|
||||
@Autowired
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { KubernetesConfigServerApplication.class },
|
||||
properties = { "spring.profiles.include=kubernetes", "debug=true",
|
||||
"spring.cloud.kubernetes.client.namespace=default",
|
||||
"spring.cloud.kubernetes.secrets.enableApi=true" })
|
||||
@Nested
|
||||
class KubernetesEnabledProfileIncludedSecretsApiEnabled {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void runTest() {
|
||||
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
|
||||
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { KubernetesConfigServerApplication.class },
|
||||
properties = { "spring.profiles.include=kubernetes", "debug=true",
|
||||
"spring.cloud.kubernetes.client.namespace=default",
|
||||
"spring.cloud.kubernetes.config.enableApi=false" })
|
||||
@Nested
|
||||
class KubernetesEnabledProfileIncludedConfigApiDisabled {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void runTest() {
|
||||
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(1);
|
||||
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).hasSize(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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.HashMap;
|
||||
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.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.config.environment.Environment;
|
||||
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource;
|
||||
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 List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers = new ArrayList<>();
|
||||
|
||||
private static final V1ConfigMapList CONFIGMAP_DEFAULT_LIST = new V1ConfigMapList()
|
||||
.addItemsItem(new V1ConfigMapBuilder()
|
||||
.withMetadata(new V1ObjectMetaBuilder().withName("application").withNamespace("default")
|
||||
.withResourceVersion("1").build())
|
||||
.addToData("application.yaml",
|
||||
"dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n")
|
||||
.build())
|
||||
.addItemsItem(new V1ConfigMapBuilder()
|
||||
.withMetadata(new V1ObjectMetaBuilder().withName("stores").withNamespace("default")
|
||||
.withResourceVersion("1").build())
|
||||
.addToData("application.yaml",
|
||||
"dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n")
|
||||
.build())
|
||||
.addItemsItem(new V1ConfigMapBuilder()
|
||||
.withMetadata(new V1ObjectMetaBuilder().withName("stores-dev").withNamespace("default")
|
||||
.withResourceVersion("1").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", "p455w0rd".getBytes()).addToData("username", "stores".getBytes()).build())
|
||||
.addToItems(new V1SecretBuilder()
|
||||
.withMetadata(new V1ObjectMetaBuilder().withName("stores-dev").withResourceVersion("0")
|
||||
.withNamespace("default").build())
|
||||
.addToData("password", "p455w0rd".getBytes()).addToData("username", "stores-dev".getBytes())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@BeforeAll
|
||||
public static void before() {
|
||||
kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> {
|
||||
List<MapPropertySource> propertySources = new ArrayList<>();
|
||||
propertySources.add(
|
||||
new KubernetesClientConfigMapPropertySource(coreApi, applicationName, "default", springEnv, ""));
|
||||
propertySources
|
||||
.add(new KubernetesClientConfigMapPropertySource(coreApi, applicationName, "dev", springEnv, ""));
|
||||
return propertySources;
|
||||
});
|
||||
kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> {
|
||||
List<MapPropertySource> propertySources = new ArrayList<>();
|
||||
propertySources.add(new KubernetesClientSecretsPropertySource(coreApi, applicationName, "default",
|
||||
springEnv, new HashMap<>()));
|
||||
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,
|
||||
kubernetesPropertySourceSuppliers, "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("secrets.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,
|
||||
kubernetesPropertySourceSuppliers, "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("secrets.application.default")
|
||||
|| propertySource.getName().equals("configmap.stores.default")
|
||||
|| propertySource.getName().equals("configmap.stores.dev")
|
||||
|| propertySource.getName().equals("secrets.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("secrets.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("secrets.stores.default")) {
|
||||
assertThat(propertySource.getSource().size()).isEqualTo(2);
|
||||
assertThat(propertySource.getSource().get("username")).isEqualTo("stores");
|
||||
assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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,
|
||||
kubernetesPropertySourceSuppliers, "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("secrets.application.default")
|
||||
|| propertySource.getName().equals("configmap.stores.default")
|
||||
|| propertySource.getName().equals("configmap.stores.dev")
|
||||
|| propertySource.getName().equals("secrets.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("secrets.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(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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
// Currently KubernetesClientSecretsPropertySource does not take into account
|
||||
// profiles, so that plays no role at the moment
|
||||
// See https://github.com/spring-cloud/spring-cloud-kubernetes/issues/880
|
||||
if (propertySource.getName().equals("secrets.stores.default")) {
|
||||
assertThat(propertySource.getSource().size()).isEqualTo(2);
|
||||
assertThat(propertySource.getSource().get("username")).isEqualTo("stores");
|
||||
assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.kubernetes.configserver.TestBootstrapConfig
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.kubernetes.configserver.KubernetesConfigServerAutoConfiguration
|
||||
@@ -13,4 +13,5 @@
|
||||
<suppress files=".*PollingConfigurationChangeDetector.*" checks="LineLength*"/>
|
||||
<suppress files=".*LeaderRecordWatcherTest.*" checks="LineLength*"/>
|
||||
<suppress files=".*ConfigurationWatcherApplication\.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files=".*KubernetesConfigServerApplication\.java" checks="HideUtilityClassConstructor"/>
|
||||
</suppressions>
|
||||
|
||||
Reference in New Issue
Block a user