First draft of configmap support as a property source. Add configmap archaius integration.

This commit is contained in:
Ioannis Canellos
2016-04-19 12:31:10 +03:00
parent e61b9f4699
commit 557fc8e9f2
12 changed files with 599 additions and 4 deletions

12
pom.xml
View File

@@ -76,19 +76,18 @@
<properties>
<!-- Dependency Versions -->
<kubernetes-client.version>1.3.74</kubernetes-client.version>
<kubernetes-client.version>1.3.83</kubernetes-client.version>
<lombok.version>1.16.8</lombok.version>
<servlet-api.version>2.5</servlet-api.version>
<spring-boot.version>1.3.3.RELEASE</spring-boot.version>
<spring-cloud.version>1.0.4.RELEASE</spring-cloud.version>
<spring-cloud.version>1.0.4.RELEASE</spring-cloud.version>
<spring-cloud-sleuth.version>1.0.0.RC1</spring-cloud-sleuth.version>
<spring-cloud-netflix.version>1.0.6.RELEASE</spring-cloud-netflix.version>
<spring.version>4.2.5.RELEASE</spring.version>
<!-- Maven Plugin Versions -->
<docker-maven-plugin.version>0.14.2</docker-maven-plugin.version>
<fabric8-maven-plugin.version>2.2.98</fabric8-maven-plugin.version>
<fabric8-maven-plugin.version>2.2.103</fabric8-maven-plugin.version>
<maven-compiler-plugin.version>3.5</maven-compiler-plugin.version>
</properties>
@@ -100,6 +99,7 @@
<module>spring-cloud-kubernetes-zipkin</module>
<module>spring-cloud-kubernetes-compat</module>
<module>spring-cloud-kubernetes-hystrix</module>
<module>spring-cloud-kubernetes-archaius</module>
<module>spring-cloud-starter-kubernetes-netflix</module>
<module>spring-cloud-starter-kubernetes-zipkin</module>
<module>spring-cloud-starter-kubernetes-all</module>
@@ -114,6 +114,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-archaius</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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-project</artifactId>
<groupId>io.fabric8</groupId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-archaius</artifactId>
<name>Fabric8 :: Spring Cloud Kubernetes :: Archaius</name>
<dependencies>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,51 @@
/*
* 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.archaius;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Configuration
@Import(ArchaiusConfigMapSourceRegistar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ArchaiusConfigMapSource {
/**
* Synonym for name (the name of the ConfigMap).
*/
String value() default "";
/**
* The name of the {@link io.fabric8.kubernetes.api.model.ConfigMap}.
*/
String name() default "";
/**
* The namespacex of the {@link io.fabric8.kubernetes.api.model.ConfigMap}.
*/
String namespace() default "";
}

View File

