Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2b4022b

Browse files
committedMar 4, 2025
feat(springboot cloudconfig): A new library that implement a backend of Spring Cloud Config(dapr#1225)
Originally from https://github.com/fangkehou-team/dapr-spring, this library created a backend of SpringCloudConfig just like SpringCloudVault. The original library only uses secret store as config store api is not stable at that time. As the configuration api is stable now, the config loader using that api would be implemented later. Signed-off-by: lony2003 <[email protected]>
1 parent f19a5f8 commit 2b4022b

16 files changed

+1018
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.dapr.spring</groupId>
8+
<artifactId>dapr-spring-parent</artifactId>
9+
<version>0.14.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>dapr-spring-cloudconfig</artifactId>
13+
<name>dapr-spring-cloudconfig</name>
14+
<description>Dapr Spring Cloud Config</description>
15+
<packaging>jar</packaging>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>io.dapr.spring</groupId>
20+
<artifactId>dapr-spring-boot-autoconfigure</artifactId>
21+
<version>${project.parent.version}</version>
22+
<scope>compile</scope>
23+
</dependency>
24+
</dependencies>
25+
26+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.autoconfigure;
18+
19+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
23+
import org.springframework.context.annotation.Configuration;
24+
25+
import io.dapr.client.DaprClient;
26+
27+
@Configuration(proxyBeanMethods = false)
28+
@EnableConfigurationProperties(DaprCloudConfigProperties.class)
29+
@ConditionalOnProperty(name = "dapr.secretstore.enabled", matchIfMissing = true)
30+
@ConditionalOnClass(DaprClient.class)
31+
public class DaprCloudConfigAutoConfiguration {
32+
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2024 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.config;
15+
16+
import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties;
17+
import io.dapr.spring.boot.autoconfigure.client.DaprConnectionDetails;
18+
19+
class CloudConfigPropertiesDaprConnectionDetails implements DaprConnectionDetails {
20+
21+
private final DaprClientProperties daprClientProperties;
22+
23+
public CloudConfigPropertiesDaprConnectionDetails(DaprClientProperties daprClientProperties) {
24+
this.daprClientProperties = daprClientProperties;
25+
}
26+
27+
@Override
28+
public String httpEndpoint() {
29+
return this.daprClientProperties.getHttpEndpoint();
30+
}
31+
32+
@Override
33+
public String grpcEndpoint() {
34+
return this.daprClientProperties.getGrpcEndpoint();
35+
}
36+
37+
@Override
38+
public Integer httpPort() {
39+
return this.daprClientProperties.getHttpPort();
40+
}
41+
42+
@Override
43+
public Integer grpcPort() {
44+
return this.daprClientProperties.getGrpcPort();
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.config;
18+
19+
import io.dapr.client.DaprClient;
20+
import io.dapr.client.DaprClientBuilder;
21+
import io.dapr.client.DaprPreviewClient;
22+
import io.dapr.config.Properties;
23+
import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties;
24+
import io.dapr.spring.boot.autoconfigure.client.DaprConnectionDetails;
25+
26+
27+
public class DaprCloudConfigClientManager {
28+
29+
private static DaprClient daprClient;
30+
private static DaprPreviewClient daprPreviewClient;
31+
private final DaprCloudConfigProperties daprCloudConfigProperties;
32+
private final DaprClientProperties daprClientConfig;
33+
34+
public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties,
35+
DaprClientProperties daprClientConfig) {
36+
this.daprCloudConfigProperties = daprCloudConfigProperties;
37+
this.daprClientConfig = daprClientConfig;
38+
39+
DaprClientBuilder daprClientBuilder = createDaprClientBuilder(
40+
createDaprConnectionDetails(daprClientConfig)
41+
);
42+
43+
if (DaprCloudConfigClientManager.daprClient == null) {
44+
DaprCloudConfigClientManager.daprClient = daprClientBuilder.build();
45+
}
46+
47+
if (DaprCloudConfigClientManager.daprPreviewClient == null) {
48+
DaprCloudConfigClientManager.daprPreviewClient = daprClientBuilder.buildPreviewClient();
49+
}
50+
}
51+
52+
private DaprConnectionDetails createDaprConnectionDetails(DaprClientProperties properties) {
53+
return new CloudConfigPropertiesDaprConnectionDetails(properties);
54+
}
55+
DaprClientBuilder createDaprClientBuilder(DaprConnectionDetails daprConnectionDetails) {
56+
DaprClientBuilder builder = new DaprClientBuilder();
57+
if (daprConnectionDetails.httpEndpoint() != null) {
58+
builder.withPropertyOverride(Properties.HTTP_ENDPOINT, daprConnectionDetails.httpEndpoint());
59+
}
60+
if (daprConnectionDetails.grpcEndpoint() != null) {
61+
builder.withPropertyOverride(Properties.GRPC_ENDPOINT, daprConnectionDetails.grpcEndpoint());
62+
}
63+
if (daprConnectionDetails.httpPort() != null) {
64+
builder.withPropertyOverride(Properties.HTTP_PORT, String.valueOf(daprConnectionDetails.httpPort()));
65+
}
66+
if (daprConnectionDetails.grpcPort() != null) {
67+
builder.withPropertyOverride(Properties.GRPC_PORT, String.valueOf(daprConnectionDetails.grpcPort()));
68+
}
69+
return builder;
70+
}
71+
72+
public static DaprPreviewClient getDaprPreviewClient() {
73+
return DaprCloudConfigClientManager.daprPreviewClient;
74+
}
75+
76+
public static DaprClient getDaprClient() {
77+
return DaprCloudConfigClientManager.daprClient;
78+
}
79+
80+
public DaprCloudConfigProperties getDaprCloudConfigProperties() {
81+
return daprCloudConfigProperties;
82+
}
83+
84+
public DaprClientProperties getDaprClientConfig() {
85+
return daprClientConfig;
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.config;
18+
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* The properties for creating dapr client.
23+
*/
24+
@ConfigurationProperties(DaprCloudConfigProperties.PROPERTY_PREFIX)
25+
public class DaprCloudConfigProperties {
26+
27+
public static final String PROPERTY_PREFIX = "dapr.cloudconfig";
28+
29+
/**
30+
* whether enable secret store
31+
*/
32+
private Boolean enabled = true;
33+
34+
/**
35+
* get config timeout
36+
*/
37+
private Integer timeout = 2000;
38+
39+
public Integer getTimeout() {
40+
return timeout;
41+
}
42+
43+
public void setTimeout(Integer timeout) {
44+
this.timeout = timeout;
45+
}
46+
47+
public Boolean getEnabled() {
48+
return enabled;
49+
}
50+
51+
public void setEnabled(Boolean enabled) {
52+
this.enabled = enabled;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.configdata.config;
18+
19+
import io.dapr.client.DaprClient;
20+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager;
21+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
22+
import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler;
23+
import org.apache.commons.logging.Log;
24+
import org.springframework.boot.context.config.ConfigData;
25+
import org.springframework.boot.context.config.ConfigDataLoader;
26+
import org.springframework.boot.context.config.ConfigDataLoaderContext;
27+
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
28+
import org.springframework.boot.logging.DeferredLogFactory;
29+
import org.springframework.core.env.PropertySource;
30+
import reactor.core.publisher.Mono;
31+
32+
import java.io.IOException;
33+
import java.time.Duration;
34+
import java.util.ArrayList;
35+
import java.util.Collections;
36+
import java.util.List;
37+
import java.util.Map;
38+
39+
import static org.springframework.boot.context.config.ConfigData.Option.*;
40+
41+
public class DaprConfigurationConfigDataLoader implements ConfigDataLoader<DaprConfigurationConfigDataResource> {
42+
43+
private final Log log;
44+
45+
private DaprClient daprClient;
46+
47+
private DaprCloudConfigProperties daprSecretStoreConfig;
48+
49+
public DaprConfigurationConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient,
50+
DaprCloudConfigProperties daprSecretStoreConfig) {
51+
this.log = logFactory.getLog(getClass());
52+
this.daprClient = daprClient;
53+
this.daprSecretStoreConfig = daprSecretStoreConfig;
54+
}
55+
56+
57+
/**
58+
* Load {@link ConfigData} for the given resource.
59+
*
60+
* @param context the loader context
61+
* @param resource the resource to load
62+
* @return the loaded config data or {@code null} if the location should be skipped
63+
* @throws IOException on IO error
64+
* @throws ConfigDataResourceNotFoundException if the resource cannot be found
65+
*/
66+
@Override
67+
public ConfigData load(ConfigDataLoaderContext context, DaprConfigurationConfigDataResource resource)
68+
throws IOException, ConfigDataResourceNotFoundException {
69+
DaprCloudConfigClientManager daprClientSecretStoreConfigManager =
70+
getBean(context, DaprCloudConfigClientManager.class);
71+
72+
daprClient = DaprCloudConfigClientManager.getDaprClient();
73+
daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties();
74+
75+
if (resource.getSecretName() == null) {
76+
return fetchConfig(resource.getStoreName());
77+
} else {
78+
return fetchConfig(resource.getStoreName(), resource.getSecretName());
79+
}
80+
}
81+
82+
private ConfigData fetchConfig(String storeName) {
83+
Mono<Map<String, Map<String, String>>> secretMapMono = daprClient.getBulkSecret(storeName);
84+
85+
Map<String, Map<String, String>> secretMap =
86+
secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
87+
88+
if (secretMap == null) {
89+
return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
90+
}
91+
92+
List<PropertySource<?>> sourceList = new ArrayList<>();
93+
94+
for (Map.Entry<String, Map<String, String>> entry : secretMap.entrySet()) {
95+
sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(),
96+
entry.getValue()));
97+
}
98+
99+
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
100+
}
101+
102+
private ConfigData fetchConfig(String storeName, String secretName) {
103+
Mono<Map<String, String>> secretMapMono = daprClient.getSecret(storeName, secretName);
104+
105+
Map<String, String> secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
106+
107+
if (secretMap == null) {
108+
return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
109+
}
110+
111+
List<PropertySource<?>> sourceList = new ArrayList<>(
112+
DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap));
113+
114+
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
115+
}
116+
117+
protected <T> T getBean(ConfigDataLoaderContext context, Class<T> type) {
118+
if (context.getBootstrapContext().isRegistered(type)) {
119+
return context.getBootstrapContext().get(type);
120+
}
121+
return null;
122+
}
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.configdata.config;
18+
19+
import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties;
20+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager;
21+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
22+
import org.apache.commons.logging.Log;
23+
import org.springframework.boot.BootstrapRegistry;
24+
import org.springframework.boot.ConfigurableBootstrapContext;
25+
import org.springframework.boot.context.config.*;
26+
import org.springframework.boot.context.properties.bind.BindHandler;
27+
import org.springframework.boot.context.properties.bind.Bindable;
28+
import org.springframework.boot.context.properties.bind.Binder;
29+
import org.springframework.boot.logging.DeferredLogFactory;
30+
import org.springframework.core.Ordered;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
35+
public class DaprConfigurationConfigDataLocationResolver
36+
implements ConfigDataLocationResolver<DaprConfigurationConfigDataResource>, Ordered {
37+
38+
public static final String PREFIX = "dapr:config:";
39+
40+
private final Log log;
41+
42+
public DaprConfigurationConfigDataLocationResolver(DeferredLogFactory logFactory) {
43+
this.log = logFactory.getLog(getClass());
44+
}
45+
46+
/**
47+
* Returns if the specified location address contains dapr prefix.
48+
*
49+
* @param context the location resolver context
50+
* @param location the location to check.
51+
* @return if the location is supported by this resolver
52+
*/
53+
@Override
54+
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
55+
log.debug(String.format("checking if %s suits for dapr config", location.toString()));
56+
return location.hasPrefix(PREFIX);
57+
}
58+
59+
/**
60+
* Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances.
61+
*
62+
* @param context the location resolver context
63+
* @param location the location that should be resolved
64+
* @return a list of {@link ConfigDataResource resources} in ascending priority order.
65+
* @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found
66+
* @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found
67+
*/
68+
@Override
69+
public List<DaprConfigurationConfigDataResource> resolve(ConfigDataLocationResolverContext context,
70+
ConfigDataLocation location)
71+
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
72+
73+
DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context);
74+
DaprClientProperties daprClientConfig = loadClientProperties(context);
75+
76+
ConfigurableBootstrapContext bootstrapContext = context
77+
.getBootstrapContext();
78+
79+
registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext);
80+
81+
List<DaprConfigurationConfigDataResource> result = new ArrayList<>();
82+
83+
String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/");
84+
85+
log.info(String.format("there is %d of values in secretConfig", secretConfig.length));
86+
87+
switch (secretConfig.length) {
88+
case 2:
89+
log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '"
90+
+ secretConfig[1] + "' secret store for config");
91+
result.add(
92+
new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1]));
93+
break;
94+
case 1:
95+
log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config");
96+
result.add(new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], null));
97+
break;
98+
default:
99+
throw new ConfigDataLocationNotFoundException(location);
100+
}
101+
102+
return result;
103+
}
104+
105+
/**
106+
* @return
107+
*/
108+
@Override
109+
public int getOrder() {
110+
return -1;
111+
}
112+
113+
private void registerConfigManager(DaprCloudConfigProperties properties,
114+
DaprClientProperties clientConfig,
115+
ConfigurableBootstrapContext bootstrapContext) {
116+
if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) {
117+
bootstrapContext.register(DaprCloudConfigClientManager.class,
118+
BootstrapRegistry.InstanceSupplier
119+
.of(new DaprCloudConfigClientManager(properties, clientConfig)));
120+
}
121+
}
122+
123+
protected DaprCloudConfigProperties loadProperties(
124+
ConfigDataLocationResolverContext context) {
125+
Binder binder = context.getBinder();
126+
BindHandler bindHandler = getBindHandler(context);
127+
128+
DaprCloudConfigProperties daprCloudConfigProperties;
129+
if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) {
130+
daprCloudConfigProperties = context.getBootstrapContext()
131+
.get(DaprCloudConfigProperties.class);
132+
} else {
133+
daprCloudConfigProperties = binder
134+
.bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class),
135+
bindHandler)
136+
.orElseGet(DaprCloudConfigProperties::new);
137+
}
138+
139+
return daprCloudConfigProperties;
140+
}
141+
142+
protected DaprClientProperties loadClientProperties(
143+
ConfigDataLocationResolverContext context) {
144+
Binder binder = context.getBinder();
145+
BindHandler bindHandler = getBindHandler(context);
146+
147+
DaprClientProperties daprClientConfig;
148+
if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) {
149+
daprClientConfig = context.getBootstrapContext()
150+
.get(DaprClientProperties.class);
151+
} else {
152+
daprClientConfig = binder
153+
.bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class),
154+
bindHandler)
155+
.orElseGet(DaprClientProperties::new);
156+
}
157+
158+
return daprClientConfig;
159+
}
160+
161+
private BindHandler getBindHandler(ConfigDataLocationResolverContext context) {
162+
return context.getBootstrapContext().getOrElse(BindHandler.class, null);
163+
}
164+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.configdata.config;
18+
19+
import org.springframework.boot.context.config.ConfigDataResource;
20+
21+
public class DaprConfigurationConfigDataResource extends ConfigDataResource {
22+
private final String storeName;
23+
private final String secretName;
24+
25+
/**
26+
* Create a new non-optional {@link ConfigDataResource} instance.
27+
*/
28+
public DaprConfigurationConfigDataResource(String storeName, String secretName) {
29+
this.storeName = storeName;
30+
this.secretName = secretName;
31+
}
32+
33+
/**
34+
* Create a new {@link ConfigDataResource} instance.
35+
*
36+
* @param optional if the resource is optional
37+
* @since 2.4.6
38+
*/
39+
public DaprConfigurationConfigDataResource(boolean optional, String storeName, String secretName) {
40+
super(optional);
41+
this.storeName = storeName;
42+
this.secretName = secretName;
43+
}
44+
45+
public String getStoreName() {
46+
return storeName;
47+
}
48+
49+
public String getSecretName() {
50+
return secretName;
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.configdata.secret;
18+
19+
import static org.springframework.boot.context.config.ConfigData.Option.*;
20+
21+
import java.io.IOException;
22+
import java.time.Duration;
23+
import java.util.ArrayList;
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager;
29+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
30+
import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler;
31+
import org.apache.commons.logging.Log;
32+
import org.springframework.boot.context.config.ConfigData;
33+
import org.springframework.boot.context.config.ConfigDataLoader;
34+
import org.springframework.boot.context.config.ConfigDataLoaderContext;
35+
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
36+
import org.springframework.boot.logging.DeferredLogFactory;
37+
import org.springframework.core.env.PropertySource;
38+
39+
import io.dapr.client.DaprClient;
40+
import reactor.core.publisher.Mono;
41+
42+
public class DaprSecretStoreConfigDataLoader implements ConfigDataLoader<DaprSecretStoreConfigDataResource> {
43+
44+
private final Log log;
45+
46+
private DaprClient daprClient;
47+
48+
private DaprCloudConfigProperties daprSecretStoreConfig;
49+
50+
public DaprSecretStoreConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient,
51+
DaprCloudConfigProperties daprSecretStoreConfig) {
52+
this.log = logFactory.getLog(getClass());
53+
this.daprClient = daprClient;
54+
this.daprSecretStoreConfig = daprSecretStoreConfig;
55+
}
56+
57+
58+
/**
59+
* Load {@link ConfigData} for the given resource.
60+
*
61+
* @param context the loader context
62+
* @param resource the resource to load
63+
* @return the loaded config data or {@code null} if the location should be skipped
64+
* @throws IOException on IO error
65+
* @throws ConfigDataResourceNotFoundException if the resource cannot be found
66+
*/
67+
@Override
68+
public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDataResource resource)
69+
throws IOException, ConfigDataResourceNotFoundException {
70+
DaprCloudConfigClientManager daprClientSecretStoreConfigManager =
71+
getBean(context, DaprCloudConfigClientManager.class);
72+
73+
daprClient = DaprCloudConfigClientManager.getDaprClient();
74+
daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties();
75+
76+
if (resource.getSecretName() == null) {
77+
return fetchConfig(resource.getStoreName());
78+
} else {
79+
return fetchConfig(resource.getStoreName(), resource.getSecretName());
80+
}
81+
}
82+
83+
private ConfigData fetchConfig(String storeName) {
84+
Mono<Map<String, Map<String, String>>> secretMapMono = daprClient.getBulkSecret(storeName);
85+
86+
Map<String, Map<String, String>> secretMap =
87+
secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
88+
89+
if (secretMap == null) {
90+
return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
91+
}
92+
93+
List<PropertySource<?>> sourceList = new ArrayList<>();
94+
95+
for (Map.Entry<String, Map<String, String>> entry : secretMap.entrySet()) {
96+
sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(),
97+
entry.getValue()));
98+
}
99+
100+
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
101+
}
102+
103+
private ConfigData fetchConfig(String storeName, String secretName) {
104+
Mono<Map<String, String>> secretMapMono = daprClient.getSecret(storeName, secretName);
105+
106+
Map<String, String> secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout()));
107+
108+
if (secretMap == null) {
109+
return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
110+
}
111+
112+
List<PropertySource<?>> sourceList = new ArrayList<>(
113+
DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap));
114+
115+
return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC);
116+
}
117+
118+
protected <T> T getBean(ConfigDataLoaderContext context, Class<T> type) {
119+
if (context.getBootstrapContext().isRegistered(type)) {
120+
return context.getBootstrapContext().get(type);
121+
}
122+
return null;
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.configdata.secret;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties;
23+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager;
24+
import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties;
25+
import org.apache.commons.logging.Log;
26+
import org.springframework.boot.BootstrapRegistry;
27+
import org.springframework.boot.ConfigurableBootstrapContext;
28+
import org.springframework.boot.context.config.*;
29+
import org.springframework.boot.context.properties.bind.BindHandler;
30+
import org.springframework.boot.context.properties.bind.Bindable;
31+
import org.springframework.boot.context.properties.bind.Binder;
32+
import org.springframework.boot.logging.DeferredLogFactory;
33+
import org.springframework.core.Ordered;
34+
35+
public class DaprSecretStoreConfigDataLocationResolver
36+
implements ConfigDataLocationResolver<DaprSecretStoreConfigDataResource>, Ordered {
37+
38+
public static final String PREFIX = "dapr:secret:";
39+
40+
private final Log log;
41+
42+
public DaprSecretStoreConfigDataLocationResolver(DeferredLogFactory logFactory) {
43+
this.log = logFactory.getLog(getClass());
44+
}
45+
46+
/**
47+
* Returns if the specified location address contains dapr prefix.
48+
*
49+
* @param context the location resolver context
50+
* @param location the location to check.
51+
* @return if the location is supported by this resolver
52+
*/
53+
@Override
54+
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
55+
log.debug(String.format("checking if %s suits for dapr secret", location.toString()));
56+
return location.hasPrefix(PREFIX);
57+
}
58+
59+
/**
60+
* Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances.
61+
*
62+
* @param context the location resolver context
63+
* @param location the location that should be resolved
64+
* @return a list of {@link ConfigDataResource resources} in ascending priority order.
65+
* @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found
66+
* @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found
67+
*/
68+
@Override
69+
public List<DaprSecretStoreConfigDataResource> resolve(ConfigDataLocationResolverContext context,
70+
ConfigDataLocation location)
71+
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
72+
73+
DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context);
74+
DaprClientProperties daprClientConfig = loadClientProperties(context);
75+
76+
ConfigurableBootstrapContext bootstrapContext = context
77+
.getBootstrapContext();
78+
79+
registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext);
80+
81+
List<DaprSecretStoreConfigDataResource> result = new ArrayList<>();
82+
83+
String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/");
84+
85+
log.info(String.format("there is %d of values in secretConfig", secretConfig.length));
86+
87+
switch (secretConfig.length) {
88+
case 2:
89+
log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '"
90+
+ secretConfig[1] + "' secret store for config");
91+
result.add(
92+
new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1]));
93+
break;
94+
case 1:
95+
log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config");
96+
result.add(new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], null));
97+
break;
98+
default:
99+
throw new ConfigDataLocationNotFoundException(location);
100+
}
101+
102+
return result;
103+
}
104+
105+
/**
106+
* @return
107+
*/
108+
@Override
109+
public int getOrder() {
110+
return -1;
111+
}
112+
113+
private void registerConfigManager(DaprCloudConfigProperties properties,
114+
DaprClientProperties clientConfig,
115+
ConfigurableBootstrapContext bootstrapContext) {
116+
if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) {
117+
bootstrapContext.register(DaprCloudConfigClientManager.class,
118+
BootstrapRegistry.InstanceSupplier
119+
.of(new DaprCloudConfigClientManager(properties, clientConfig)));
120+
}
121+
}
122+
123+
protected DaprCloudConfigProperties loadProperties(
124+
ConfigDataLocationResolverContext context) {
125+
Binder binder = context.getBinder();
126+
BindHandler bindHandler = getBindHandler(context);
127+
128+
DaprCloudConfigProperties daprCloudConfigProperties;
129+
if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) {
130+
daprCloudConfigProperties = context.getBootstrapContext()
131+
.get(DaprCloudConfigProperties.class);
132+
} else {
133+
daprCloudConfigProperties = binder
134+
.bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class),
135+
bindHandler)
136+
.orElseGet(DaprCloudConfigProperties::new);
137+
}
138+
139+
return daprCloudConfigProperties;
140+
}
141+
142+
protected DaprClientProperties loadClientProperties(
143+
ConfigDataLocationResolverContext context) {
144+
Binder binder = context.getBinder();
145+
BindHandler bindHandler = getBindHandler(context);
146+
147+
DaprClientProperties daprClientConfig;
148+
if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) {
149+
daprClientConfig = context.getBootstrapContext()
150+
.get(DaprClientProperties.class);
151+
} else {
152+
daprClientConfig = binder
153+
.bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class),
154+
bindHandler)
155+
.orElseGet(DaprClientProperties::new);
156+
}
157+
158+
return daprClientConfig;
159+
}
160+
161+
private BindHandler getBindHandler(ConfigDataLocationResolverContext context) {
162+
return context.getBootstrapContext().getOrElse(BindHandler.class, null);
163+
}
164+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.configdata.secret;
18+
19+
import org.springframework.boot.context.config.ConfigDataResource;
20+
21+
public class DaprSecretStoreConfigDataResource extends ConfigDataResource {
22+
private final String storeName;
23+
private final String secretName;
24+
25+
/**
26+
* Create a new non-optional {@link ConfigDataResource} instance.
27+
*/
28+
public DaprSecretStoreConfigDataResource(String storeName, String secretName) {
29+
this.storeName = storeName;
30+
this.secretName = secretName;
31+
}
32+
33+
/**
34+
* Create a new {@link ConfigDataResource} instance.
35+
*
36+
* @param optional if the resource is optional
37+
* @since 2.4.6
38+
*/
39+
public DaprSecretStoreConfigDataResource(boolean optional, String storeName, String secretName) {
40+
super(optional);
41+
this.storeName = storeName;
42+
this.secretName = secretName;
43+
}
44+
45+
public String getStoreName() {
46+
return storeName;
47+
}
48+
49+
public String getSecretName() {
50+
return secretName;
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2016-2024 Team Fangkehou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.dapr.spring.boot.cloudconfig.parser;
18+
19+
import java.io.IOException;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import org.springframework.boot.env.PropertySourceLoader;
25+
import org.springframework.core.env.PropertySource;
26+
import org.springframework.core.io.ByteArrayResource;
27+
import org.springframework.core.io.Resource;
28+
import org.springframework.core.io.support.SpringFactoriesLoader;
29+
30+
public class DaprSecretStoreParserHandler {
31+
32+
private static List<PropertySourceLoader> propertySourceLoaders;
33+
34+
private DaprSecretStoreParserHandler() {
35+
propertySourceLoaders = SpringFactoriesLoader
36+
.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
37+
}
38+
39+
public List<PropertySource<?>> parseDaprSecretStoreData(String configName, Map<String, String> configValue) {
40+
List<PropertySource<?>> result = new ArrayList<>();
41+
42+
List<String> configList = new ArrayList<>();
43+
configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value)));
44+
45+
Resource configResult = new ByteArrayResource(String.join("\n", configList).getBytes());
46+
47+
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
48+
try {
49+
result.addAll(propertySourceLoader.load(configName, configResult));
50+
} catch (IOException ignored) {
51+
}
52+
}
53+
54+
return result;
55+
}
56+
57+
public static DaprSecretStoreParserHandler getInstance() {
58+
return ParserHandler.HANDLER;
59+
}
60+
61+
private static class ParserHandler {
62+
63+
private static final DaprSecretStoreParserHandler HANDLER = new DaprSecretStoreParserHandler();
64+
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"properties": [
3+
{
4+
"name": "dapr.cloudconfig.enabled",
5+
"type": "java.lang.Boolean",
6+
"defaultValue": true,
7+
"sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties",
8+
"description": "enable dapr secret store or not."
9+
},
10+
{
11+
"name": "dapr.cloudconfig.timeout",
12+
"type": "java.lang.Integer",
13+
"defaultValue": 2000,
14+
"sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties",
15+
"description": "timeout for getting dapr secret store."
16+
}
17+
]
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# ConfigData Location Resolvers
2+
org.springframework.boot.context.config.ConfigDataLocationResolver=\
3+
io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver
4+
5+
# ConfigData Loaders
6+
org.springframework.boot.context.config.ConfigDataLoader=\
7+
io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.dapr.spring.boot.cloudconfig.autoconfigure.DaprCloudConfigAutoConfiguration

‎dapr-spring/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<module>dapr-spring-boot-tests</module>
2626
<module>dapr-spring-boot-starters/dapr-spring-boot-starter</module>
2727
<module>dapr-spring-boot-starters/dapr-spring-boot-starter-test</module>
28+
<module>dapr-spring-cloudconfig</module>
2829
</modules>
2930

3031
<properties>

0 commit comments

Comments
 (0)
Please sign in to comment.