Skip to content

Commit 6303b5a

Browse files
committed
feat(springboot cloudconfig): Implement Configuration Cloud Config Importer
Implemented the configuration importer using dapr client configuration api also done a style check Signed-off-by: lony2003 <zhangke200377@outlook.com>
1 parent 02e3bbf commit 6303b5a

13 files changed

+411
-198
lines changed

dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties;
2121
import io.dapr.spring.boot.autoconfigure.client.DaprConnectionDetails;
2222

23-
2423
public class DaprCloudConfigClientManager {
2524

2625
private static DaprClient daprClient;
2726
private static DaprPreviewClient daprPreviewClient;
2827
private final DaprCloudConfigProperties daprCloudConfigProperties;
2928
private final DaprClientProperties daprClientConfig;
3029

30+
/**
31+
* Create a DaprCloudConfigClientManager to create Config-Specific Dapr Client.
32+
*
33+
* @param daprCloudConfigProperties Properties of Dapr Cloud Config
34+
* @param daprClientConfig Properties of Dapr Client
35+
*/
3136
public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties,
3237
DaprClientProperties daprClientConfig) {
3338
this.daprCloudConfigProperties = daprCloudConfigProperties;

dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ public class DaprCloudConfigProperties {
2424
public static final String PROPERTY_PREFIX = "dapr.cloudconfig";
2525

2626
/**
27-
* whether enable secret store
27+
* whether enable cloud config.
2828
*/
2929
private Boolean enabled = true;
3030

3131
/**
32-
* get config timeout
32+
* get config timeout (include wait for dapr sidecar).
3333
*/
3434
private Integer timeout = 2000;
3535

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.dapr.spring.boot.cloudconfig.configdata;
2+
3+
public enum DaprCloudConfigType {
4+
Doc,
5+
Value;
6+
7+
/**
8+
* Get Type from String.
9+
* @param value type specified in schema
10+
* @return type enum
11+
*/
12+
public static DaprCloudConfigType fromString(String value) {
13+
return "doc".equals(value)
14+
? DaprCloudConfigType.Doc
15+
: DaprCloudConfigType.Value;
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2025 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.spring.boot.cloudconfig.configdata;
15+
16+
import io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataResource;
17+
import org.springframework.boot.env.PropertySourceLoader;
18+
import org.springframework.core.env.PropertySource;
19+
import org.springframework.core.io.ByteArrayResource;
20+
import org.springframework.core.io.Resource;
21+
import org.springframework.core.io.support.SpringFactoriesLoader;
22+
import org.springframework.util.StringUtils;
23+
24+
import java.io.IOException;
25+
import java.util.ArrayList;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.Map;
29+
30+
public class DaprSecretStoreConfigParserHandler {
31+
32+
private static List<PropertySourceLoader> propertySourceLoaders;
33+
34+
private DaprSecretStoreConfigParserHandler() {
35+
propertySourceLoaders = SpringFactoriesLoader
36+
.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
37+
}
38+
39+
public static DaprSecretStoreConfigParserHandler getInstance() {
40+
return ParserHandler.HANDLER;
41+
}
42+
43+
/**
44+
* Parse Secret using PropertySourceLoaders.
45+
*
46+
* <p>
47+
* if type = doc, will treat all values as a property source (both "properties" or "yaml" format supported)
48+
* </p>
49+
*
50+
* <p>
51+
* if type = value, will transform key and value to "key=value" format ("properties" format)
52+
* </p>
53+
*
54+
* @param configName name of the config
55+
* @param configValue value of the config
56+
* @param type value type
57+
* @return property source list
58+
*/
59+
public List<PropertySource<?>> parseDaprSecretStoreData(
60+
String configName,
61+
Map<String, String> configValue,
62+
DaprCloudConfigType type
63+
) {
64+
List<PropertySource<?>> result = new ArrayList<>();
65+
66+
Map<String, Resource> configResults = getConfigResult(configValue, type);
67+
68+
configResults.forEach((key, configResult) -> {
69+
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
70+
String fullConfigName = StringUtils.hasText(key) ? configName + "." + key : configName;
71+
try {
72+
result.addAll(propertySourceLoader.load(fullConfigName, configResult));
73+
} catch (IOException ignored) {
74+
continue;
75+
}
76+
}
77+
});
78+
79+
return result;
80+
}
81+
82+
private Map<String, Resource> getConfigResult(
83+
Map<String, String> configValue,
84+
DaprCloudConfigType type
85+
) {
86+
Map<String, Resource> result = new HashMap<>();
87+
if (DaprCloudConfigType.Doc.equals(type)) {
88+
configValue.forEach((key, value) -> result.put(key, new ByteArrayResource(value.getBytes())));
89+
} else {
90+
List<String> configList = new ArrayList<>();
91+
configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value)));
92+
result.put("", new ByteArrayResource(String.join("\n", configList).getBytes()));
93+
}
94+
return result;
95+
}
96+
97+
private static class ParserHandler {
98+
99+
private static final DaprSecretStoreConfigParserHandler HANDLER = new DaprSecretStoreConfigParserHandler();
100+
101+
}
102+
}

dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,25 @@
1414
package io.dapr.spring.boot.cloudconfig.configdata.config;
1515

1616
import io.dapr.client.DaprClient;
17+
import io.dapr.client.domain.ConfigurationItem;
18+
import io.dapr.client.domain.GetConfigurationRequest;
1719
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager;
1820
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
19-
import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler;
21+
import io.dapr.spring.boot.cloudconfig.configdata.DaprSecretStoreConfigParserHandler;
2022
import org.apache.commons.logging.Log;
2123
import org.springframework.boot.context.config.ConfigData;
2224
import org.springframework.boot.context.config.ConfigDataLoader;
2325
import org.springframework.boot.context.config.ConfigDataLoaderContext;
2426
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
2527
import org.springframework.boot.logging.DeferredLogFactory;
2628
import org.springframework.core.env.PropertySource;
29+
import org.springframework.util.StringUtils;
2730
import reactor.core.publisher.Mono;
2831

2932
import java.io.IOException;
3033
import java.time.Duration;
3134
import java.util.ArrayList;
32-
import java.util.Collections;
35+
import java.util.HashMap;
3336
import java.util.List;
3437
import java.util.Map;
3538

@@ -45,6 +48,13 @@ public class DaprConfigurationConfigDataLoader implements ConfigDataLoader<DaprC
4548

4649
private DaprCloudConfigProperties daprSecretStoreConfig;
4750

51+
/**
52+
* Create a Config Data Loader to load config from Dapr Configuration api.
53+
*
54+
* @param logFactory logFactory
55+
* @param daprClient Dapr Client created
56+
* @param daprSecretStoreConfig Dapr Cloud Config Properties
57+
*/
4858
public DaprConfigurationConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient,
4959
DaprCloudConfigProperties daprSecretStoreConfig) {
5060
this.log = logFactory.getLog(getClass());
@@ -71,46 +81,59 @@ public ConfigData load(ConfigDataLoaderContext context, DaprConfigurationConfigD
7181
daprClient = DaprCloudConfigClientManager.getDaprClient();
7282
daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties();
7383

74-
if (resource.getSecretName() == null) {
75-
return fetchConfig(resource.getStoreName());
76-
} else {
77-
return fetchConfig(resource.getStoreName(), resource.getSecretName());
78-
}
79-
}
80-
81-
private ConfigData fetchConfig(String storeName) {
82-
Mono<Map<String, Map<String, String>>> secretMapMono = daprClient.getBulkSecret(storeName);
84+
waitForSidecar();
8385

84-
Map<String, Map<String, String>> secretMap =
85-
secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
86-
87-
if (secretMap == null) {
88-
return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
89-
}
90-
91-
List<PropertySource<?>> sourceList = new ArrayList<>();
86+
return fetchConfig(resource);
87+
}
9288

93-
for (Map.Entry<String, Map<String, String>> entry : secretMap.entrySet()) {
94-
sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(),
95-
entry.getValue()));
89+
private void waitForSidecar() throws IOException {
90+
try {
91+
daprClient.waitForSidecar(daprSecretStoreConfig.getTimeout())
92+
.retry(3)
93+
.block();
94+
} catch (RuntimeException e) {
95+
log.info(e.getMessage(), e);
96+
throw new IOException("Failed to wait for sidecar", e);
9697
}
97-
98-
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
9998
}
10099

