Skip to content

Commit a006296

Browse files
geoandiocanel
authored andcommitted
Rationalize ConfigMap usage when paths (mounted files) are involved
This is done by removing the file walking and loading logic that existed before and replacing it with an implementation that creates on ConfigMapResource per specified path. Reading and processing the content of the path is done in the exact same way as the enableApi method does thus providing a consistent experience. The implementation is inspired by the following comment: spring-cloud#230 (comment) Fixes: spring-cloud#230
1 parent 9a7be93 commit a006296

File tree

13 files changed

+473
-221
lines changed

13 files changed

+473
-221
lines changed

README.adoc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,20 @@ spec:
224224
- check the security configuration section, to access config maps from inside a pod you need to have the correct
225225
Kubernetes service accounts, roles and role bindings.
226226

227+
Another option for using ConfigMaps, is to mount them into the Pod running the Spring Cloud Kubernetes application
228+
and have Spring Cloud Kubernetes read them from the file system.
229+
This behavior is controlled by the `spring.cloud.kubernetes.config.paths` property and can be used in
230+
addition to or instead of the mechanism described earlier.
231+
Multiple (exact) file paths can be specified in `spring.cloud.kubernetes.config.paths` by using the `,` delimiter
232+
227233
.Properties:
228234
[options="header,footer"]
229235
|===
230236
| Name | Type | Default | Description
231237
| spring.cloud.kubernetes.config.enabled | Boolean | true | Enable Secrets PropertySource
232238
| spring.cloud.kubernetes.config.name | String | ${spring.application.name} | Sets the name of ConfigMap to lookup
233239
| spring.cloud.kubernetes.config.namespace | String | Client namespace | Sets the Kubernetes namespace where to lookup
234-
| spring.cloud.kubernetes.config.paths | List | null | Sets the paths where ConfigMaps are mounted
240+
| spring.cloud.kubernetes.config.paths | List | [] | Sets the paths where ConfigMaps are mounted
235241
| spring.cloud.kubernetes.config.enableApi | Boolean | true | Enable/Disable consuming ConfigMaps via APIs
236242
|===
237243

docs/src/main/asciidoc/property-source-config.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ spec:
178178
- check the security configuration section, to access config maps from inside a pod you need to have the correct
179179
Kubernetes service accounts, roles and role bindings.
180180

181+
Another option for using ConfigMaps, is to mount them into the Pod running the Spring Cloud Kubernetes application
182+
and have Spring Cloud Kubernetes read them from the file system.
183+
This behavior is controlled by the `spring.cloud.kubernetes.config.paths` property and can be used in
184+
addition to or instead of the mechanism described earlier.
185+
Multiple (exact) file paths can be specified in `spring.cloud.kubernetes.config.paths` by using the `,` delimiter
186+
181187
.Properties:
182188
[options="header,footer"]
183189
|===

spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySource.java

Lines changed: 41 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,23 @@
1717

1818
package org.springframework.cloud.kubernetes.config;
1919

20-
import java.io.ByteArrayInputStream;
21-
import java.io.IOException;
20+
import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.KEY_VALUE_TO_PROPERTIES;
21+
import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.PROPERTIES_TO_MAP;
22+
import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.yamlParserGenerator;
23+
24+
import io.fabric8.kubernetes.api.model.ConfigMap;
25+
import io.fabric8.kubernetes.client.KubernetesClient;
2226
import java.util.HashMap;
2327
import java.util.Map;
2428
import java.util.Map.Entry;
25-
import java.util.Properties;
2629
import java.util.Set;
27-
import java.util.function.Function;
2830
import java.util.stream.Collectors;
29-
30-
import io.fabric8.kubernetes.api.model.ConfigMap;
31-
import io.fabric8.kubernetes.client.KubernetesClient;
3231
import org.apache.commons.logging.Log;
3332
import org.apache.commons.logging.LogFactory;
34-
35-
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
36-
import org.springframework.core.io.ByteArrayResource;
33+
import org.springframework.core.env.MapPropertySource;
3734
import org.springframework.util.StringUtils;
3835