@@ -0,0 +1,140 @@
/*
* 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.archaius;
import com.google.common.base.Strings;
import com.netflix.config.WatchedConfigurationSource;
import com.netflix.config.WatchedUpdateListener;
import com.netflix.config.WatchedUpdateResult;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class ArchaiusConfigMapSourceConfiguration implements InitializingBean, DisposableBean, WatchedConfigurationSource, Closeable {
private final KubernetesClient client;
private final String name;
private final String namespace;
private final List<WatchedUpdateListener> listeners = new ArrayList<>();
private final AtomicBoolean started = new AtomicBoolean(false);
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private AtomicReference<Map<String, Object>> currentData = new AtomicReference<>();
private Watch watch;
private volatile Watcher<ConfigMap> watcher = new Watcher<ConfigMap>() {
@Override
public void eventReceived(Action action, ConfigMap configMap) {
offer(WatchedUpdateResult.createFull(asObjectMap(configMap.getData())));
}
@Override
public void onClose(KubernetesClientException e) {
}
};
public ArchaiusConfigMapSourceConfiguration(KubernetesClient client, String name, String namespace) {
this.client = client;
this.name = name;
this.namespace = namespace;
}
public void start() {
ConfigMap map = Strings.isNullOrEmpty(namespace)
? client.configMaps().withName(name).get()
: client.configMaps().inNamespace(namespace).withName(name).get();
if (map != null) {
currentData.set(asObjectMap(map.getData()));
}
watch = Strings.isNullOrEmpty(namespace)
? client.configMaps().withName(name).watch(watcher)
: client.configMaps().inNamespace(namespace).withName(namespace).watch(watcher);
started.set(true);
}
@Override
public void close() throws IOException {
started.set(false);
if (watch != null) {
watch.close();
}
executorService.shutdown();
}
@Override
public synchronized void addUpdateListener(WatchedUpdateListener watchedUpdateListener) {
listeners.add(watchedUpdateListener);
}
@Override
public synchronized void removeUpdateListener(WatchedUpdateListener watchedUpdateListener) {
listeners.remove(watchedUpdateListener);
}
@Override
public Map<String, Object> getCurrentData() throws Exception {
return currentData.get();
}
private void offer(WatchedUpdateResult event) {
submit(() -> {
listeners.stream().forEach(l -> l.updateConfiguration(event));
currentData.set(event.getComplete());
});
}
private synchronized void submit(final Runnable command) {
if (started.get()) {
executorService.submit(command);
}
}
private static Map<String, Object> asObjectMap(Map<String, String> source) {
return source.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void destroy() throws Exception {
close();
}
@Override
public void afterPropertiesSet() throws Exception {
start();
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.archaius;
import com.netflix.config.DynamicWatchedConfiguration;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;
import java.util.Map;
class ArchaiusConfigMapSourceRegistar implements ImportBeanDefinitionRegistrar {
private static final String KUBERNETES_CLIENT_REF = "kubernetesClient";
private static final String VALUE_ATTR = "value";
private static final String NAME_ATTR = "name";
private static final String NAMESPACE_ATTR = "namespace";
private static final String CONFIG_MAP_SOURCE_SUFFIX = ".ConfigMapSourceConfiguration";
private static final String DYNAMIC_WATCH_CONFIG_SUFFIX = ".DynamicWatchedConfiguration";
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> source = metadata.getAnnotationAttributes(ArchaiusConfigMapSource.class.getName(), true);
String name = getSourceName(source);
String namespace = getSourceNamespace(source);
if (name != null) {
registerSourceConfiguration(registry, name, namespace);
}
}
private String getSourceName(Map<String, Object> source) {
if (source == null) {
return null;
}
String value = (String) source.get(VALUE_ATTR);
if (!StringUtils.hasText(value)) {
value = (String) source.get(NAME_ATTR);
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException(
"Either 'name' or 'value' must be provided in @ConfigMapSource");
}
private String getSourceNamespace(Map<String, Object> source) {
if (source == null) {
return null;
}
String namespace = (String) source.get(NAMESPACE_ATTR);
if (StringUtils.hasText(namespace)) {
return namespace;
}
return null;
}
private void registerSourceConfiguration(BeanDefinitionRegistry registry, Object name, Object namespace) {
BeanDefinitionBuilder configMapSourceConfigBuilder = BeanDefinitionBuilder.genericBeanDefinition(ArchaiusConfigMapSourceConfiguration.class);
BeanDefinitionBuilder dynamicWatchedConfigBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicWatchedConfiguration.class);
configMapSourceConfigBuilder.addConstructorArgReference(KUBERNETES_CLIENT_REF);
configMapSourceConfigBuilder.addConstructorArgValue(name);
configMapSourceConfigBuilder.addConstructorArgValue(namespace);
String configMapSourceConfigName = name + CONFIG_MAP_SOURCE_SUFFIX;
registry.registerBeanDefinition(configMapSourceConfigName, configMapSourceConfigBuilder.getBeanDefinition());
String dynamicWatchedConfigName = name + DYNAMIC_WATCH_CONFIG_SUFFIX;
dynamicWatchedConfigBuilder.addConstructorArgReference(configMapSourceConfigName);
registry.registerBeanDefinition(dynamicWatchedConfigName, dynamicWatchedConfigBuilder.getBeanDefinition());
}
}

View File

@@ -41,6 +41,17 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
/*
* 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 io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
public class ConfigMapBootstrapConfiguration {
@Configuration
@EnableConfigurationProperties(ConfigMapConfigProperties.class)
@Import(KubernetesAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cloud.kubernetes.config.enabled", matchIfMissing = true)
protected static class KubernetesPropertySourceConfiguration {
@Autowired
private KubernetesClient client;
@Bean
public ConfigMapPropertySourceLocator configMapPropertySourceLocator(ConfigMapConfigProperties properties) {
return new ConfigMapPropertySourceLocator(client, properties);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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 org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.cloud.kubernetes.config")
public class ConfigMapConfigProperties {
private boolean enabled = true;
private String name;
private String namespace;
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;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.api.model.ConfigMap;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.core.env.MapPropertySource;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class ConfigMapPropertySource extends MapPropertySource {
private static final String PREFIX = "configmap";
private static final String SEPARATOR = ".";
private final KubernetesClient client;
private final String name;
private final String namespace;
public ConfigMapPropertySource(KubernetesClient client, String name) {
this(client, name, null);
}
public ConfigMapPropertySource(KubernetesClient client, String name, String namespace) {
super(getName(client, name, namespace), asObjectMap(getData(client, name, namespace)));
this.client = client;
this.name = name;
this.namespace = namespace;
}
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();
}
private static Map<String, String> getData(KubernetesClient client, String name, String namespace) {
ConfigMap map = namespace == null || namespace.isEmpty()
? client.configMaps().withName(name).get()
: client.configMaps().inNamespace(namespace).withName(name).get();
return map != null ? map.getData() : Collections.emptyMap();
}
private static Map<String, Object> asObjectMap(Map<String, String> source) {
return source.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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(0)
public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
private static final String SPRING_APPLICATION_NAME = "spring.application.name";
private final KubernetesClient client;
private final ConfigMapConfigProperties properties;
public ConfigMapPropertySourceLocator(KubernetesClient client, ConfigMapConfigProperties properties) {
this.client = client;
this.properties = properties;
}
@Override
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = env.getProperty(SPRING_APPLICATION_NAME);
String name = properties.getName() == null || properties.getName().isEmpty() ? appName : properties.getName();
String namespace = properties.getNamespace();
return new ConfigMapPropertySource(client, name, namespace);
}
return null;
}
}

View File

@@ -1,5 +1,8 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
io.fabric8.spring.cloud.kubernetes.config.ConfigMapBootstrapConfiguration
org.springframework.context.ApplicationContextInitializer=\
io.fabric8.spring.cloud.kubernetes.profile.KubernetesApplicationContextInitializer

View File

@@ -41,6 +41,11 @@
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-archaius</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
@@ -70,7 +75,6 @@
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
</dependencies>
</project>