101-
private ConfigData fetchConfig(String storeName, String secretName) {
102-
Mono<Map<String, String>> secretMapMono = daprClient.getSecret(storeName, secretName);
103-
104-
Map<String, String> secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
105-
106-
if (secretMap == null) {
107-
return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
100+
private ConfigData fetchConfig(DaprConfigurationConfigDataResource resource)
101+
throws IOException, ConfigDataResourceNotFoundException {
102+
Mono<Map<String, ConfigurationItem>> secretMapMono = daprClient.getConfiguration(new GetConfigurationRequest(
103+
resource.getStoreName(),
104+
StringUtils.hasText(resource.getConfigName())
105+
? List.of(resource.getConfigName())
106+
: null
107+
));
108+
109+
try {
110+
Map<String, ConfigurationItem> secretMap =
111+
secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
112+
113+
if (secretMap == null) {
114+
log.info("Config not found");
115+
throw new ConfigDataResourceNotFoundException(resource);
116+
}
117+
118+
List<PropertySource<?>> sourceList = new ArrayList<>();
119+
120+
Map<String, String> configMap = new HashMap<>();
121+
secretMap.forEach((key, value) -> {
122+
configMap.put(value.getKey(), value.getValue());
123+
});
124+
125+
sourceList.addAll(DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData(
126+
resource.getStoreName(),
127+
configMap,
128+
resource.getType()
129+
));
130+
131+
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
132+
} catch (RuntimeException e) {
133+
log.info("Failed to get config from sidecar: " + e.getMessage(), e);
134+
throw new IOException("Failed to get config from sidecar", e);
108135
}
109136

110-
List<PropertySource<?>> sourceList = new ArrayList<>(
111-
DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap));
112-
113-
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
114137
}
115138

116139
protected <T> T getBean(ConfigDataLoaderContext context, Class<T> type) {

dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties;
1717
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager;
1818
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
19+
import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType;
1920
import org.apache.commons.logging.Log;
2021
import org.springframework.boot.BootstrapRegistry;
2122
import org.springframework.boot.ConfigurableBootstrapContext;
@@ -30,6 +31,10 @@
3031
import org.springframework.boot.context.properties.bind.Binder;
3132
import org.springframework.boot.logging.DeferredLogFactory;
3233
import org.springframework.core.Ordered;
34+
import org.springframework.util.MultiValueMap;
35+
import org.springframework.util.StringUtils;
36+
import org.springframework.web.util.UriComponents;
37+
import org.springframework.web.util.UriComponentsBuilder;
3338

3439
import java.util.ArrayList;
3540
import java.util.List;
@@ -82,31 +87,42 @@ public List<DaprConfigurationConfigDataResource> resolve(ConfigDataLocationResol
8287

8388
List<DaprConfigurationConfigDataResource> result = new ArrayList<>();
8489

85-
String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/");
86-
87-
log.info(String.format("there is %d of values in secretConfig", secretConfig.length));
88-
89-
switch (secretConfig.length) {
90-
case 2:
91-
log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '"
92-
+ secretConfig[1] + "' secret store for config");
93-
result.add(
94-
new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1]));
95-
break;
96-
case 1:
97-
log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config");
98-
result.add(new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], null));
99-
break;
100-
default:
101-
throw new ConfigDataLocationNotFoundException(location);
90+
// To avoid UriComponentsBuilder to decode a wrong host.
91+
String fullConfig = "config://" + location.getNonPrefixedValue(PREFIX);
92+
93+
UriComponents configUri = UriComponentsBuilder.fromUriString(fullConfig).build();
94+
95+
String storeName = configUri.getHost();
96+
String configName = StringUtils.hasText(configUri.getPath())
97+
? StringUtils.trimLeadingCharacter(configUri.getPath(), '/')
98+
: null;
99+
100+
MultiValueMap<String, String> configQuery = configUri.getQueryParams();
101+
DaprCloudConfigType configType = DaprCloudConfigType.fromString(configQuery.getFirst("type"));
102+
Boolean subscribe = StringUtils.hasText(configQuery.getFirst("subscribe"))
103+
&& Boolean.parseBoolean(configQuery.getFirst("subscribe"));
104+
105+
106+
if (configName == null) {
107+
log.debug("Dapr Cloud Config now gains store name: '" + storeName + "' configuration for config");
108+
result.add(new DaprConfigurationConfigDataResource(location.isOptional(), storeName,
109+
null, configType, subscribe));
110+
111+
} else if (configName.contains("/")) {
112+
throw new ConfigDataLocationNotFoundException(location);
113+
114+
} else {
115+
log.debug("Dapr Cloud Config now gains store name: '" + storeName + "' and config name: '"
116+
+ configName + "' configuration for config");
117+
result.add(
118+
new DaprConfigurationConfigDataResource(location.isOptional(), storeName, configName,
119+
configType, subscribe));
120+
102121
}
103122

104123
return result;
105124
}
106125

107-
/**
108-
* @return
109-
*/
110126
@Override
111127
public int getOrder() {
112128
return -1;
@@ -115,10 +131,12 @@ public int getOrder() {
115131
private void registerConfigManager(DaprCloudConfigProperties properties,
116132
DaprClientProperties clientConfig,
117133
ConfigurableBootstrapContext bootstrapContext) {
118-
if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) {
119-
bootstrapContext.register(DaprCloudConfigClientManager.class,
120-
BootstrapRegistry.InstanceSupplier
121-
.of(new DaprCloudConfigClientManager(properties, clientConfig)));
134+
synchronized (DaprCloudConfigClientManager.class) {
135+
if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) {
136+
bootstrapContext.register(DaprCloudConfigClientManager.class,
137+
BootstrapRegistry.InstanceSupplier
138+
.of(new DaprCloudConfigClientManager(properties, clientConfig)));
139+
}
122140
}
123141
}
124142

0 commit comments

Comments
 (0)