39-
import static java.util.Arrays.asList;
40-
import static org.springframework.beans.factory.config.YamlProcessor.MatchStatus.ABSTAIN;
41-
import static org.springframework.beans.factory.config.YamlProcessor.MatchStatus.FOUND;
42-
import static org.springframework.beans.factory.config.YamlProcessor.MatchStatus.NOT_FOUND;
43-
44-
public class ConfigMapPropertySource extends KubernetesPropertySource {
36+
public class ConfigMapPropertySource extends MapPropertySource {
4537
private static final Log LOG = LogFactory.getLog(ConfigMapPropertySource.class);
4638

4739
private static final String APPLICATION_YML = "application.yml";
@@ -50,20 +42,14 @@ public class ConfigMapPropertySource extends KubernetesPropertySource {
5042

5143
private static final String PREFIX = "configmap";
5244

53-
public ConfigMapPropertySource(KubernetesClient client, String name,
54-
ConfigMapConfigProperties config) {
55-
this(client, name, null, config);
56-
}
57-
58-
public ConfigMapPropertySource(KubernetesClient client, String name,
59-
String[] profiles, ConfigMapConfigProperties config) {
60-
this(client, name, null, profiles, config);
45+
public ConfigMapPropertySource(KubernetesClient client, String name) {
46+
this(client, name, null, null);
6147
}
6248

6349
public ConfigMapPropertySource(KubernetesClient client, String name, String namespace,
64-
String[] profiles, ConfigMapConfigProperties config) {
50+
String[] profiles) {
6551
super(getName(client, name, namespace),
66-
asObjectMap(getData(client, name, namespace, profiles, config)));
52+
asObjectMap(getData(client, name, namespace, profiles)));
6753
}
6854

6955
private static String getName(KubernetesClient client, String name,
@@ -77,35 +63,31 @@ private static String getName(KubernetesClient client, String name,
7763
}
7864

7965
private static Map<String, String> getData(KubernetesClient client, String name,
80-
String namespace, String[] profiles, ConfigMapConfigProperties config) {
81-
Map<String, String> result = new HashMap<>();
82-
if (config.isEnableApi()) {
83-
try {
84-
ConfigMap map = StringUtils.isEmpty(namespace)
85-
? client.configMaps().withName(name).get()
86-
: client.configMaps().inNamespace(namespace).withName(name).get();
87-
88-
if (map != null) {
89-
result.putAll(processAllEntries(map.getData(), profiles));
90-
}
91-
}
92-
catch (Exception e) {
93-
LOG.warn("Can't read configMap with name: [" + name + "] in namespace:["
94-
+ namespace + "]. Ignoring", e);
66+
String namespace, String[] profiles) {
67+
try {
68+
ConfigMap map = StringUtils.isEmpty(namespace)
69+
? client.configMaps().withName(name).get()
70+
: client.configMaps().inNamespace(namespace).withName(name).get();
71+
72+
if (map != null) {
73+
return processAllEntries(map.getData(), profiles);
9574
}
9675
}
76+
catch (Exception e) {
77+
LOG.warn("Can't read configMap with name: [" + name + "] in namespace:["
78+
+ namespace + "]. Ignoring", e);
79+
}
9780

98-
Map<String, String> configsFromPaths = new HashMap<>();
99-
putPathConfig(configsFromPaths, config.getPaths());
100-
result.putAll(processAllEntries(configsFromPaths, profiles));
101-
return result;
81+
return new HashMap<>();
10282
}
10383

10484
private static Map<String, String> processAllEntries(Map<String, String> input,
10585
String[] profiles) {
10686

10787
Set<Entry<String, String>> entrySet = input.entrySet();
10888
if (entrySet.size() == 1) {
89+
// we handle the case where the configmap contains a single "file"
90+
// in this case we don't care what the name of t he file is
10991
Entry<String, String> singleEntry = entrySet.iterator().next();
11092
String propertyName = singleEntry.getKey();
11193
String propertyValue = singleEntry.getValue();
@@ -115,7 +97,8 @@ private static Map<String, String> processAllEntries(Map<String, String> input,
11597
+ "] will be treated as a yaml file");
11698
}
11799

118-
return yamlParserGenerator(profiles).andThen(PROPERTIES_TO_MAP)
100+
return yamlParserGenerator(profiles).andThen(
101+
PROPERTIES_TO_MAP)
119102
.apply(propertyValue);
120103
}
121104
else if (propertyName.endsWith(".properties")) {
@@ -124,7 +107,8 @@ else if (propertyName.endsWith(".properties")) {
124107
+ "] will be treated as a properties file");
125108
}
126109

127-
return KEY_VALUE_TO_PROPERTIES.andThen(PROPERTIES_TO_MAP)
110+
return KEY_VALUE_TO_PROPERTIES.andThen(
111+
PROPERTIES_TO_MAP)
128112
.apply(propertyValue);
129113
}
130114
else {
@@ -140,65 +124,29 @@ private static Map<String, String> defaultProcessAllEntries(Map<String, String>
140124

141125
return input.entrySet().stream()
142126
.map(e -> extractProperties(e.getKey(), e.getValue(), profiles))
143-
.filter(m -> !m.isEmpty()).flatMap(m -> m.entrySet().stream())
144-
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
127+
.filter(m -> !m.isEmpty())
128+
.flatMap(m -> m.entrySet().stream())
129+
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
145130
}
146131

147132
private static Map<String, String> extractProperties(String resourceName,
148133
String content, String[] profiles) {
149-
Map<String, String> result = new HashMap<>();
150134

151-
if (resourceName.equals(APPLICATION_YAML)
152-
|| resourceName.equals(APPLICATION_YML)) {
153-
result.putAll(yamlParserGenerator(profiles).andThen(PROPERTIES_TO_MAP)
154-
.apply(content));
135+
if (resourceName.equals(APPLICATION_YAML) || resourceName.equals(APPLICATION_YML)) {
136+
return yamlParserGenerator(profiles).andThen(PROPERTIES_TO_MAP).apply(content);
155137
}
156138
else if (resourceName.equals(APPLICATION_PROPERTIES)) {
157-
result.putAll(
158-
KEY_VALUE_TO_PROPERTIES.andThen(PROPERTIES_TO_MAP).apply(content));
139+
return KEY_VALUE_TO_PROPERTIES.andThen(PROPERTIES_TO_MAP).apply(content);
159140
}
160-
else {
161-
result.put(resourceName, content);
162-
}
163-
return result;
141+
142+
return new HashMap<String, String>() {{
143+
put(resourceName, content);
144+
}};
164145
}
165146

166147
private static Map<String, Object> asObjectMap(Map<String, String> source) {
167148
return source.entrySet().stream()
168149
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
169150
}
170151

171-
private static Function<String, Properties> yamlParserGenerator(
172-
final String[] profiles) {
173-
return s -> {
174-
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
175-
yamlFactory.setDocumentMatchers(properties -> {
176-
String profileProperty = properties.getProperty("spring.profiles");
177-
if (profileProperty != null && profileProperty.length() > 0) {
178-
return asList(profiles).contains(profileProperty) ? FOUND : NOT_FOUND;
179-
}
180-
else {
181-
return ABSTAIN;
182-
}
183-
});
184-
yamlFactory.setResources(new ByteArrayResource(s.getBytes()));
185-
return yamlFactory.getObject();
186-
};
187-
}
188-
189-
private static final Function<String, Properties> KEY_VALUE_TO_PROPERTIES = s -> {
190-
Properties properties = new Properties();
191-
try {
192-
properties.load(new ByteArrayInputStream(s.getBytes()));
193-
return properties;
194-
}
195-
catch (IOException e) {
196-
throw new IllegalArgumentException();
197-
}
198-
};
199-
200-
private static final Function<Properties, Map<String, String>> PROPERTIES_TO_MAP = p -> p
201-
.entrySet().stream().collect(Collectors.toMap(e -> String.valueOf(e.getKey()),
202-
e -> String.valueOf(e.getValue())));
203-
204152
}

spring-cloud-kubernetes-config/src/main/java/org/springframework/cloud/kubernetes/config/ConfigMapPropertySourceLocator.java

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,22 @@
1717

1818
package org.springframework.cloud.kubernetes.config;
1919

20-
import java.util.List;
20+
import static org.springframework.cloud.kubernetes.config.ConfigUtils.getApplicationName;
21+
import static org.springframework.cloud.kubernetes.config.ConfigUtils.getApplicationNamespace;
22+
import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.KEY_VALUE_TO_PROPERTIES;
23+
import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.PROPERTIES_TO_MAP;
24+
import static org.springframework.cloud.kubernetes.config.PropertySourceUtils.yamlParserGenerator;
2125

26+
import io.fabric8.kubernetes.api.builder.Function;
2227
import io.fabric8.kubernetes.client.KubernetesClient;
23-
28+
import java.io.IOException;
29+
import java.nio.file.Files;
30+
import java.nio.file.Paths;
31+
import java.util.HashMap;
32+
import java.util.List;
33+
import java.util.Map;
34+
import org.apache.commons.logging.Log;
35+
import org.apache.commons.logging.LogFactory;
2436
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
2537
import org.springframework.cloud.kubernetes.config.ConfigMapConfigProperties.NormalizedSource;
2638
import org.springframework.core.annotation.Order;
@@ -30,11 +42,11 @@
3042
import org.springframework.core.env.MapPropertySource;
3143
import org.springframework.core.env.PropertySource;
3244

33-
import static org.springframework.cloud.kubernetes.config.ConfigUtils.getApplicationName;
34-
import static org.springframework.cloud.kubernetes.config.ConfigUtils.getApplicationNamespace;
35-
3645
@Order(0)
3746
public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
47+
48+
private static final Log LOG = LogFactory.getLog(ConfigMapPropertySourceLocator.class);
49+
3850
private final KubernetesClient client;
3951
private final ConfigMapConfigProperties properties;
4052

@@ -51,14 +63,14 @@ public PropertySource locate(Environment environment) {
5163

5264
List<ConfigMapConfigProperties.NormalizedSource> sources = properties
5365
.determineSources();
54-
if (sources.size() == 1) {
55-
return getMapPropertySourceForSingleConfigMap(env, sources.get(0));
56-
}
57-
5866
CompositePropertySource composite = new CompositePropertySource(
5967
"composite-configmap");
60-
sources.forEach(s -> composite.addFirstPropertySource(
61-
getMapPropertySourceForSingleConfigMap(env, s)));
68+
if (properties.isEnableApi()) {
69+
sources.forEach(s -> composite.addFirstPropertySource(
70+
getMapPropertySourceForSingleConfigMap(env, s)));
71+
}
72+
73+
addPropertySourcesFromPaths(environment, composite);
6274

6375
return composite;
6476
}
@@ -74,6 +86,65 @@ private MapPropertySource getMapPropertySourceForSingleConfigMap(
7486
configurationTarget),
7587
getApplicationNamespace(client, normalizedSource.getNamespace(),
7688
configurationTarget),
77-
environment.getActiveProfiles(), properties);
89+
environment.getActiveProfiles());
90+
}
91+
92+
private void addPropertySourcesFromPaths(Environment environment,
93+
CompositePropertySource composite) {
94+
properties
95+
.getPaths()
96+
.stream()
97+
.map(Paths::get)
98+
.peek(p -> {
99+
if(!Files.exists(p)) {
100+
LOG.warn("Configured input path: " + p + " will be ignored because it does not exist on the file system");
101+
}
102+
})
103+
.filter(Files::exists)
104+
.peek(p -> {
105+
if(!Files.isRegularFile(p)) {
106+
LOG.warn("Configured input path: " + p + " will be ignored because it is not a regular file");
107+
}
108+
})
109+
.filter(Files::isRegularFile)
110+
.forEach(p -> {
111+
try {
112+
String content = new String(Files.readAllBytes(p)).trim();
113+
String filename = p.getFileName().toString().toLowerCase();
114+
if(filename.endsWith(".properties")) {
115+
addPropertySourceIfNeeded(
116+
c -> PROPERTIES_TO_MAP.apply(KEY_VALUE_TO_PROPERTIES.apply(c)),
117+
content,
118+
filename,
119+
composite
120+
);
121+
}
122+
else if(filename.endsWith(".yml") || filename.endsWith(".yaml")) {
123+
addPropertySourceIfNeeded(
124+
c -> PROPERTIES_TO_MAP.apply(
125+
yamlParserGenerator(environment.getActiveProfiles()).apply(c)
126+
),
127+
content,
128+
filename,
129+
composite
130+
);
131+
}
132+
} catch (IOException e) {
133+
LOG.warn("Error reading input file", e);
134+
}
135+
});
136+
}
137+
138+
private void addPropertySourceIfNeeded(Function<String, Map<String, String>> contentToMapFunction,
139+
String content, String name, CompositePropertySource composite) {
140+
141+
Map<String, Object> map = new HashMap<>();
142+
map.putAll(contentToMapFunction.apply(content));
143+
if(map.isEmpty()) {
144+
LOG.warn("Property source: " + name + "will be ignored because no properties could be found");
145+
}
146+
else {
147+
composite.addFirstPropertySource(new MapPropertySource(name, map));
148+
}
78149
}
79150
}

0 commit comments

Comments
 (0)