diff --git a/docs/src/main/asciidoc/spring-cloud-kubernetes-configserver.adoc b/docs/src/main/asciidoc/spring-cloud-kubernetes-configserver.adoc
new file mode 100644
index 00000000..d15122c5
--- /dev/null
+++ b/docs/src/main/asciidoc/spring-cloud-kubernetes-configserver.adoc
@@ -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
+
+----
+====
diff --git a/docs/src/main/asciidoc/spring-cloud-kubernetes.adoc b/docs/src/main/asciidoc/spring-cloud-kubernetes.adoc
index b91696f7..ba6f7afe 100644
--- a/docs/src/main/asciidoc/spring-cloud-kubernetes.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-kubernetes.adoc
@@ -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[]
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index a5c61146..7b39b7eb 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -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 ${@}
diff --git a/spring-cloud-kubernetes-controllers/pom.xml b/spring-cloud-kubernetes-controllers/pom.xml
index 9594985d..d6a87525 100644
--- a/spring-cloud-kubernetes-controllers/pom.xml
+++ b/spring-cloud-kubernetes-controllers/pom.xml
@@ -14,6 +14,7 @@
spring-cloud-kubernetes-configuration-watcher
+ spring-cloud-kubernetes-configserver
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/k8s/deployment.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/k8s/deployment.yaml
new file mode 100644
index 00000000..563cf2b5
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/k8s/deployment.yaml
@@ -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
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/pom.xml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/pom.xml
new file mode 100644
index 00000000..5eca21df
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/pom.xml
@@ -0,0 +1,145 @@
+
+
+
+ spring-cloud-kubernetes-controllers
+ org.springframework.cloud
+ 2.1.0-SNAPSHOT
+
+ 4.0.0
+
+ spring-cloud-kubernetes-configserver
+
+
+ 1.8.0
+ openjdk:8u222-slim
+ springcloud
+ 4.1.0
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-config-server
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.cloud
+ spring-cloud-starter-kubernetes-client-config
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.github.tomakehurst
+ wiremock-jre8
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ ${env.IMAGE}
+
+ build-image
+
+
+
+ package
+
+ build-image
+
+
+
+
+
+
+
+
+
+ dockerpush
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+ 1.4.12
+
+ ${docker.registry.organization}/${artifactId}
+ ${project.version}
+ ${env.DOCKER_HUB_USERNAME}
+ ${env.DOCKER_HUB_PASSWORD}
+
+ true
+
+
+
+
+ org.codehaus.plexus
+ plexus-archiver
+ ${plexus-archiver.version}
+
+
+
+
+
+
+
+ imagename
+
+
+ !env.IMAGE
+
+
+
+ springcloud/${project.artifactId}:${project.version}
+
+
+
+ jib
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+ ${jib.version}
+
+
+ ${base.image}
+
+
+ spring-cloud/${project.artifactId}
+
+
+ nobody:nogroup
+
+
+
+
+
+
+ package
+
+ dockerBuild
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/skaffold.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/skaffold.yaml
new file mode 100644
index 00000000..138b208c
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/skaffold.yaml
@@ -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
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerApplication.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerApplication.java
new file mode 100644
index 00000000..e7b44f9a
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerApplication.java
@@ -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);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java
new file mode 100644
index 00000000..38f27ab7
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java
@@ -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 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 namespaces = namespaceSplitter(properties.getSecretsNamespaces(), namespace);
+ List 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 namespaces = namespaceSplitter(properties.getSecretsNamespaces(), namespace);
+ List propertySources = new ArrayList<>();
+ namespaces.forEach(space -> propertySources.add(new KubernetesClientSecretsPropertySource(coreApi,
+ applicationName, space, springEnv, new HashMap<>())));
+ return propertySources;
+ };
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerProperties.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerProperties.java
new file mode 100644
index 00000000..bbb88b81
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerProperties.java
@@ -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;
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java
new file mode 100644
index 00000000..e4e7c3c2
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepository.java
@@ -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 kubernetesPropertySourceSuppliers;
+
+ private String namespace;
+
+ public KubernetesEnvironmentRepository(CoreV1Api coreApi,
+ List 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 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()));
+ }
+ });
+ });
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplier.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplier.java
new file mode 100644
index 00000000..a73af430
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesPropertySourceSupplier.java
@@ -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 get(CoreV1Api coreV1Api, String name, String namespace, Environment environment);
+
+ static List namespaceSplitter(String namespacesString, String currentNamespace) {
+ List namespaces = Collections.singletonList(currentNamespace);
+ String[] namespacesArray = StringUtils.commaDelimitedListToStringArray(namespacesString);
+ if (namespacesArray.length > 0) {
+ namespaces = Arrays.asList(namespacesArray);
+ }
+ return namespaces;
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..453ef580
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.springframework.cloud.kubernetes.configserver.KubernetesConfigServerAutoConfiguration
+
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/resources/application.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/resources/application.yaml
new file mode 100644
index 00000000..aed3a8c7
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/resources/application.yaml
@@ -0,0 +1,6 @@
+spring:
+ application:
+ name: kubernetesconfigserver
+
+server:
+ port: 8888
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java
new file mode 100644
index 00000000..690f9b13
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java
@@ -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");
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfigurationTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfigurationTests.java
new file mode 100644
index 00000000..0ae3aaa7
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfigurationTests.java
@@ -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);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java
new file mode 100644
index 00000000..59b0bdf5
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java
@@ -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 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 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 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");
+ }
+ });
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/TestBootstrapConfig.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/TestBootstrapConfig.java
new file mode 100644
index 00000000..b93a81ed
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/TestBootstrapConfig.java
@@ -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);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/resources/META-INF/spring.factories b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/resources/META-INF/spring.factories
new file mode 100644
index 00000000..86565476
--- /dev/null
+++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/resources/META-INF/spring.factories
@@ -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
diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml
index b7714c41..d2aa4f0d 100644
--- a/src/checkstyle/checkstyle-suppressions.xml
+++ b/src/checkstyle/checkstyle-suppressions.xml
@@ -13,4 +13,5 @@
+