Add suport for Secrets based PropertySource
This commit is contained in:
committed by
Ioannis Canellos
parent
064af9822b
commit
a3c2e54311
@@ -66,31 +66,33 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-client</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -18,6 +18,7 @@
|
||||
package io.fabric8.spring.cloud.kubernetes.config;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -30,12 +31,12 @@ import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
|
||||
@ConditionalOnClass(ConfigMap.class)
|
||||
public class ConfigMapBootstrapConfiguration {
|
||||
@ConditionalOnClass({ ConfigMap.class, Secret.class })
|
||||
public class BootstrapConfiguration {
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ConfigMapConfigProperties.class)
|
||||
@Import(KubernetesAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({ ConfigMapConfigProperties.class, SecretsConfigProperties.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.kubernetes.config.enabled", matchIfMissing = true)
|
||||
protected static class KubernetesPropertySourceConfiguration {
|
||||
@Autowired
|
||||
@@ -45,5 +46,10 @@ public class ConfigMapBootstrapConfiguration {
|
||||
public ConfigMapPropertySourceLocator configMapPropertySourceLocator(ConfigMapConfigProperties properties) {
|
||||
return new ConfigMapPropertySourceLocator(client, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecretsPropertySourceLocator secretsPropertySourceLocator(SecretsConfigProperties properties) {
|
||||
return new SecretsPropertySourceLocator(client, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,6 @@
|
||||
|
||||
package io.fabric8.spring.cloud.kubernetes.config;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
@@ -33,17 +25,22 @@ import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConfigMapPropertySource extends MapPropertySource {
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
|
||||
public class ConfigMapPropertySource extends MapPropertySource {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigMapPropertySource.class);
|
||||
|
||||
private static final String CONFIGMAP_PATH = "/configmaps";
|
||||
private static final String APPLICATION_YML = "application.yml";
|
||||
private static final String APPLICATION_YAML = "application.yaml";
|
||||
private static final String APPLICATION_PROPERTIES = "application.properties";
|
||||
|
||||
private static final String PREFIX = "configmap";
|
||||
private static final String SEPARATOR = ".";
|
||||
|
||||
public ConfigMapPropertySource(KubernetesClient client, String name) {
|
||||
this(client, name, null);
|
||||
@@ -54,9 +51,13 @@ public class ConfigMapPropertySource extends MapPropertySource {
|
||||
}
|
||||
|
||||
private static String getName(KubernetesClient client, String name, String namespace) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(PREFIX).append(SEPARATOR).append(name).append(SEPARATOR).append(namespace == null || namespace.isEmpty() ? client.getNamespace() : namespace);
|
||||
return sb.toString();
|
||||
return new StringBuilder()
|
||||
.append(PREFIX)
|
||||
.append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR)
|
||||
.append(name)
|
||||
.append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR)
|
||||
.append(namespace == null || namespace.isEmpty() ? client.getNamespace() : namespace)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static Map<String, String> getData(KubernetesClient client, String name, String namespace) {
|
||||
|
||||
@@ -26,10 +26,6 @@ import org.springframework.core.env.PropertySource;
|
||||
|
||||
@Order(0)
|
||||
public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
|
||||
|
||||
private static final String SPRING_APPLICATION_NAME = "spring.application.name";
|
||||
private static final String FALLBACK_APPLICATION_NAME = "application";
|
||||
|
||||
private final KubernetesClient client;
|
||||
private final ConfigMapConfigProperties properties;
|
||||
|
||||
@@ -42,7 +38,7 @@ public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
|
||||
String appName = env.getProperty(SPRING_APPLICATION_NAME, FALLBACK_APPLICATION_NAME);
|
||||
String appName = env.getProperty(Constants.SPRING_APPLICATION_NAME, Constants.FALLBACK_APPLICATION_NAME);
|
||||
String name = properties.getName() == null || properties.getName().isEmpty() ? appName : properties.getName();
|
||||
String namespace = properties.getNamespace();
|
||||
return new ConfigMapPropertySource(client, name, namespace);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2016 to the original 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
|
||||
*
|
||||
* http://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 io.fabric8.spring.cloud.kubernetes.config;
|
||||
|
||||
final class Constants {
|
||||
static final String SPRING_APPLICATION_NAME = "spring.application.name";
|
||||
static final String FALLBACK_APPLICATION_NAME = "application";
|
||||
static final String PROPERTY_SOURCE_NAME_SEPARATOR = ".";
|
||||
|
||||
private Constants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2016 to the original 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
|
||||
*
|
||||
* http://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 io.fabric8.spring.cloud.kubernetes.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("spring.cloud.kubernetes.secrets")
|
||||
public class SecretsConfigProperties {
|
||||
|
||||
private boolean enabled = true;
|
||||
private String name;
|
||||
private String namespace;
|
||||
private Map<String, String> labels = new HashMap<>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public Map<String, String> getLabels() {
|
||||
return labels;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2016 to the original 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
|
||||
*
|
||||
* http://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 io.fabric8.spring.cloud.kubernetes.config;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class SecretsPropertySource extends MapPropertySource {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SecretsPropertySource.class);
|
||||
|
||||
private static final String PREFIX = "secrets";
|
||||
|
||||
public SecretsPropertySource(KubernetesClient client, Environment env, SecretsConfigProperties config) {
|
||||
super(
|
||||
getSourceName(client, env, config),
|
||||
getSourceData(client, env, config)
|
||||
);
|
||||
}
|
||||
|
||||
private static String getSourceName(KubernetesClient client, Environment env, SecretsConfigProperties config) {
|
||||
return new StringBuilder()
|
||||
.append(PREFIX)
|
||||
.append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR)
|
||||
.append(getApplicationName(env,config))
|
||||
.append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR)
|
||||
.append(getApplicationNamespace(client, config))
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static Map<String, Object> getSourceData(KubernetesClient client, Environment env, SecretsConfigProperties config) {
|
||||
String name = getApplicationName(env, config);
|
||||
String namespace = getApplicationNamespace(client, config);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (config.getLabels().isEmpty()) {
|
||||
if (StringUtils.isEmpty(namespace)) {
|
||||
putAll(
|
||||
client.secrets()
|
||||
.withName(name)
|
||||
.get(),
|
||||
result);
|
||||
} else {
|
||||
putAll(
|
||||
client.secrets()
|
||||
.inNamespace(namespace)
|
||||
.withName(name)
|
||||
.get(),
|
||||
result);
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.isEmpty(namespace)) {
|
||||
client.secrets()
|
||||
.withLabels(config.getLabels())
|
||||
.list()
|
||||
.getItems()
|
||||
.forEach(s -> putAll(s, result));
|
||||
} else {
|
||||
client.secrets()
|
||||
.inNamespace(namespace)
|
||||
.withLabels(config.getLabels())
|
||||
.list()
|
||||
.getItems()
|
||||
.forEach(s -> putAll(s, result));
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Can't read secret with name: [{}] or labels [{}] in namespace:[{}]. Ignoring",
|
||||
config.getName(),
|
||||
config.getLabels(),
|
||||
namespace,
|
||||
e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// *****************************
|
||||
// Helpers
|
||||
// *****************************
|
||||
|
||||
private static String getApplicationName(Environment env, SecretsConfigProperties config) {
|
||||
String name = config.getName();
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
name = env.getProperty(
|
||||
Constants.SPRING_APPLICATION_NAME,
|
||||
Constants.FALLBACK_APPLICATION_NAME);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static String getApplicationNamespace(KubernetesClient client, SecretsConfigProperties config) {
|
||||
String namespace = config.getNamespace();
|
||||
if (StringUtils.isEmpty(namespace)) {
|
||||
namespace = client.getNamespace();
|
||||
}
|
||||
|
||||
return namespace;
|
||||
}
|
||||
|
||||
private static void putAll(Secret secret, Map<String, Object> result) {
|
||||
if (secret != null && secret.getData() != null) {
|
||||
secret.getData().forEach((k, v) -> result.put(
|
||||
k,
|
||||
new String(Base64.getDecoder().decode(v)).trim())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2016 to the original 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
|
||||
*
|
||||
* http://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 io.fabric8.spring.cloud.kubernetes.config;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
@Order(1)
|
||||
public class SecretsPropertySourceLocator implements PropertySourceLocator {
|
||||
private final KubernetesClient client;
|
||||
private final SecretsConfigProperties properties;
|
||||
|
||||
public SecretsPropertySourceLocator(KubernetesClient client, SecretsConfigProperties properties) {
|
||||
this.client = client;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
return environment instanceof ConfigurableEnvironment
|
||||
? new SecretsPropertySource(client, environment, properties)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration
|
||||
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
io.fabric8.spring.cloud.kubernetes.config.ConfigMapBootstrapConfiguration
|
||||
io.fabric8.spring.cloud.kubernetes.config.BootstrapConfiguration
|
||||
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
io.fabric8.spring.cloud.kubernetes.profile.KubernetesApplicationContextInitializer
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package io.fabric8.spring.cloud.kubernetes.config.test
|
||||
|
||||
import io.fabric8.kubernetes.api.model.SecretBuilder
|
||||
import io.fabric8.kubernetes.api.model.ConfigMapBuilder
|
||||
import io.fabric8.kubernetes.client.Config
|
||||
import io.fabric8.kubernetes.client.KubernetesClient
|
||||
@@ -27,14 +28,15 @@ import org.springframework.boot.test.IntegrationTest
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration
|
||||
import org.springframework.core.env.Environment
|
||||
import spock.lang.Specification
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
@Slf4j
|
||||
@SpringApplicationConfiguration(TestApplication.class)
|
||||
@IntegrationTest(
|
||||
[
|
||||
"spring.application.name=testapp",
|
||||
"spring.cloud.kubernetes.client.namespace=testns",
|
||||
"spring.cloud.kubernetes.client.trustCerts=true",
|
||||
"spring.cloud.kubernetes.config.namespace=testns"
|
||||
@IntegrationTest([
|
||||
"spring.application.name=testapp",
|
||||
"spring.cloud.kubernetes.client.namespace=testns",
|
||||
"spring.cloud.kubernetes.client.trustCerts=true",
|
||||
"spring.cloud.kubernetes.config.namespace=testns"
|
||||
])
|
||||
@EnableConfigurationProperties
|
||||
class CoreTest extends Specification {
|
||||
@@ -55,12 +57,26 @@ class CoreTest extends Specification {
|
||||
mockServer.init()
|
||||
mockClient = mockServer.createClient()
|
||||
|
||||
//Setup configmap data
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("spring.kubernetes.test.value", "value1")
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/testns/configmaps/testapp").andReturn(200, new ConfigMapBuilder()
|
||||
.withData(data)
|
||||
.build()).always()
|
||||
mockServer.expect().get()
|
||||
.withPath("/api/v1/namespaces/testns/configmaps/testapp")
|
||||
.andReturn(
|
||||
200,
|
||||
new ConfigMapBuilder()
|
||||
.withData([
|
||||
'spring.kubernetes.test.value': 'value1'])
|
||||
.build())
|
||||
.always()
|
||||
mockServer.expect().get()
|
||||
.withPath("/api/v1/namespaces/testns/secrets/testapp")
|
||||
.andReturn(
|
||||
200,
|
||||
new SecretBuilder()
|
||||
.withData([
|
||||
'amq.pwd': 'MWYyZDFlMmU2N2Rm',
|
||||
'amq.usr': 'YWRtaW4K'
|
||||
])
|
||||
.build())
|
||||
.always()
|
||||
|
||||
//Configure the kubernetes master url to point to the mock server
|
||||
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl())
|
||||
@@ -80,8 +96,8 @@ class CoreTest extends Specification {
|
||||
|
||||
def "Kubernetes client config bean should be configurable via system properties"() {
|
||||
expect:
|
||||
config.getMasterUrl().equals(mockClient.getConfiguration().getMasterUrl());
|
||||
config.getNamespace().equals("testns");
|
||||
config.getMasterUrl().equals(mockClient.getConfiguration().getMasterUrl())
|
||||
config.getNamespace().equals("testns")
|
||||
config.trustCerts
|
||||
}
|
||||
|
||||
@@ -92,12 +108,17 @@ class CoreTest extends Specification {
|
||||
|
||||
def "Kubernetes client should be configured from system properties"() {
|
||||
expect:
|
||||
client.getConfiguration().getMasterUrl().equals(mockClient.getConfiguration().getMasterUrl());
|
||||
client.getConfiguration().getMasterUrl().equals(mockClient.getConfiguration().getMasterUrl())
|
||||
}
|
||||
|
||||
|
||||
def "properties should be read from config map"() {
|
||||
expect:
|
||||
environment.getProperty("spring.kubernetes.test.value").equals("value1");
|
||||
environment.getProperty("spring.kubernetes.test.value").equals("value1")
|
||||
}
|
||||
|
||||
def "properties should be read from secrets"() {
|
||||
expect:
|
||||
environment.getProperty("amq.pwd").equals("1f2d1e2e67df")
|
||||
environment.getProperty("amq.usr").equals('admin');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user